Deploy NestJS บน Lambda ด้วย Docker มันทำยังไงว้า~

Netipun "Nae" Jiwjaroen
Abbon Corporation
Published in
3 min readOct 11, 2023

With story behind the scene

หนูกำหมัด + logo half-life + วาฬแบกตู้

Intro

สวัสดีผู้อ่านที่เป็นที่รักของผมทุกท่าน วันนี้อาจจะแปลกใจที่ไม่ได้มาเขียนรีวิวบอร์ด(ที่ยังดองไว้อีกมากโข) เนื่องจากบทความนี้

เป็นเรื่องการ Deploy NestJS บน AWS Lambda ด้วยการใช้ Docker กัน

กล่าวทักทายท่านผู้อ่านกลุ่มเดิมที่อ่านและติดตามเรื่องคีย์บอร์ดกันอยู่เสมอมา พวกท่านอาจจะทราบเรื่องว่าเมื่อวันที่ 5 ต.ค. 66 วันนั้น ตัวผมมีการ Stream build QK100 ให้ลูกค้าท่านหนึ่งไปทาง Twitch จนดึกดื่นตามปกติวิสัย ซึ่งโดยปกติแล้วหลังเวลางานผมจะไม่ค่อยหยิบจับมือถือมาเช็ค LINE สักเท่าไหร่……แต่

ณ วันนั้นเหมือนมีองค์เทพสามตาละมาดลใจ นุยๆนุ้ย~ 😂 ผมเปิด LINE ขึ้นมาอ่านเพื่ออะไรก็ไม่รู้แล้วพบว่างานเข้า!

ภาพจำลองเหตุการณ์จริง

แน่นอนว่าคนขยัน(?)อย่างผมไม่นิ่งดูดาย วิ่งไปคว้าคอมพับตราผลไม้มาเพื่อเข้าไป Join ทันทีและหลังจากนี้คือเรื่องราวของคืนนั้นครับ

First part

เข้าไปถึงก็พบสามหนุ่มนั่งสุมหัวกันอยู่ในห้องประชุมทาง Discord กำลังปั่นงานช่วยกันเรื่องที่จะต้อง Deploy API ขึ้นเพราะลูกค้า Surprise เราเล่นเล็กๆ ก็คือยังไงจะต้องเอาขึ้นไปให้ได้ในคืนนี้แหละ

คนแรก Dev-Ops มือพระกาฬ (เล่น Custom keyboard เช่นกัน ที่รู้เพราะเสียงบอร์ดเข้าไมค์แล้วผมเลยถามดื้อๆว่าเล่นใช่ไหมครับ 🤣) กำลังจัดการกับ AWS นู่นนี่นั่น ปั่นกันสุดชีวิตซึ่งพาร์ทนี้ผมบอกตรงๆว่าไม่รู้เรื่องจ้า ก็ให้เขาทำงานของเขาไปเราก็ชวนคุยเล่นไปเรื่องบอร์ดนิดหน่อยให้เสียการเสียงานพอหอมปากหอมคอ

คนที่สอง อปป้า หัวหน้าผู้ทำการเรียกผมมาร่วมประสบภัยและมีศักดิ์เป็นลูกพี่ลูกน้องของผม (เขาเป็นลูกพี่ ส่วนผมเป็นลูกน้อง 555) กำลังขมักเขม้นกับการโม Code ในส่วนที่เป็น Environment settings ต่างๆพร้อมทั้งตอบคำถามของทุกคนอยู่

คนที่สาม เทพ.Net ท่านนึง คู่หูผู้เป็นทั้งอาจารย์และเป็นศิษย์ของผมในเวลาเดียวกัน กำลังติด Stun เพราะเพิ่งจับ NodeJS ได้ไม่นาน ยังดีว่ามีพื้นฐานการใช้งาน AWS พอตัวกำลังหาทาง Deploy NestJS ลงบน Lambda

คนที่สี่(เลขนะไม่ใช่ภาษาอีสาน) ผมเองผู้ซึ่ง Docker ก็งูๆปลาๆ, ไม่เคยจับ AWS และไม่รู้ว่า Lambda ซึ่งเป็น Serverless มันจะเอา NestJS ไปวางยังไง……………😱 เนื่องจาก 7–8 ปีที่ผ่านมาแทบจะใช้แต่ AdonisJS ซึ่งจะกั๊กเอาไว้เล่าทีหลังแล้วกันว่ามันดียังไงครับ

Second part

ในเมื่อมาถึงแล้วก็จัดไป ผมไม่รอช้าเข้าคลุกวงในทันทีอะไรที่ไม่รู้ก็ไปเรียนรู้เอาเดี๋ยวนี้แหละ คิดในใจอย่างเดียวว่ายังไงก็ต้องได้ ต้องทันเวลา

หลังจากที่ไล่อ่าน Document ต่างๆ อ่านบทความอีกมากมาย รวมไปถึงถาม GPT แล้วนั้นไซร้ สรุปได้ว่าท่า Deploy ที่เราถูกกำหนดให้ทำสำหรับงานนี้นั้นมันไม่มีใครเขียนไว้เลย เขาใช้ Library ตัวนึงในการเอาขึ้นกันทั้งนั้น 🤯

ทีนี้ก็งานละเอียดล่ะครับพี่น้อง ต้องมานั่งไล่แกะไล่แงะทำความเข้าใจ อาศัย Basic, ประสบการณ์, การสื่อสารในทีม และความถึก

สุดท้ายเราก็นั่งหาทำกัน ดัดแปลงกระบวนท่ากันอยู่พักใหญ่จนตกผลึกและได้ข้อสรุปเมื่อรุ่งสาง

แล้วผมก็จะเข้าเรื่องมันตรงนี้ดื้อๆนี่แหละว่าสรุปแล้วเราทำแบบไหนมันถึงได้วางสำเร็จครับ

NestJS part

ผมจะอนุมานว่าท่านมีความคุ้นเคยกับ NestJS, NodeJS, TypeScript, NPM กันบ้างแล้วละกันเพราะหากอ่านลงมาถึงนี่แล้วถ้าไม่ใช่อ่านเอามันส์ก็คือท่านต้องมี Basic มาแล้วแหละ

จากเดิมปกติแล้วเราจะใช้ไฟล์หนึ่งคือ main.ts เป็น Entry point ใช่ไหมครับ แต่เนื่องจากว่าเราจะใช้ Lambda ซึ่งเป็น Serverless แล้วยังต้องเก็บ main.ts ไว้เพื่อใช้ใน Local dev อีก ดังนั้นแล้วเราจึงต้องสร้างเจ้าไฟล์ lambda.ts นี่ขึ้นมาวางไว้ใน Level เดียวกับ main.ts

//lambda.ts
import { configure as serverlessExpress } from '@vendia/serverless-express'
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'

let cachedServer

export const handler = async (event, context) => {
if (!cachedServer) {
const nestApp = await NestFactory.create(AppModule)
await nestApp.init()
cachedServer = serverlessExpress({
app: nestApp.getHttpAdapter().getInstance(),
})
}

return cachedServer(event, context)
}
วาง lambda.ts ไว้ข้าง main.ts แถวนี้แหละ

Docker part

สร้าง Dockerfile ขึ้นมาแล้วใช้ image nodejs 18 จาก AWS Lambda เป็นตัวตั้ง
ในตัวอย่างผมใช้ Prisma ในงานด้วยก็เลยต้องมีกระบวนการ generate ของมันติดไปใน Dockerfile ด้วย ซึ่งหากท่านไม่ใช้ก็ละมันไปได้เลย

#Dockerfile
FROM public.ecr.aws/lambda/nodejs:18 AS base

FROM base AS dependencies
RUN mkdir -p /app
WORKDIR /app
COPY package*.json .
RUN npm install


FROM base AS build
WORKDIR /app
COPY . .
COPY --from=dependencies /app/node_modules ./node_modules
RUN npm run build
RUN npx prisma generate

FROM base AS deploy
WORKDIR /app
ARG BUILD_ENV=production
RUN echo '==== BUILD ' ${BUILD_ENV} ' ENV ===='
ENV NODE_ENV=${BUILD_ENV}

COPY --from=build /app/dist ${LAMBDA_TASK_ROOT}/dist
COPY ./prisma ${LAMBDA_TASK_ROOT}/prisma
COPY --from=build /app/node_modules ${LAMBDA_TASK_ROOT}/node_modules

CMD ["dist/lambda.handler" ]

จะเห็นได้ว่าเราแบ่งซอยมันเป็น 3 Stage ไม่ใช่เพราะอยากเท่เฉยๆแต่เนื่องจากว่าเราต้องไล่เรียงการทำงานของ Docker ให้มัน Build ได้อย่างถูกต้องและวางทุกอย่างให้อยู่ถูกที่

อย่าลืม .dockerignore เพื่อให้ Docker มันไม่เอาส่วนที่เราไม่อยากให้ติดไปด้วยตอน Build

#.dockerignore
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/charts
**/docker-compose*
**/compose*
**/Dockerfile*
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

อธิบายแบบคร่าวๆก็คือเราลง Dependencies ต่อด้วย Compile แล้วไล่ Copy ส่วนต่างๆให้มันไปวางให้ถูก เสร็จแล้วก็เรียกไปที่ dist/lambda.js (เป็น .js เพราะถูก Compile แล้ว) ใน Method ที่ชื่อว่า handler เพื่อให้มันทำงานครับ

วางทั้งสองไฟล์ไว้ที่ level นี้

Deploy part

เริ่มจากการสั่ง Build ด้วย Docker ก่อน มันก็จะไปอ่านไฟล์ Dockerfile ที่เราทำไปก่อนหน้านี้

กรณีของเราต้องการใช้ linux/amd64 ก็กำหนดไปด้วยเพราะถ้าไม่กำหนดไปใน Flag platform มันก็จะเป็นไปตามเครื่องที่เราใช้ Build มันแหละ

docker buildx build --platform linux/amd64 --build-arg BUILD_ENV=pgd -t [aws_ecr_path]:lambda-poc .
ผลจากการ run คำสั่ง build

หลังจาก Build เสร็จก็เอามันขึ้นไปโลดเป็นอันจบกระบวนท่า

docker push [aws_ecr_path]:lambda-poc

The end

นั่นล่ะฮะท่านผู้ชม เห็นเราทำกันจริงๆไม่กี่บรรทัดนี่ล่ะ แต่กว่าจะได้ออกมาเป็นผลสำเร็จของบทความนี้เราก็ทำแล้วลบเอาขึ้นไปลองวนไป หาท่าใหม่อยู่กันหลายสิบรอบจนถึงได้ต้องมีการเขียนบทความนี้ขึ้นมาเพื่อส่งต่อความรู้นี้ไว้สู่ผู้ประสบภัยร่วมชะตากรรมเดียวกันในภายภาคหน้า 😘

หวังว่ามันจะเป็นประโยชน์และเป็นแนวทางให้ท่านได้ไม่มากก็น้อย ถ้าชอบใจก็อย่าลืมกด Follow และ Clap เพื่อเป็นกำลังใจให้ผมด้วย เพียงท่านกระดิกนิ้วกดคลิกให้ผมสักทีสองทีให้ผมได้มีกำลังใจก็เพียงพอแล้วครับ 😁

Bonus cat!

ตามธรรมเนียมของบทความที่ผมเขียนขึ้นมา หากครั้งไหนขาดส่วนนี้ไปก็ย่อมมีคำถามจากท่านผู้อ่านที่ติดตามอ่านกันมาประจำตามมาเสมอ ดังนั้น“ของมันต้องมี

ดช.ไมค์กี้ 4 เดือน ผู้เพิ่งถูกพาไปรพ. เนื่องจากไม่สบายถ่ายเหลวแล้วต้องเอายากลับมากินที่บ้าน กำลังทำเป็นเก่งจะแทะขวดยาโชว์

ยาอะได้กินแน่ แต่ไม่ใช่กินทั้งขวดแบบนี้ลูกกกกกก

--

--

Netipun "Nae" Jiwjaroen
Abbon Corporation

My name is Netipun "Nae" Jiwjaroen AKA Kurenai. Custom keyboard enthusiasm, Streamer, Gamer, Musician, Cat Lover and also Programmer