icon
12장 : 고급 주제와 최신 트렌드

서버리스 NestJS (AWS Lambda 통합)


지난 11장에서는 NestJS 애플리케이션의 배포와 안정적인 운영을 위한 Docker 컨테이너화, CI/CD 파이프라인 구축, 그리고 다양한 클라우드 배포 전략 및 모니터링/로깅 시스템 구축에 대해 알아보았습니다. 이제 12장에서는 NestJS를 활용한 고급 주제와 최신 소프트웨어 개발 트렌드를 다루며, 그 첫 번째 주제로 서버리스(Serverless) 환경에서의 NestJS에 대해 살펴보겠습니다.

서버리스는 클라우드 컴퓨팅의 패러다임 중 하나로, 개발자가 서버 인프라를 직접 관리할 필요 없이 애플리케이션 코드 작성에만 집중할 수 있게 해줍니다. NestJS는 유연한 구조 덕분에 이러한 서버리스 환경에서도 효과적으로 작동할 수 있습니다.


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

서버리스 컴퓨팅은 클라우드 서비스 제공업체가 서버 인프라의 프로비저닝, 스케일링, 패치, 유지보수 등 모든 운영 업무를 대신 처리하는 컴퓨팅 실행 모델입니다. 개발자는 자신의 애플리케이션 코드를 함수나 컨테이너 이미지 형태로 배포하고, 클라우드 플랫폼이 코드를 실행하고 자동으로 확장합니다.

서버리스의 주요 특징

  • 서버 관리 불필요: 개발자는 서버를 직접 프로비저닝하거나 관리할 필요가 없습니다. 클라우드 공급자가 모든 인프라를 처리합니다.
  • 이벤트 기반(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 환경에서는 이 어댑터를 aws-serverless-express 라이브러리와 함께 사용합니다.

npm install aws-serverless-express
npm install --save-dev @types/aws-serverless-express
// src/main.ts (Lambda 환경을 위한 수정)
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { INestApplication } from '@nestjs/common';
import { ExpressAdapter } from '@nestjs/platform-express';
import * as serverlessExpress from 'aws-serverless-express';
import { Callback, Context, Handler } from 'aws-lambda';
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.createExpressHandler(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가 호출하는 실제 진입점입니다. aws-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 제외
    - 'node_modules/aws-serverless-express/**' # 필요한 모듈은 포함
    - 'node_modules/@nestjs/**'
    - 'node_modules/express/**'
    - 'node_modules/reflect-metadata/**'
    - 'node_modules/rxjs/**'
    - 'node_modules/class-validator/**'
    - 'node_modules/class-transformer/**'
    # ... NestJS 앱에 필요한 모든 node_modules를 정확히 명시해야 함
    # 또는, dist 폴더만 포함하고 나머지 node_modules를 배포 후 설치하는 방식도 있음
    - '!src/**' # 소스 코드 제외
    - 'dist/**' # 빌드된 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 Check) 경로(예: /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 이미지를 그대로 사용하여 서버리스 환경의 이점을 누릴 수 있도록 합니다.


NestJS는 유연하고 확장 가능한 아키텍처 덕분에 서버리스 환경에서도 매우 효과적으로 동작할 수 있습니다. AWS Lambda/API Gateway 조합은 함수 기반 서버리스의 대표적인 예시이며, AWS Fargate, Google Cloud Run, Azure Container Apps는 컨테이너 기반 서버리스의 훌륭한 대안입니다. 각 서비스의 장단점과 프로젝트의 요구사항을 고려하여 가장 적합한 서버리스 전략을 선택하는 것이 중요합니다. 서버리스 NestJS는 운영 부담을 줄이고, 비용을 최적화하며, 탁월한 확장성을 제공하여 현대적인 웹 애플리케이션 개발에 큰 이점을 가져다줄 것입니다.