본문으로 건너뛰기

안동민 개발노트

본문 시작

서버리스 NestJS (AWS Lambda 통합)

NestJS를 AWS Lambda 핸들러에 연결하고 콜드 스타트·상태 저장·연결 재사용을 고려해 서버리스로 배포합니다.

지난 11장에서는 NestJS 애플리케이션의 배포와 안정 운영을 위한 Docker 컨테이너화, CI/CD 파이프라인, 클라우드 배포 전략, 모니터링/로깅 시스템을 다뤘습니다.

12장에서는 NestJS 고급 주제를 다루며, 먼저 서버리스(Serverless) 환경에서의 NestJS를 설명합니다.

서버리스는 클라우드 컴퓨팅의 패러다임 중 하나로, 개발자가 서버 인프라를 직접 관리할 필요 없이 애플리케이션 코드 작성에만 집중할 수 있게 해줍니다.

NestJS는 유연한 구조 덕분에 이러한 서버리스 환경에서도 효과적으로 작동할 수 있습니다.

Lambda에서는 NestJS를 요청마다 새 서버로 띄우지 않는다

API Gateway 이벤트를 handler가 받고, 첫 요청에서 만든 Nest 어댑터는 가능한 한 재사용한다. 오래 남아야 하는 상태는 함수 밖에 둔다.

  1. 1. API Gateway
    HTTP를 이벤트로 변환

    API Gateway 경로, 메서드, 헤더, 본문이 Lambda 입력이 된다.

  2. 2. handler
    웜업과 실제 요청 구분

    handler 프리워밍 신호는 바로 끝내고, 실제 요청만 앱으로 넘긴다.

  3. 3. cached server
    Nest 어댑터 재사용

    cached server module scope에 저장한 Express 어댑터를 다음 요청에 쓴다.

  4. 4. external state
    상태는 밖으로 분리

    external state 세션, 파일, 큐, 캐시는 RDS, Redis, S3, SQS로 보낸다.

  5. Lambda가 맞는 경우

    짧은 HTTP 이벤트와 들쭉날쭉한 트래픽이 중심일 때.

  6. 컨테이너가 맞는 경우

    긴 연결, 긴 작업, 상시 프로세스가 필요할 때.

  7. 공통 전제

    비밀값, 파일, 세션은 코드와 함수 메모리에 두지 않는다.


서버리스(Serverless)란 무엇인가?

서버리스 NestJS(AWS Lambda 통합)를 구현할 때 콜드 스타트, 핸들러 어댑터, 상태 저장 위치, 연결 재사용 기준을 확인합니다.

서버리스는 요청마다 실행 수명주기를 열고 상태를 밖에 둔다

서버가 사라지는 것이 아니라 부트·동시성·연결·상태 책임이 함수 실행 모델과 관리형 서비스로 이동한다.

  1. 1
    Event

    API Gateway·queue 이벤트가 함수 실행을 깨운다.

  2. 2
    Boot / Reuse

    cold이면 Nest를 초기화하고 warm이면 컨텍스트를 재사용한다.

  3. 3
    Handle

    요청을 처리하되 함수 시간·메모리 예산 안에 둔다.

  4. 4
    External State

    세션·파일·캐시·DB 상태를 외부 서비스에 저장한다.

  5. 5
    End / Freeze

    응답 후 인스턴스는 종료되거나 다음 호출을 위해 남는다.

  6. 6
    Cold path

    Nest bootstrap과 새 DB 연결이 첫 응답 지연에 포함된다.

  7. 7
    Warm path

    재사용 인스턴스와 연결을 활용하지만 전역 상태의 안전성을 확인한다.

서버리스 컴퓨팅은 클라우드 서비스 제공업체가 서버 인프라의 프로비저닝, 스케일링, 패치, 유지보수 등 모든 운영 업무를 대신 처리하는 컴퓨팅 실행 모델입니다.

개발자는 자신의 애플리케이션 코드를 함수나 컨테이너 이미지 형태로 배포하고, 클라우드 플랫폼이 코드를 실행하고 자동으로 확장합니다.

서버리스의 주요 특징
  • 서버 관리 불필요: 개발자는 서버를 직접 프로비저닝하거나 관리할 필요가 없습니다. 클라우드 공급자가 모든 인프라를 처리합니다.
  • 이벤트 기반(Event-Driven): 코드는 HTTP 요청, 데이터베이스 변경, 파일 업로드, 메시지 큐 이벤트 등 특정 이벤트에 의해 트리거될 때만 실행됩니다.
  • 자동 스케일링: 트래픽 증가에 따라 자동으로 인스턴스가 확장되고, 트래픽 감소 시 자동으로 축소됩니다. 유휴 상태에서는 인스턴스 수가 0으로 줄어들 수 있습니다.
  • 사용량 기반 과금: 코드가 실행되는 동안에만 비용을 지불합니다. 유휴 상태에서는 거의 또는 전혀 비용이 발생하지 않습니다.
  • 고가용성 내장: 클라우드 공급자가 기본적으로 고가용성과 내결함성을 제공합니다.
서버리스의 장점
  • 개발 생산성 향상: 인프라 관리 부담이 줄어들어 개발자가 핵심 비즈니스 로직에 집중할 수 있습니다.
  • 비용 효율성: 사용한 만큼만 지불하므로, 트래픽 변동이 심하거나 간헐적으로 사용되는 애플리케이션에 매우 효과적입니다.
  • 뛰어난 확장성: 트래픽 급증에도 자동으로 확장되어 안정적인 서비스를 제공합니다.
  • 운영 부담 감소: 인프라 패치, 보안 업데이트 등을 클라우드 공급자가 처리합니다.
서버리스의 단점
  • 콜드 스타트(Cold Start): 유휴 상태에서 첫 요청이 들어올 때 컨테이너나 런타임이 시작되는 초기 지연 시간(밀리초~수 초)이 발생할 수 있습니다.
  • 벤더 종속성: 특정 클라우드 서비스의 API 및 생태계에 종속될 가능성이 있습니다.
  • 제한 사항: 함수 실행 시간 제한, 메모리 제한, 파일 시스템 접근 제한 등이 있을 수 있습니다.
  • 복잡한 상태 관리: 서버리스는 기본적으로 상태 비저장(Stateless) 아키텍처를 지향하므로, 상태 관리가 필요한 경우 외부 데이터베이스나 캐시를 사용해야 합니다.

NestJS와 서버리스의 통합

NestJS는 Express 또는 Fastify 기반으로 동작하며, 미들웨어, 가드, 인터셉터, 파이프 등 모듈화된 구조를 갖추고 있어 서버리스 환경에서도 유연하게 적응할 수 있습니다.

특히 @nestjs/platform-express 패키지의 어댑터 패턴 덕분에 다양한 런타임 환경에 맞게 애플리케이션을 조절할 수 있습니다.

NestJS 애플리케이션을 서버리스 환경에 배포하는 주요 방법은 다음과 같습니다.

AWS Lambda & API Gateway

가장 보편적인 서버리스 NestJS 배포 모델입니다.

HTTP 요청을 API Gateway가 수신하고, 이를 AWS Lambda 함수로 전달하여 NestJS 애플리케이션 코드를 실행하는 방식입니다.

Serverless Framework는 이러한 배포 과정을 자동화하는 데 매우 유용합니다.

구성 요소
  • AWS Lambda: NestJS 코드를 실행하는 서버리스 컴퓨팅 서비스.
  • API Gateway: HTTP 요청을 받아 Lambda 함수를 트리거하는 완전 관리형 서비스.
  • Serverless Framework: Lambda, API Gateway 등 AWS 리소스를 쉽게 정의하고 배포할 수 있는 CLI 툴.
NestJS 프로젝트 준비
Serverless Framework 설치
npm install -g serverless

NestJS 프로젝트 설정: NestJS 프로젝트를 생성하고, main.ts 파일을 Lambda 환경에 맞게 수정합니다.

NestJS는 기본적으로 Express(또는 Fastify) 어댑터를 사용하는데, Lambda 환경에서는 이 어댑터를 @codegenie/serverless-express와 함께 사용하는 구성이 현재 기준으로 일반적입니다.

npm install @codegenie/serverless-express
npm install --save-dev @types/aws-lambda
src/main.ts (Lambda 환경을 위한 수정)
import { 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 * as 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() 함수는 NestJS 애플리케이션 인스턴스를 한 번만 생성하여 cachedServer에 저장합니다. 이는 콜드 스타트를 줄이는 데 도움이 됩니다. 첫 요청 시에는 초기화 시간이 걸리지만, 이후 요청들은 캐시된 인스턴스를 사용하여 빠르게 처리됩니다.
  • handler 함수는 AWS Lambda가 호출하는 실제 진입점입니다. @codegenie/serverless-express를 사용하여 Express 앱을 Lambda 이벤트와 통합합니다.
  • serverless-plugin-warmup은 콜드 스타트 방지를 위해 주기적으로 Lambda 함수를 호출하여 활성화 상태를 유지하는 플러그인입니다.

serverless.yml 파일 작성: 프로젝트 루트에 serverless.yml 파일을 생성하여 AWS 리소스와 NestJS Lambda 함수를 정의합니다.

serverless.yml
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: # HTTP API Gateway (더 빠르고 저렴)
          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
서버리스 NestJS의 고려사항
  • 콜드 스타트 최적화: 위에서 언급한 캐싱 전략 외에도 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 함수로 배포하는 것이 일반적입니다.

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 배포 과정 (Fargate on ECS 예시)

NestJS Docker 이미지 빌드: 이전 11장 1절에서 설명한 대로 NestJS 애플리케이션의 Docker 이미지를 빌드하고 ECR에 푸시합니다.

ECS 클러스터 생성: Fargate 런치 타입으로 ECS 클러스터를 생성합니다.

태스크 정의(Task Definition) 생성
  • 컨테이너 이미지 URI, 포트 매핑(3000), CPU/메모리 할당 등을 정의합니다.
  • NestJS에 필요한 환경 변수를 설정합니다. 민감 정보는 Secrets Manager와 연동하거나, 직접 주입합니다.
  • 헬스 체크 경로(예: /health)를 설정합니다.
ECS 서비스 생성
  • 생성된 태스크 정의를 기반으로 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, 콜드 스타트 대응, 상태 외부화까지 선택 흐름으로 정리한 것입니다.

Lambda에서 NestJS는 서버가 아니라 handler로 동작한다

API Gateway 이벤트가 들어오면 handler가 Nest 앱을 만들거나 재사용하고, 오래 살아야 하는 상태는 외부 서비스에 둔다.

  1. HTTP가 Lambda 이벤트가 된다

    Event input path, method, headers, body가 controller 앞의 입력이 된다. 요청 진입점

  2. Nest 앱은 가능한 한 재사용한다

    Cached bootstrap 첫 요청은 초기화하고, 웜 요청은 캐시된 server를 쓴다. 콜드 스타트 관리

  3. 상태는 함수 밖에 둔다

    External state 세션, 파일, 큐, 캐시는 RDS, Redis, S3, SQS로 분리한다. 함수 수명과 분리

판정맞는 경우위험 신호보완책
Use Lambda 짧은 HTTP 이벤트요청 단위 작업, 낮은 유휴 비용초기화 지연이 SLA를 넘음번들 축소, 메모리 조정, 프리워밍
Move state out 상태 분리세션, 업로드, 큐가 필요함/tmp나 메모리에 의존관리형 DB, Redis, S3, SQS
Use container 상시 연결WebSocket, 긴 작업, 워커 프로세스실행 시간과 연결 수명 초과Fargate, Cloud Run, ECS

서버리스 NestJS는 비용과 운영 부담을 줄여주지만, 콜드 스타트와 상태 외부화라는 대가가 있습니다.

아래 보드는 Lambda와 Fargate/Cloud Run 중 어느 쪽이 더 자연스러운지 판단하는 기준입니다.

서버리스는 실행 모델의 제약까지 함께 고른다

Lambda와 서버리스 컨테이너는 모두 서버 관리를 줄이지만, 요청 수명과 상태 처리 방식이 다르다.

  1. 짧은 이벤트 처리

    Lambda API Gateway 이벤트를 Nest handler로 넘기고 bootstrap 결과를 캐시한다. 요청 기반 과금

  2. 컨테이너 웹 서버

    Fargate · Cloud Run 기존 Docker 이미지와 웹 서버 흐름을 유지하면서 인스턴스 관리를 맡긴다. 마이그레이션 부담 낮음

  3. 상태는 밖으로 분리

    External state 세션, 파일, 캐시는 RDS, DynamoDB, S3, Redis 같은 관리형 서비스에 둔다. stateless 전제

서버리스 전략을 고르기 전에는 실행 형태, 콜드 스타트, 상태 외부화, 운영 지표를 한 번에 점검해야 합니다.

서버리스 전략은 네 가지 제약을 통과해야 한다

요청 수명, 시작 지연, 상태 저장, 운영 추적이 서비스 요구와 맞을 때 Lambda 또는 서버리스 컨테이너를 고른다.

  1. 유휴 비용 절감이 클 때

    Lambda 상태를 밖으로 뺄 수 있고 요청이 짧으면 Lambda가 잘 맞는다.

  2. 웹 서버 흐름이 필요할 때

    Fargate / Cloud Run 긴 연결과 무거운 런타임이면 컨테이너형 서버리스부터 검토한다.

검사 축서버리스에 유리컨테이너형이 유리
요청 형태짧은 HTTP 이벤트, 간헐적 트래픽긴 연결, 스트리밍, 백그라운드 상주 작업
시작 지연bootstrap 캐시와 warm-up으로 목표 지연 충족초기화가 무겁고 콜드 스타트 허용치가 낮음
상태 저장세션, 파일, 캐시를 외부 관리형 서비스로 분리로컬 파일, 메모리 상태, 장시간 연결 의존
운영 추적함수별 비용, 에러율, 로그를 배포 단위로 추적인스턴스 단위 튜닝과 장기 실행 관측이 필요

NestJS는 유연하고 확장 가능한 아키텍처 덕분에 서버리스 환경에서도 매우 효과적으로 동작할 수 있습니다.

AWS Lambda/API Gateway 조합은 함수 기반 서버리스의 대표 예시이고, AWS Fargate, Google Cloud Run, Azure Container Apps는 컨테이너 기반 서버리스의 훌륭한 대안입니다.

서비스별 장단점과 프로젝트 요구사항을 고려해 가장 적합한 서버리스 전략을 선택하는 것이 중요합니다.

서버리스 NestJS는 운영 부담을 줄이고 비용을 최적화하며, 뛰어난 확장성을 제공해 현대 웹 애플리케이션 개발에 큰 이점을 줍니다.