Nest.js 애플리케이션을 Serverless 오픈소스를 활용하여 AWS Lambda에 배포하고 API Gateway를 통해 접근하는 API를 생성하자.
기본적인 nest application을 생성해준다.
$ npm i -g @nestjs/cli
$ nest new nest-server
이후 해당 폴더에 접근하여 serverless 프레임워크를 설치하고 관련 설정을 진행하자.
$ cd nest-server
$ npm i -g serverless
$ npm i @vendia/serverless-express aws-lambda
$ npm i -D @types/aws-lambda
이후 Lambda에서 실행할 진입점이 되는 handler 함수를 작성하자.
// /src/lambda.ts
import { NestFactory } from '@nestjs/core';
import serverlessExpress from '@vendia/serverless-express';
import { Callback, Context, Handler } from 'aws-lambda';
import { AppModule } from './app.module';
let server: Handler;
async function bootstrap(): Promise<Handler> {
const app = await NestFactory.create(AppModule);
await app.init();
const expressApp = app.getHttpAdapter().getInstance();
return serverlessExpress({ app: expressApp });
}
export const handler: Handler = async (
event: any,
context: Context,
callback: Callback,
) => {
server = server ?? (await bootstrap());
return server(event, context, callback);
};
위 Lambda 함수를 살펴보면, 서버 인스턴스를 담을 변수를 생성한 뒤 서버의 존재 여부(cold, warm)를 판단하여 새로 bootstrapping을 하여 응답을 반환할지 기존의 서버를 이용해 응답을 반환할지 결정하는 로직임을 알 수 있다.
이후 serverless 관련 설정 파일인 serverless.yaml
을 생성해준다. 이 경우 나는 node_modules의 크기 이슈 때문에 lambda 코드 번들에서는 이를 제외하고, 이후 lambda 계층에 필요한 node_modules 파일들을 추가하여 제공해주었다. 따라서 serverless.yaml
파일에서 node_modules를 제외하는 설정이 필요하다.
# serverless.yaml
service: nest-serverless-example
frameworkVersion: '3'
plugins:
provider:
name: aws
region: ap-northeast-2
runtime: nodejs20.x
functions:
api:
handler: dist/lambda.handler
events:
- http:
method: any
path: /{proxy+}
이외에 functions 부분은 실제 API Gateway의 설정이 담겨 있다. 특정 path를 기준으로 서비스가 나뉘는 것이 아닌 하나의 모놀리식 서비스를 기준으로 하므로 모든 요청이 프록시되어 lambda로 전달되는 형태임을 알 수 있다.
마지막으로 Typescript 설정이 필요하다.
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2021",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental":false,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false,
"allowJs": true,
"esModuleInterop": true
}
}
기본적인 Nest.js 애플리케이션 설정에서 ‘incremental’ 이 false
가 되었고, ‘allowJs’ 및 ‘esModuleInterop’ 설정이 true
로 추가되었다.
Incremental의 경우 마지막 컴파일에 프로젝트에 대한 정보를 캐싱하여 동작에 효율성을 더해주는데, 반복적인 배포 과정에서 오류를 뱉는 경우가 존재한다고 한다.
‘esModuleInterop’ – CommonJS 모듈을 가져올 수 있도록 허용하는 설정
‘allowJs’ - 자바스크립트 파일을 가져올 수 있도록 허용하는 설정
위의 serverless 배포 동작에서 필요로 하는 권한들에 대한 설정이 필요하다. 본래 필수적인 권한들을 찾아서 해당 권한들만 허용하도록 하는 것이 맞지만, 이번 글에서는 필요한 서비스들에 대한 full access를 추가하였다. 실무에서는 귀찮더라도 필요 권한들만 찾아서 추가하도록 하자.
그리고 개인 계정 보안을 위해 연습이 끝난 뒤에는 FullAccess 및 administrator 권한들은 최대한 제거하자
위와 같이 필요 권한들이 추가된 IAM User로 콘솔에 로그인하자.
이제 애플리케이션을 빌드하고 배포하자.
$ npm run build
$ sls deploy
근데 여기서 문제가 발생한다. 앞서 언급한 것처럼 node_modules를 serverless 설정 파일에서 제외하지 않게 되면 전체 코드 용량이 커져 Lambda 배포가 불가하다고 나왔다. 그래서 Lambda 계층에 필요한 modules 만 추가하는 과정을 거쳐야한다고 했는데, Lambda 계층에 추가할 수 있는 zip 파일의 최대 크기가 50MB이고 내 node_modules 를 압축한 파일의 경우 100MB 언저리의 용량을 가지고 있었다.
이는 devDependencies 도 node_modules에 포함되어 Production에 불필요한 코드들이 대량 포함되어있기 때문이었는데, 이를 해결하기 위해 기존의 node_modules를 제거하고 productioon에 필요한 패키지만 다운받을 수 있도록 다시 install을 해주자.
$ npm install -production
위 명령어를 통해 devDependencies 를 제외한 라이브러리들만 다운받을 수 있다.
이제 Lambda 계층을 추가하자.
Node.js 런타임을 기반으로 하는 Lambda의 경우, node_modules를 계층에 추가하여 사용하기 위해서는 nodejs/node_modules/*
의 경로를 따라야 한다.
아래 문서에서 각 Lambda 런타임에 대한 계층 경로들을 확인할 수 있다.
https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/packaging-layers.html
이제 압축한 파일을 계층에 업로드하고, 호출 런타임을 Node.js로 맞춰주면 해당 계층을 기존의 함수에 추가할 수 있게 된다.
이후 lambda에서의 테스트도 잘 되고,
Postman에서도 무리없이 동작함을 확인할 수 있다.
이렇게 Serverless 프레임워크를 활용하여 Lambda - API Gateway 를 활용한 서버리스 Nest.js 애플리케이션을 배포해보았다. 이를 활용해 비용 효율적인 애플리케이션 배포가 가능하고, 작은 단위의 모듈들로 애플리케이션을 나누면서 서버리스 기반 아키텍쳐를 쉽게 적용할 수 있다.
또한 Lambda@Edge를 사용하거나, API Gateway 앞단에 Cloudfront 를 직접 연결하면서 CDN을 활용한 지연 속도 개선도 노려볼 수 있다.
https://docs.nestjs.com/faq/serverless
https://falsy.me/nestjs-serverless-rds-배포하기/
https://medium.com/@sunjang/aws-lambda-layer-사용하기-node-js-8c299a1d0a6f