서버리스 NestJS (AWS Lambda 통합)
NestJS를 AWS Lambda 핸들러에 연결하고 콜드 스타트·상태 저장·연결 재사용을 고려해 서버리스로 배포합니다.
지난 11장에서는 NestJS 애플리케이션의 배포와 안정 운영을 위한 Docker 컨테이너화, CI/CD 파이프라인, 클라우드 배포 전략, 모니터링/로깅 시스템을 다뤘습니다.
12장에서는 NestJS 고급 주제를 다루며, 먼저 서버리스(Serverless) 환경에서의 NestJS를 설명합니다.
서버리스는 클라우드 컴퓨팅의 패러다임 중 하나로, 개발자가 서버 인프라를 직접 관리할 필요 없이 애플리케이션 코드 작성에만 집중할 수 있게 해줍니다.
NestJS는 유연한 구조 덕분에 이러한 서버리스 환경에서도 효과적으로 작동할 수 있습니다.
Lambda 적합성 판단부터 cold·warm 실행과 상태 외부화까지
Lambda를 선택한 사례에서 cold 초기화·처리·외부 상태·다음 warm 재사용의 실행 상태를 구별할 수 있다.
Nest · Lambda lifecycle
Lambda 선택과 cold·warm 실행
원문의 Node20 handler 예제로 초기화 완료 후 캐시와 실행 환경 재사용을 구분합니다. 외부 업무 상태·연결 예산은 추가 운영 설계입니다. 현재 배포에는 지원 runtime과 handler·adapter 계약을 별도로 맞춰야 합니다.
-
실행 형태를 먼저 선택
함수가 직접 지속 연결을 소유하거나 작업 시간이 함수 예산을 넘으면 컨테이너·잡을 검토합니다. Lambda response streaming 지원은 별도로 확인합니다.
-
비동기 이벤트 진입
원문 handler는 event·context·callback을 받아 serverless-express handler로 전달합니다.
-
cold 초기화
cachedServer가 없으면 bootstrap을 실행하고, 완료된 handler만 저장합니다. 동시 최초 호출을 합치는 Promise cache는 없습니다.
-
warm 재사용
준비된 실행 환경은 handler와 연결을 재사용할 수 있지만 그 메모리는 내구 상태가 아닙니다.
-
요청 처리 예산
예약 동시성과 환경별 DB pool을 함께 제한하고 외부 호출 timeout을 함수 예산보다 짧게 둡니다.
-
상태 외부화
세션·파일·작업 진행 상태는 DB, cache, object storage, queue에 기록해 freeze나 폐기 후에도 남깁니다.
원문의 nodejs20.x는 deprecated 예시입니다. Node24의 Lambda async handler는 event·context를 받으며 callback을 지원하지 않습니다. 현재 배포는 runtime·adapter 계약과 전체 의존성 패키징을 다시 검증해야 합니다. 원문 warm-up은 bootstrap 전에 반환해 Nest 초기화를 보장하지 않습니다.
이 예의 조건
- 두 박스는 같은 실행의 진행 정보와 자료·결과를 관찰하는 고정 역할입니다. 박스의 좌우·위아래 위치를 별도 호출 순서로 해석하지 않습니다.
- 조건별 종료는 대안입니다. 실패 뒤 정상 후속 단계를 실행하거나 독립 비교 사례를 같은 요청으로 연결하지 않습니다.
- 기존 두 영역, 아홉 노드와 아홉 화살표다. 지속 연결·예산 초과는 컨테이너로, 나머지는 Lambda로 향한다. cold 초기화와 warm 재사용은 각각 요청 처리로 합쳐진다. 상태 저장은 외부 단계이며 메모리 재사용과 다르다.
- 지속 연결과 실행 예산으로 Lambda 또는 서버리스 컨테이너를 고른다. Lambda에서는 API Gateway 이벤트가 cold 초기화 또는 warm handler 재사용으로 갈라진 뒤 하나의 Nest 요청 처리로 합쳐지고, 내구 상태는 외부 저장소에 기록된다.
| 장면 | 관찰 값 | 전환 이유 |
|---|---|---|
| cold 또는 warm 시작 Lambda 선택 경로에서 cold는 초기화하고 warm은 기존 실행 환경을 재사용합니다. | cold: 초기화 · warm: 재사용 · 지속 연결·예산 초과: 컨테이너 별도 선택 | 요청 처리 전 준비 상태가 실행 환경에 따라 다릅니다. |
| 요청 처리 cold와 warm 경로가 요청 처리로 합쳐집니다. | 실행: 요청 처리 · 메모리: 실행 환경 내부 | 준비된 실행 환경에서 업무가 진행됩니다. |
| 외부 상태 보관 지속할 상태는 외부 저장 경계에 둡니다. | 지속 상태: 외부 · 메모리 재사용: 지속성 보장 아님 | 요청 결과의 보관 위치가 실행 환경과 분리됩니다. |
Lambda 적합성 판단부터 cold·warm 실행과 상태 외부화까지
Lambda를 선택한 사례에서 cold 초기화·처리·외부 상태·다음 warm 재사용의 실행 상태를 구별할 수 있다.
Nest · Lambda lifecycle
Lambda 선택과 cold·warm 실행
원문의 Node20 handler 예제로 초기화 완료 후 캐시와 실행 환경 재사용을 구분합니다. 외부 업무 상태·연결 예산은 추가 운영 설계입니다. 현재 배포에는 지원 runtime과 handler·adapter 계약을 별도로 맞춰야 합니다.
-
실행 형태를 먼저 선택
함수가 직접 지속 연결을 소유하거나 작업 시간이 함수 예산을 넘으면 컨테이너·잡을 검토합니다. Lambda response streaming 지원은 별도로 확인합니다.
-
비동기 이벤트 진입
원문 handler는 event·context·callback을 받아 serverless-express handler로 전달합니다.
-
cold 초기화
cachedServer가 없으면 bootstrap을 실행하고, 완료된 handler만 저장합니다. 동시 최초 호출을 합치는 Promise cache는 없습니다.
-
warm 재사용
준비된 실행 환경은 handler와 연결을 재사용할 수 있지만 그 메모리는 내구 상태가 아닙니다.
-
요청 처리 예산
예약 동시성과 환경별 DB pool을 함께 제한하고 외부 호출 timeout을 함수 예산보다 짧게 둡니다.
-
상태 외부화
세션·파일·작업 진행 상태는 DB, cache, object storage, queue에 기록해 freeze나 폐기 후에도 남깁니다.
원문의 nodejs20.x는 deprecated 예시입니다. Node24의 Lambda async handler는 event·context를 받으며 callback을 지원하지 않습니다. 현재 배포는 runtime·adapter 계약과 전체 의존성 패키징을 다시 검증해야 합니다. 원문 warm-up은 bootstrap 전에 반환해 Nest 초기화를 보장하지 않습니다.
이 예의 조건
- 두 박스는 같은 실행의 진행 정보와 자료·결과를 관찰하는 고정 역할입니다. 박스의 좌우·위아래 위치를 별도 호출 순서로 해석하지 않습니다.
- 조건별 종료는 대안입니다. 실패 뒤 정상 후속 단계를 실행하거나 독립 비교 사례를 같은 요청으로 연결하지 않습니다.
- 기존 두 영역, 아홉 노드와 아홉 화살표다. 지속 연결·예산 초과는 컨테이너로, 나머지는 Lambda로 향한다. cold 초기화와 warm 재사용은 각각 요청 처리로 합쳐진다. 상태 저장은 외부 단계이며 메모리 재사용과 다르다.
- 지속 연결과 실행 예산으로 Lambda 또는 서버리스 컨테이너를 고른다. Lambda에서는 API Gateway 이벤트가 cold 초기화 또는 warm handler 재사용으로 갈라진 뒤 하나의 Nest 요청 처리로 합쳐지고, 내구 상태는 외부 저장소에 기록된다.
| 장면 | 관찰 값 | 전환 이유 |
|---|---|---|
| cold 또는 warm 시작 Lambda 선택 경로에서 cold는 초기화하고 warm은 기존 실행 환경을 재사용합니다. | cold: 초기화 · warm: 재사용 · 지속 연결·예산 초과: 컨테이너 별도 선택 | 요청 처리 전 준비 상태가 실행 환경에 따라 다릅니다. |
| 요청 처리 cold와 warm 경로가 요청 처리로 합쳐집니다. | 실행: 요청 처리 · 메모리: 실행 환경 내부 | 준비된 실행 환경에서 업무가 진행됩니다. |
| 외부 상태 보관 지속할 상태는 외부 저장 경계에 둡니다. | 지속 상태: 외부 · 메모리 재사용: 지속성 보장 아님 | 요청 결과의 보관 위치가 실행 환경과 분리됩니다. |
서버리스(Serverless)란 무엇인가?
서버리스 NestJS(AWS Lambda 통합)를 구현할 때 콜드 스타트, 핸들러 어댑터, 상태 저장 위치, 연결 재사용 기준을 확인합니다.
서버리스 컴퓨팅은 클라우드 서비스 제공업체가 서버 인프라의 프로비저닝, 스케일링, 패치, 유지보수 등 모든 운영 업무를 대신 처리하는 컴퓨팅 실행 모델입니다.
개발자는 자신의 애플리케이션 코드를 함수나 컨테이너 이미지 형태로 배포하고, 클라우드 플랫폼이 코드를 실행하고 자동으로 확장합니다.
서버리스의 주요 특징- 서버 관리 불필요: 개발자는 서버를 직접 프로비저닝하거나 관리할 필요가 없습니다. 클라우드 공급자가 모든 인프라를 처리합니다.
- 이벤트 기반(Event-Driven): 코드는 HTTP 요청, 데이터베이스 변경, 파일 업로드, 메시지 큐 이벤트 등 특정 이벤트에 의해 트리거될 때만 실행됩니다.
- 자동 스케일링: 트래픽 증가에 따라 자동으로 인스턴스가 확장되고, 트래픽 감소 시 자동으로 축소됩니다. 유휴 상태에서는 인스턴스 수가 0으로 줄어들 수 있습니다.
- 사용량 기반 과금: 코드가 실행되는 동안에만 비용을 지불합니다. 유휴 상태에서는 거의 또는 전혀 비용이 발생하지 않습니다.
- 고가용성 내장: 클라우드 공급자가 기본적으로 고가용성과 내결함성을 제공합니다.
- 개발 생산성 향상: 인프라 관리 부담이 줄어들어 개발자가 핵심 비즈니스 로직에 집중할 수 있습니다.
- 비용 효율성: 사용한 만큼만 지불하므로, 트래픽 변동이 심하거나 간헐적으로 사용되는 애플리케이션에 매우 효과적입니다.
- 뛰어난 확장성: 트래픽 급증에도 자동으로 확장되어 안정적인 서비스를 제공합니다.
- 운영 부담 감소: 인프라 패치, 보안 업데이트 등을 클라우드 공급자가 처리합니다.
- 콜드 스타트(Cold Start): 유휴 상태에서 첫 요청이 들어올 때 컨테이너나 런타임이 시작되는 초기 지연 시간(밀리초~수 초)이 발생할 수 있습니다.
- 벤더 종속성: 특정 클라우드 서비스의 API 및 생태계에 종속될 가능성이 있습니다.
- 제한 사항: 함수 실행 시간 제한, 메모리 제한, 파일 시스템 접근 제한 등이 있을 수 있습니다.
- 복잡한 상태 관리: 서버리스는 기본적으로 상태 비저장(Stateless) 아키텍처를 지향하므로, 상태 관리가 필요한 경우 외부 데이터베이스나 캐시를 사용해야 합니다.
NestJS와 서버리스의 통합
NestJS는 Express 또는 Fastify 기반으로 동작하며, 미들웨어, 가드, 인터셉터, 파이프 등 모듈화된 구조를 갖추고 있어 서버리스 환경에서도 유연하게 적응할 수 있습니다.
특히 @nestjs/platform-express 패키지의 어댑터 패턴 덕분에 다양한 런타임 환경에 맞게 애플리케이션을 조절할 수 있습니다.
NestJS 애플리케이션을 서버리스 환경에 배포하는 주요 방법은 다음과 같습니다.
AWS Lambda & API Gateway
아래 Node.js 20 설정과 3인자 handler는 기존 통합 방식을 설명하는 예제입니다. AWS runtime 표에서 Node 20은 deprecated이며, Node 24의 async handler는 event·context만 받고 callback을 지원하지 않습니다. 현재 배포에는 지원 runtime과 adapter의 Promise 계약을 맞추고, 전체 runtime 의존성이 artifact에 포함되는지 검증해야 합니다. 아래 일부 node_modules 패턴이나 “배포 후 설치” 주석을 완전한 Lambda 패키징 절차로 해석하지 않습니다.
가장 보편적인 서버리스 NestJS 배포 모델입니다.
HTTP 요청을 API Gateway가 수신하고, 이를 AWS Lambda 함수로 전달하여 NestJS 애플리케이션 코드를 실행하는 방식입니다.
Serverless Framework는 이러한 배포 과정을 자동화하는 데 매우 유용합니다.
구성 요소- AWS Lambda: NestJS 코드를 실행하는 서버리스 컴퓨팅 서비스.
- API Gateway: HTTP 요청을 받아 Lambda 함수를 트리거하는 완전 관리형 서비스.
- Serverless Framework: Lambda, API Gateway 등 AWS 리소스를 쉽게 정의하고 배포할 수 있는 CLI 툴.
npm install -g serverlessNestJS 프로젝트 설정: NestJS 프로젝트를 생성하고, main.ts 파일을 Lambda 환경에 맞게 수정합니다.
NestJS는 기본적으로 Express(또는 Fastify) 어댑터를 사용하는데, Lambda 환경에서는 이 어댑터를 @codegenie/serverless-express와 함께 사용하는 구성이 현재 기준으로 일반적입니다.
npm install @codegenie/serverless-express
npm install --save-dev @types/aws-lambdaimport { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ExpressAdapter } from '@nestjs/platform-express';
import { Callback, Context, Handler } from 'aws-lambda';
import serverlessExpress from '@codegenie/serverless-express';
import express from 'express'; // express 직접 임포트
let cachedServer: Handler;
async function bootstrap(): Promise<Handler> {
if (!cachedServer) {
const expressApp = express();
const app = await NestFactory.create(AppModule, new ExpressAdapter(expressApp));
app.enableCors(); // CORS 설정 (필요시)
await app.init();
cachedServer = serverlessExpress({ app: expressApp });
}
return cachedServer;
}
// AWS Lambda 핸들러 함수
// 이 함수가 Lambda의 진입점 (serverless.yml의 handler 설정)
export const handler = async (event: any, context: Context, callback: Callback) => {
// Warm-up 요청 처리 (콜드 스타트 방지)
if (event.source === 'serverless-plugin-warmup') {
console.log('WarmUp - Lambda is warm!');
return callback(null, 'Lambda is warm!');
}
const server = await bootstrap();
return server(event, context, callback);
};- bootstrap()은 초기화가 완료된 handler를 cachedServer에 저장합니다. 같은 실행 환경이 재사용되는 동안 이후 일반 요청은 그 handler를 사용할 수 있습니다. 새 실행 환경의 콜드 스타트나 동시 초기화까지 방지하는 Promise cache는 아닙니다.
handler함수는 AWS Lambda가 호출하는 실제 진입점입니다.@codegenie/serverless-express를 사용하여 Express 앱을 Lambda 이벤트와 통합합니다.- serverless-plugin-warmup은 주기 호출로 실행 환경 재사용을 유도하지만 콜드 스타트 제거를 보장하지 않습니다. 특히 이 원문의 warm-up 분기는 bootstrap() 전에 반환하므로 Nest를 미리 초기화하지 않습니다.
serverless.yml 파일 작성: 프로젝트 루트에 serverless.yml 파일을 생성하여 AWS 리소스와 NestJS Lambda 함수를 정의합니다.
service: nestjs-serverless-app # 서비스 이름
# 플러그인 설정
plugins:
- serverless-offline # 로컬 개발용 (선택 사항)
- serverless-plugin-warmup # 콜드 스타트 방지 (선택 사항)
- serverless-dotenv-plugin # .env 파일 로드 (선택 사항)
provider:
name: aws
runtime: nodejs20.x # Node.js 런타임 버전
stage: dev # 배포 스테이지 (dev, prod 등)
region: ap-northeast-2 # AWS 리전
memorySize: 256 # Lambda 함수 메모리 (MB)
timeout: 30 # Lambda 함수 타임아웃 (초)
logRetentionInDays: 7 # CloudWatch Logs 보존 기간
apiGateway:
minimumCompressionSize: 1024 # API Gateway 압축 설정 (gzip)
environment: # Lambda 환경 변수 (민감 정보는 AWS Secrets Manager 사용 권장)
NODE_ENV: ${self:provider.stage}
DATABASE_HOST: ${env:DATABASE_HOST} # .env 파일에서 가져오기
JWT_SECRET: ${env:JWT_SECRET}
# ... 기타 환경 변수
package:
individually: true # 각 함수별로 패키징
patterns:
- '!node_modules/**' # 필요한 모듈은 포함
- 'node_modules/@nestjs/**'
- 'node_modules/reflect-metadata/**'
- 'node_modules/class-validator/**'
# ... NestJS 앱에 필요한 모든 node_modules를 정확히 명시해야 함
# 또는, dist 폴더만 포함하고 나머지 node_modules를 배포 후 설치하는 방식도 있음
- '!src/**' # 빌드된 JS 코드만 포함
functions:
api:
handler: dist/main.handler # Lambda 핸들러 함수 경로 (빌드 후의 JS 파일)
events: # 이 함수를 트리거할 이벤트 (API Gateway)
- http: # REST API Gateway (v1)
path: /{any+} # 모든 경로를 이 Lambda 함수로 라우팅
method: ANY # 모든 HTTP 메서드 허용
cors: true # CORS 활성화 (필요시)
private: false # 공개 API
# .env 파일 로드를 위한 플러그인 설정 (serverless-dotenv-plugin)
custom:
dotenv:
path: ./.env.${self:provider.stage} # 스테이지별 .env 파일 경로 지정- 로컬에서 NestJS 앱 빌드:
npm run build -
Serverless Framework로 배포
sls deploy --stage dev -
로컬에서 테스트 (serverless-offline 플러그인 사용)
sls offline
- 콜드 스타트 최적화: 위에서 언급한 캐싱 전략 외에도
serverless-plugin-warmup사용, 충분한 메모리 할당, Node.js 런타임 버전 최신화 등을 통해 콜드 스타트 영향을 줄일 수 있습니다. - 상태 관리: Lambda 함수는 상태 비저장이므로, 세션, 사용자 데이터 등 상태가 필요한 경우 AWS RDS(데이터베이스), DynamoDB, S3, ElastiCache(Redis) 등 외부 관리형 서비스를 사용해야 합니다.
- 환경 변수 관리: 민감한 정보는
serverless.yml에 직접 명시하지 않고, AWS Secrets Manager 또는 Parameter Store와 같은 서비스를 통해 런타임에 가져오도록 구현하는 것이 안전합니다. - 로깅 및 모니터링: Lambda는 CloudWatch Logs에 자동으로 로그를 보내고, CloudWatch Metrics를 통해 실행 시간, 에러 수 등을 모니터링합니다. NestJS 내부 로거(Winston 등)를 CloudWatch Logs와 연동하도록 설정하면 좋습니다.
- 파일 시스템 접근: Lambda 함수는
/tmp디렉토리만 쓰기 가능하며, 파일 시스템 접근에 제한이 있습니다. 파일 업로드 등은 S3와 같은 객체 스토리지를 사용해야 합니다. - 배포 아키텍처: MSA(Microservice Architecture)에서 각 서비스를 별도의 Lambda 함수로 배포하는 것이 일반적입니다.
cachedServer는 모듈 변수입니다. API Gateway 요청 하나의 응답 값과, 이후 요청에도 재사용할 수 있는 실행 환경의 메모리를 구별해야 합니다. 아래 추적은 원래의 callback 예제를 읽는 연습이며 Node 24 배포용 코드로 전환한 예제가 아닙니다.
빈 실행 환경 A에 warm-up 이벤트가 먼저 오면 조건문에서 반환하므로 Nest 초기화는 0회입니다. 다음 일반 요청이 오면 bootstrap()이 처음 완료되어 handler가 저장되고 초기화는 누적 1회가 됩니다. 같은 A의 다음 일반 요청은 저장된 handler를 사용하므로 여전히 1회입니다. 별개의 빈 환경 B에 warm-up만 보내도 B의 초기화는 0회입니다. A가 준비되어 있다는 사실은 B의 준비나 요청 배치 위치를 보장하지 않습니다. AWS의 handler 지침은 실행 환경 재사용을 활용하는 초기화와 요청마다 처리할 일을 구분합니다.
독립 확인: 같은 A에서 warm-up, 일반 요청, warm-up, 일반 요청이 순서대로 성공합니다. 이어 새 B의 첫 일반 요청은 app.init()에서 실패하고, 같은 B에 일반 요청을 다시 보냅니다. 각 환경의 초기화 시도 수와 완료된 handler 유무를 추적하세요. A의 handler에 보관한 사용자 진행 상태가 B에도 있다고 판단할 수 있나요?
풀이 확인
A의 초기화 시도는 1회이고 완료된 handler가 있습니다. 두 warm-up은 초기화 경로에 들어가지 않습니다. B의 첫 시도에서는 cachedServer를 대입하는 줄까지 못 가므로 완료된 handler가 없습니다. B의 둘째 일반 요청은 다시 초기화를 시도하므로 누적 시도는 2회이며, 성공했다면 그때 handler가 저장됩니다. B의 둘째 시도 성공을 입력 조건 없이 미리 단정하지 않습니다. 모듈 메모리는 환경별 값이므로 A의 사용자 상태가 B에 공유되거나 환경 폐기 뒤 남는다고 판단할 수 없습니다. 그 상태는 별도 내구 저장소 계약으로 다룹니다.
AWS Fargate (ECS/EKS)
Lambda는 함수 기반이지만, Fargate는 Docker 컨테이너를 서버리스 형태로 실행합니다.
NestJS가 이미 Docker 이미지로 컨테이너화되어 있다면, Fargate는 Lambda보다 더 자연스러운 선택일 수 있습니다.
개념: 개발자는 Docker 이미지만 제공하고, Fargate가 컨테이너를 실행하고 스케일링하는 EC2 인스턴스를 관리합니다.
ECS(Elastic Container Service) 또는 EKS(Elastic Kubernetes Service)의 런치 타입으로 Fargate를 선택할 수 있습니다.
장점- 컨테이너 기반: 기존 Docker 컨테이너 이미지를 거의 그대로 사용할 수 있어 마이그레이션이 용이합니다.
- 유연성: Lambda보다 더 큰 메모리와 긴 실행 시간을 지원하며, 일반적인 웹 서버처럼 동작합니다.
- 서버 관리 불필요: Lambda와 마찬가지로 서버 인스턴스를 직접 관리할 필요가 없습니다.
- Lambda보다는 비용이 더 발생할 수 있습니다 (유휴 상태에서도 최소 리소스 유지).
- Lambda보다 콜드 스타트가 더 길 수 있습니다.
NestJS Docker 이미지 빌드: 이전 11장 1절에서 설명한 대로 NestJS 애플리케이션의 Docker 이미지를 빌드하고 ECR에 푸시합니다.
ECS 클러스터 생성: Fargate 런치 타입으로 ECS 클러스터를 생성합니다.
- 컨테이너 이미지 URI, 포트 매핑(3000), CPU/메모리 할당 등을 정의합니다.
- NestJS에 필요한 환경 변수를 설정합니다. 민감 정보는 Secrets Manager와 연동하거나, 직접 주입합니다.
- 헬스 체크 경로(예:
/health)를 설정합니다.
- 생성된 태스크 정의를 기반으로 ECS 서비스를 생성합니다.
- 네트워크 구성:
awsvpc모드를 사용하고, 필요한 서브넷과 보안 그룹을 선택합니다. - 로드 밸런싱: ALB(Application Load Balancer)를 서비스에 연결하여 외부 트래픽을 컨테이너로 라우팅합니다.
- 자동 스케일링: CPU 사용량, 요청 수 등 지표에 따라 서비스의 태스크(컨테이너 인스턴스) 수를 자동으로 조절하도록 설정합니다.
Google / Azure
- Google Cloud Run: GCP의 완전 관리형 서버리스 컨테이너 플랫폼입니다. Docker 이미지만 제공하면 자동으로 스케일링되고, HTTP 요청 또는 Cloud Pub/Sub 이벤트에 의해 트리거됩니다.
- Azure Container Apps: Azure의 서버리스 컨테이너 서비스로, 마이크로서비스, 이벤트 기반 처리 등에 최적화되어 있습니다. Kubernetes 기반이지만 복잡한 오케스트레이션 없이 컨테이너를 배포할 수 있습니다.
이러한 서비스들은 AWS Fargate와 유사하게 NestJS Docker 이미지를 그대로 사용하여 서버리스 환경의 이점을 누릴 수 있도록 합니다.
아래 다이어그램은 API Gateway에서 Lambda, Nest bootstrap, 콜드 스타트 대응, 상태 외부화까지 선택 흐름으로 정리한 것입니다.
서버리스 NestJS는 비용과 운영 부담을 줄여주지만, 콜드 스타트와 상태 외부화라는 대가가 있습니다.
아래 보드는 Lambda와 Fargate/Cloud Run 중 어느 쪽이 더 자연스러운지 판단하는 기준입니다.
서버리스 전략을 고르기 전에는 실행 형태, 콜드 스타트, 상태 외부화, 운영 지표를 한 번에 점검해야 합니다.
NestJS는 유연하고 확장 가능한 아키텍처 덕분에 서버리스 환경에서도 매우 효과적으로 동작할 수 있습니다.
AWS Lambda/API Gateway 조합은 함수 기반 서버리스의 대표 예시이고, AWS Fargate, Google Cloud Run, Azure Container Apps는 컨테이너 기반 서버리스의 훌륭한 대안입니다.
서비스별 장단점과 프로젝트 요구사항을 고려해 가장 적합한 서버리스 전략을 선택하는 것이 중요합니다.
서버리스 NestJS는 운영 부담을 줄이고 비용을 최적화하며, 뛰어난 확장성을 제공해 현대 웹 애플리케이션 개발에 큰 이점을 줍니다.
배포 파일과 실행 환경의 경계를 따라가기
먼저 일반 NestJS 앱을 빌드할 수 있고, API Gateway가 전달한 이벤트를 HTTP 어댑터가 요청·응답으로 바꾼다는 흐름을 이해해야 합니다. dist/main.handler는 “dist/main.js 모듈의 handler 내보내기를 호출한다”는 설정입니다. 소스에 함수가 있다는 사실만으로 배포 파일이 완성되지는 않습니다.
| 확인할 대상 | 이 예제에서 필요한 결과 | 빠졌을 때 먼저 확인할 곳 |
|---|---|---|
| 진입 파일 | 압축 해제한 배포 패키지에 dist/main.js가 있고 handler를 내보낸다 | 빌드 출력 경로와 handler 설정 |
| 실행 시 의존성 | Express·Nest 플랫폼 어댑터·serverless-express와 그 전이 의존성이 함께 제공된다 | 패키지 제외 규칙·번들·레이어 |
| 런타임 계약 | 사용 중인 Node.js 런타임과 핸들러의 Promise/콜백 방식이 맞는다 | 런타임 설정과 어댑터의 응답 방식 |
| HTTP 경로 | 하위 경로와 루트 경로를 각각 필요한 리소스에 연결한다 | API Gateway의 리소스·메서드 설정 |
Nest 패키지 이름 몇 개만 포함하는 앞의 YAML은 완전한 의존성 목록이 아닙니다. 배포 전에 실행 의존성을 패키지·번들 또는 레이어에 준비해야 하며, Lambda가 앱 시작 때 누락된 패키지를 자동 설치해 주는 방식으로 이해하면 안 됩니다. AWS Node.js 배포 패키지 문서는 런타임에 없는 의존성을 패키지나 레이어로 제공하는 방법을 설명합니다.
앞서 다룬 콜백 예제에서 Node.js 24의 비동기 핸들러로 전환할 때는 콜백 인수를 제거하는 것에 더해, warm-up 분기도 값을 반환하고 실제 요청의 어댑터도 Promise 방식으로 결과를 반환하도록 맞춰야 합니다. 환경 변수의 런타임 이름만 바꾸고 세 인수 콜백 호출을 그대로 남기는 변경은 이 계약을 확인하지 못합니다. AWS 핸들러 계약과 serverless-express의 응답 모드를 함께 확인합니다.
예제: 동시 실행 수가 연결 수에 미치는 영향
각 실행 환경이 별도 DB 풀을 가지고, 동시에 살아 있는 환경이 8개이며 풀당 최대 연결이 5개라고 가정해 봅시다. 이 앱의 풀 설정상 연결 상한 합계는 8 × 5 = 40입니다. 환경이 12개로 늘어나면 같은 설정의 합계는 12 × 5 = 60으로 바뀝니다. 한 환경 안에서 캐시한 풀이 다른 환경의 풀과 합쳐지는 것은 아닙니다.
이 앱에 허용한 DB 연결 예산이 50개이고 환경 상한을 12개로 제한한다면, 다른 연결 수요가 없다는 단순 가정에서 풀당 최대 4개는 12 × 4 = 48로 예산 안에 들어갑니다. 실제 열린 연결 수·환경 재사용·DB 프록시·다른 앱의 연결은 따로 측정해야 합니다. 계산한 48개는 요청 처리량이나 항상 열려 있는 연결 수가 아니라 설정 상한들의 합입니다.
조건을 바꿔 보는 추가 연습
- REST API에
/{any+}의 ANY 메서드만 등록했습니다.GET /users와GET /가 모두 같은 Lambda로 전달된다고 단정할 수 있을까요? 앱의 루트 컨트롤러가 있는데 루트 요청만 실패한다면 어디를 보완해야 할까요? - 실행 환경 10개, 풀당 최대 6개, 앱에 할당한 연결 예산 55개입니다. 설정 상한 합계를 계산하고, 환경 수를 유지할 때 예산 안에 드는 풀 상한을 구하세요. 이 결과로 실제 DB 연결 수를 확정할 수 있는지도 설명하세요.
- 배포 파일에
dist/main.js는 있지만 serverless-express가 없습니다. 개발 PC에서는 실행되고 Lambda에서는 모듈 로드가 실패합니다. 캐시 변수나 warm-up 호출을 바꾸면 해결될까요? 수정할 대상을 설명하세요.
풀이
{any+}는 하나 이상의 경로 부분을 받는 greedy 하위 리소스입니다./users는 그 대상이지만 루트/는 별도 루트 리소스와 메서드 연결을 확인해야 합니다. 루트에도 필요한 메서드를 명시적으로 연결한 뒤, 두 요청의 API Gateway 전달 경로를 각각 확인합니다. Nest의 루트 컨트롤러만 추가해서 Gateway에 없는 루트 연결이 생기지는 않습니다. API Gateway 리소스·메서드 설명- 상한 합계는
10 × 6 = 60으로 55개를 넘습니다. 환경당 정수 상한 5개라면10 × 5 = 50으로 예산 안입니다. 이는 다른 연결 수요가 없고 환경 수가 10개 이하라는 가정의 설정 계산입니다. 실제 연결 수는 풀의 지연 생성·유휴 연결과 실행 환경 상태에 따라 다르므로 관측이 필요합니다. - 모듈 로드 실패는 배포 의존성 문제입니다. 필요한 패키지와 전이 의존성을 배포 패키지·번들·레이어에 포함하고 handler 경로에서 불러올 수 있게 해야 합니다. 캐시와 warm-up은 앱을 성공적으로 로드한 뒤의 초기화·재사용 흐름에 관한 것이므로 누락된 모듈을 대신 제공하지 않습니다.