1편에서는 간단한 REST Api 앱을 업로드 해보았습니다.

Node.js로 프로그래밍을 하다보면 npm을 사용하는 일이 흔합니다. 이번에는 node_modules를 포함한 패키지를 AWS CDK를 이용하여 어떻게 배포할 수 있는지 설명드리겠습니다. 예제로서 Node.js의 가장 기본 웹 프레임워크인 Express 앱을 만들고 배포해보도록 하겠습니다.

1편에서 작업한 소스를 기반으로 설명합니다. 혹시나 1편을 보지 않으셨다면 먼저 읽어보고 오시는걸 권해드립니다.

가장 먼저 Express 프레임워크를 설치합니다.

npm install express
npm install @types/aws-serverless-express -D

Express 프레임워크는 프로그램을 실행하면 서버를 띄우고 Http 이벤트가 발생하면 해당하는 핸들러(함수)를 실행합니다. 근데 AWS Lambda는 서버를 띄우지 않고 Http 이벤트를 특정한 핸들러(함수) 전달하는 구조로 되어있습니다. 기본 구조가 다르기 때문에 AWS Lambda에서 Express 프레임워크는 사용하기 어렵습니다. 하지만, Express를 Lambda에서 사용할 수 있도록 추상화한 라이브러리(aws-serverless-express)를 이미 AWS에서 만들어서 제공하고 있습니다. 이 패키지도 설치합니다.

npm install aws-serverless-express
npm install @types/aws-serverless-express -D

aws-serverless-express 문서를 참고하여 src/entry.ts 파일을 다음과 같이 작성합니다.

import { APIGatewayProxyHandler } from 'aws-lambda'
import { createServer, proxy } from 'aws-serverless-express'
import express from 'express'

const app = express()

app.get('/', (req, res) => {
  res.json({
    message: 'hello Express!',
  })
})

app.get('/about', (req, res) => {
  res.json({
    message: 'this is about page',
  })
})

const server = createServer(app)

export const handler: APIGatewayProxyHandler = (event, context) => { proxy(server, event, context) }

infra/express-stack.ts 파일을 작성합니다. 지난 1편과는 다르게 LambdaRestApi를 사용합니다.

import { LambdaRestApi } from '@aws-cdk/aws-apigateway'
import { Code, Function as LFunction, Runtime } from '@aws-cdk/aws-lambda'
import { Construct, Stack, StackProps } from '@aws-cdk/core'
import { join } from 'path'

export class ExpressStack extends Stack {
  public constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props)

    const code = Code.fromAsset(join(__dirname, '../dist'))

    new LambdaRestApi(this, 'SampleExpress', {
      handler: new LFunction(this, 'HomeHandler', {
        runtime: Runtime.NODEJS_12_X,
        code,
        handler: 'entry.handler',
      }),
    })
  }
}

RestApi는 AWS Api Gateway의 특정 경로(이를테면, GET /, POST /articles와 같은)에 Labmda 함수를 매핑할 때 사용합니다. 이와는 다르게 LambdaRestApi/{proxy+}를 경로로 설정합니다. 그래서 모든 URL 요청 이벤트가 단 하나의 Lambda 함수를 호출합니다.

API Gateway Proxy

infra/entry.ts 파일도 조금 손봅니다.

#!/usr/bin/env node

import { App } from '@aws-cdk/core'

import { ExpressStack } from './express-stack'

const app = new App()

new ExpressStack(app, 'WaniSampleExpressStack')

여기까지 작업 후 지난번처럼 배포해봅시다.

npm run deploy

예상했겠지만, 배포후 서비스에 접속해보면 에러가 발생하면서 동작하지 않습니다.

API Gateway Proxy

AWS Cloudwatch의 에러로그는 다음과 같이 출력됩니다.

API Gateway Proxy

에러로그를 요약하면, npm으로 설치한 패키지인 aws-serverless-express를 찾지 못했다는 내용입니다. aws-serverless-exprss 패키지를 Lambda 배포시에 포함시켜주면 이 문제가 해결됩니다. 간단하게 node_modules 디렉토리를 dist디렉토리에 복사하면 됩니다.

package.jsondeploy 스크립트를 다음과 같이 개선하면 됩니다.

{
  "scripts": {
    "build": "npm run build:infra && npm run build:server",
    "build:server": "babel ./src --out-dir ./dist --extensions \".ts\"",
    "build:infra": "babel ./infra --out-dir ./infra-dist --extensions \".ts\"",
    "deploy": "npm run build && cp -r node_modules dist && cdk deploy"
  }
}

AWS Lambda에는 압축기준 50MB, 압축해제 기준 250MB라는 제한이 있습니다. 초반에 간단한 형태의 API는 문제가 없습니다. 서비스를 개발하면서 패키지를 추가로 몇개 더 설치하면 금방 한계용량에 다다르게됩니다.

Heaviest Objects in the Universe
패키지 몇개만 설치하면.. (이미지 출처)

node_modules를 복사하게 되면 dependenciesdevDependencies 둘다 복사가 되는데, 실제 서비스를 동작하는데는 dependencies만 필요로 합니다. 즉, 깔끔하게 dependencies 패키지만 복사하면 됩니다. (심플!)

예전에 서버리스 프레임워크(Serverless Framework) 사용했을 때에 있었던 기능이라 패키지 형태로 있을 줄 알았는데, 대충 찾아보니 없었습니다. 그래서 그냥 하나 만들었습니다.

distize를 설치합니다.

npm install distize -D

그리고 package.jsonscripts 부분을 다음과 같이 정리합니다. 서버 소스 결과를 src-dist로 생성하고, 배포시에 src-dist 디렉토리를 dist로 복사합니다. distize에서 별다른 옵션을 지정하지 않으면 node_modulesdependenciesdist 디렉토리로 복사합니다.

{
  "scripts": {
    "build": "npm run build:infra && npm run build:server",
    "build:server": "babel ./src --out-dir ./src-dist --extensions \".ts\"",
    "build:infra": "babel ./infra --out-dir ./infra-dist --extensions \".ts\"",
    "deploy": "npm run build && distize ./src-dist/* && cdk deploy"
  }
}

다시 배포합시다.

npm run deploy

그리고 우리가 만든 두개의 페이지에 접속해봅시다.

결과1
https://생성된이름.ap-northeast-2.amazonaws.com/prod
결과2
https://생성된이름.ap-northeast-2.amazonaws.com/prod/about

이제 우리는 Express를 AWS CDK를 통해 Lambda에 배포하는 방법을 익혔습니다. 만약에 Static 파일을 업로드 해야하는 경우에, distize를 응용하시면 쉽게 업로드 가능합니다.

전체 소스코드를 Github에 올려두었습니다. 다음에는 Vue의 SSR 프레임워크인 Nuxt 앱을 AWS CDK를 활용하여 배포하는 방법에 대해 다루어 보겠습니다.