티스토리 뷰

개요

  • 사이드 프로젝트 백엔드를 서버리스로 배포하는 방법에 대해 정리한다.
  • 포트폴리오 시 링크를 달아야 할 때 서버리스로 배포하면 비용절감에 도움이 될 것이라 생각함
  • 클라우드 서비스에 좀 더 익숙해지기 위해 사용

단점

nestjs + lambda를 적용하기 전에 단점부터 알아보자.

  • 일단 nestjs 전체를 묶어서 올리는 것이기 때문에 무겁다.
  • api gateway에서 routing을 한번, nestjs에서 routing을 한번 더 하기 때문에 속도가 느리다.

즉, 실제 고객한테 제공하는 서비스를 만들 예정이라면 쓰지 않는 것을 추천한다.

방법

순서

  1. serverless 환경설정
    1. serverless에 필요한 의존성 설치
    2. serverless yml 작성
    3. serverless offline으로 먼저 동작하는지 배포
    4. serverless deploy를 통해 배포
  2. script 실행
  3. api gateway, lambda, dynamodb가 모두 제대로 배포되는지 확인한다.

serverless 환경 설정하기

// serverless.yml (참고용)
service: backend-template
useDotenv: true
package:
  individually: true

plugins:
  - serverless-offline
  - serverless-plugin-typescript

provider:
  name: aws
  runtime: nodejs18.x # 현재 serverless-offline이 18버전까지만 지원하기 떄문에 18버전으로 사용
  region: ap-northeast-2
  memorySize: 1024
  timeout: 20
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:*
      Resource: "*"
# lambda 리소스 설정
functions:
  main:
    handler: src/main.handler
    events:
      - http:
          method: ANY
          path: /
          cors: true # cors 에러를 피하기 위해 설정
      - http:
          method: ANY
          path: '{proxy+}'
          cors: true # cors 에러를 피하기 위해 설정
resources:
  Resources:
# DynamoDB 설정
    usersTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: Users
        AttributeDefinitions:
          - AttributeName: unique 속성
            AttributeType: S
        KeySchema:
          - AttributeName: unique 속성
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1
    challengesTable:
        Type: AWS::DynamoDB::Table
        Properties:
          TableName
          AttributeDefinitions:
            - AttributeName:
              AttributeType: S
          KeySchema:
            - AttributeName: 
              KeyType: HASH
          ProvisionedThroughput:
            ReadCapacityUnits: 1
            WriteCapacityUnits: 1
custom:
  serverless-offline:
    httpPort: 4000
    lambdaPort: 4001
// dependencies
{
	"@aws-sdk/client-dynamodb": "^3.441.0",
	"@aws-sdk/lib-dynamodb": "^3.441.0",
	"@vendia/serverless-express": "^4.10.4",
  	"aws-lambda": "^1.0.7",
  	"aws-sdk": "^2.1484.0",
  	"aws-serverless-express": "^3.4.0",
}
//devdependencies
{
	"@types/aws-lambda": "^8.10.125",
 	"@types/aws-serverless-express": "^3.3.7",
	"serverless-offline": "^13.2.0",
  	"serverless-plugin-typescript": "^2.1.5",
}

기본적으로 위와 같이 설정 했다.

scripts 작성하기

# 서버리스 배포하기
"deploy": "serverless deploy --aws-profile <default일 경우 생략, 지정할 profile 있으면 설정할 것>
# 배포한 서버리스 삭제하기
"remove": "serverless remove --aws-profile <default일 경우 생략, 지정할 profile 있으면 설정할 것>
# 오프라인으로 실행하기
"dev:serverless": "serverless offline"

삽질

  • 이번 serverless로 배포하면서 겪었던 삽질들을 모두 정리하려 한다.

그냥 실행이 안되요.

배포 전에 serverless-offline으로 먼저 제대로 실행이 되는지 알아본다.

nestjs의 경우 dev환경에서 로그가 뜨도록 설정해 놓았는데 로그가 뜨지 않는다.

이 경우엔 severless.yml의 memory size를 너무 작게 설정한건 아닌지 한번 확인 해봐야 한다.

cors 에러

아래의 링크를 통해 cors 에러를 해결했다

https://www.serverless.com/blog/cors-api-gateway-survival-guide

 

Your CORS and API Gateway survival guide

Get the basics on Cross-Origin Resource Sharing (CORS) and how to avoid problems with your Serverless web APIs on Lambda.

www.serverless.com

내가 사용한 방법은 두가지의 방법이다

  • serverless yml에 cors 설정을 추가하는 방식 (위에 yml 파일 작성한거 확인)
  • interceptors를 이용해 response시 header를 강제적으로 달아주는 방식이다.
interceptor.ts
// common-headers.interceptor.ts
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class CommonHeadersInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    // 원하는 헤더를 여기에 추가합니다.
    const response = context.switchToHttp().getResponse();
    response.header('Access-Control-Allow-Origin', '*');
    response.header('Access-Control-Allow-Credentials', 'true');

    // 다음 핸들러 실행
    return next.handle();
  }
}

// app.module.ts
import { APP_INTERCEPTOR } from '@nestjs/core';

providers: [
    AppService,
    {
      provide: APP_INTERCEPTOR,
      useClass: Interceptor, // 작성한 interceptor.ts
    },
  ],

[개선해야 할 점]

  • 일단 cors를 해결하는데 집중하기 위해 *표시를 사용했으나 그러면 모든 도메인에서 접근이 가능하기 떄문에 frontend 도메인에서만 사용할 수 있게 변경해야 한다.
  • 물론 yml 파일도 마찬가지이다.

cloudwatch 확인하기 (bcrypt error)

lambda가 제대로 실행되는지를 확인하기 위해 cloudwatch 로그를 확인한다.

[에러]

"/var/task/node_modules/bcrypt/lib/binding/napi-v3/bcrypt_lib.node: invalid ELF header" in aws lambda

다음 링크를 확인해보자

https://stackoverflow.com/questions/68918946/im-having-a-problem-running-bcrypt-on-aws-lambda-nodejs-can-someone-help-me

 

I'm having a problem running bcrypt on AWS Lambda NodeJS, Can someone help me?

This is the error shown while running. On bcrypt's GitHub wiki, they say that it is a native module for node-js and that it requires a compiler and build dependencies in order to build. What am I d...

stackoverflow.com

bcrypt의 경우 node-gyp를 사용하기 때문에 lambda에서 사용할 때 에러가 난다고 나와있다

bcryptjs로 바꾸면 에러가 발생하지 않는다.

환경변수 관련 이슈

  • env 파일에서 불러오지 못해 undefiend가 발생하는 현상

aws console > lambda로 들어가서 환경변수를 설정하는 부분이 있으니 거기에 추가해보자.

개선 사항

  • cors 설정 다시하기
  • build 해서 올리는데 너무 오래걸린다. yarn berry나 다른 것들을 도입해서 시간을 줄일 수 없는지 한번 확인해 봐야겠다.
  • 환경변수 일일이 추가하기 귀찮다 build 단계에서 추가하는 법이 없나 찾아볼 예정이다.