블록체인(인턴)

실 서비스를 Nestjs + Serverless + Lambda로 배포해보자

in-seo 2024. 9. 24. 17:38

기존에 나와 있는 예제들은 단순히 예제코드를 nest new를 통해 만들고 바로 배포하는 방법입니다.

하지만 이러한 방법은 실 서비스에는 무용지물이였습니다.

왜냐면 각각의 의존성 에러와 라이브러리 import 오류 등을 마주하지 않기 때문..

 

일반적인 서비스에는 DB연동, API 컨트롤러 매핑, 프론트 웹 페이지, Base URL 설정 등 복잡합니다. 

따라서 개발 완료된 실제 Production레벨의 프로젝트를 Serverless 형태로 배포하고자 합니다.

 

1. aws credentials 등록.  Serverless는 AWS 상에 CloudFormation을 통해 인프라 구축을 하기 때문에 AccessKey, SecretKey가 필요합니다.  (참고 : MFA 2단계 인증은 현재 Serverless v4 버전에서 불가능, 시도하려고 여러 라이브러리를 도입했으나 불가능. + 스크립트를 짜서 억지로 해보려 했으나 불가능.)

이와 같이 루트 폴더에 자격 증명 발급을 위한 키값을 입력하여 줍니다.

만약 default 프로필이 아닌, 특정 프로필을 사용하게 된다면

serverless deploy --aws-profile=프로필이름

serverless deploy --aws-profile=프로필이름

   으로 진행하시면 됩니다.

 

2. 가장 중요한 serverless.yaml 작성

org: serverless 계정명
app: 프로젝트명
service: 프로젝트명

plugins:

provider:
  name: aws
  region: ap-northeast-2
  runtime: nodejs20.x
  role: arn:aws:iam::xxxxx:role/assumerole-lambda
  # role을 설정하는 이유는 람다가 sts:assume-role을 하는 권한을 주기 위해서 (오류가 나중에 뜨시는 분들은 역할을 만들고 추가하셔야 합니다.)

package:
  exclude:
    - node_modules/** #node_modules를 제거하는 이유는 람다 함수의 압축 코드가 50MB를 넘으면 안되기 때문이다.
   
functions:
  main:
    handler: dist/lambda.handler   # 핵심! src/lambda.handler 가 아니라, 반드시 dist로 해야 한다. 삽질 5시간의 노하우^^;
    events:
      - http:
          method: any     
          path: /{any+}    #모든 API 경로를 열어주기 추후에 원하시는 분들은 수정하시면 됩니다.
          cors:
            origin: '*'
            headers:
              - Content-Type
              - X-Amz-Date
              - Authorization
              - X-Api-Key
              - X-Amz-Security-Token
              - X-Amz-User-Agent

handler 속성에 보이는 저 dist/lambda.handler는  dist 폴더의 lambda.js 내부의 handler 함수를 지정하는 것입니다.

저는 기존 예제들을 따라 기존에 src/lambda.handler로 진행하였다가 해당 오류로 몇시간을 고생했던 것 같습니다...

 

lambda.ts

import { NestFactory } from '@nestjs/core';
import serverlessExpress from '@vendia/serverless-express';
import { Callback, Context, Handler } from 'aws-lambda';

import { ApiModule } from './api/api.module';
import { NestExpressApplication } from "@nestjs/platform-express";
import { join } from "path";

let server: Handler;

async function bootstrap(): Promise<Handler> {
  const app = await NestFactory.create<NestExpressApplication>(ApiModule);
  app.useStaticAssets(join(__dirname, '..', 'public'));

  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);
};

람다를 호출하기 위해 handler로 감싸는 모습.

 

 

이후 두 명령어 중 하나를 택해 배포를 진행합니다.

1. serverless

2. serverless deploy

 

만약 오류가 발생한다면 tsconfig.json에 해당 옵션들을 추가해줍니다. (공식문서에 기재되어 있는 내용)

 

CloudFormation에 해당 이벤트가 찍혀있으면 성공!

이후 배포된 람다함수를 보실 수 있을겁니다.

 

하지만 저기 보시는 Layers의 개수가 (0)이라고 되어 있으실 겁니다.  해당 Layer는 람다함수가 이용할 수 있는 폴더를 담는 곳입니다.

저희는 아까 node_modules를 exclude 했기 때문에 람다 코드를 압축해서 올릴 수 있었고, 그에 따라 node_modules를 저 구조에 넣어주어야 합니다. 반드시.

왜 node_modules를 넣어야 하나요?

현재는 자신이 사용하던 라이브러리에 대한 정보가 람다 코드내에 아무것도 없습니다. 예를들어 axios 라이브러리를 이용해서 http fetch 명령을 실행했거나, DB를 조작하기 위해 typeORM을 사용하는 경우 이에 대해 어떻게 실행할 지에 대한 정보가 없는거죠

 

그래서 그에 대한 정보를 담은 node_modules를 넣어야 합니다.

이는 저희가 구현하던 nestjs 프로젝트의 폴더에 존재합니다. 단, 쓸데 없는 라이브러리들은 제거를 하고 진행합시다. (크기가 크면 Layer안에 넣을 수가 없음)

npx depcheck

를 통해서 안쓰는 라이브러리들을 제거하여 압축해줍시다.

 

이후 nodejs/node_modules 의 경로를 만들 수 있도록 xxx.zip 을 생성해야 합니다.

https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/nodejs-package.html

 

.zip 파일 아카이브를 사용하여 Node.js Lambda 함수 배포 - AWS Lambda

AWS CLI를 사용하여 Amazon S3 버킷에서 .zip 파일을 업로드하는 경우 버킷은 함수와 동일한 AWS 리전에 있어야 합니다.

docs.aws.amazon.com

해당 경로를 반드시 준수하여야 합니다. AWS 측에서 정해놓은 규칙.. (이래야 람다 함수가 참조할 수 있답니다. 삽질..)

 

이후 아까 말씀드렸던 Layers에 삽입하는 과정

 

이후 함수 URL을 생성해줍시다. (비교적 쉬운 테스트를 위해 )

 

이렇게 되면 해당 URL로 접속해보시면 모든 기능이 잘 작동되는 것을 볼 수 있습니다!

(CORS는 아까 serverless.yaml에서 허용했음)

 

로직들이 잘 돌아가는 모습

 

이상 실 서비스 Nestjs + Serverless + Lambda로 배포의 최신판을 작성했습니다.