AWS Cloud Development Kit(이하 AWS CDK)는 2019년 하반기 즈음에 출시(발표는 AWS Re Invent 2018)한 인프라 관리 도구입니다.

이미 기존에 IaC(Infrastructure as Code, 인프라를 코드로 관리)를 위한 도구들이 여럿있었습니다. 대표적으로 몇가지 이야기하자면, 저희회사에서도 사용하고 있는 테라폼(Terraform), AWS에서 먼저 사용중이었던 CloudFormation 등이 있습니다.

(과거에 작성한 Infrastruct as Code 발표)

AWS CDK는 이 두가지와는 확연히 다른점이 있습니다. 바로 언어입니다. Terraform의 경우 별도의 HCL(Hashcorp Configuration Language)이라는 언어를 익혀야 합니다. 크게 어렵지는 않지만, Configuration Language라는 이름에서 알 수 있듯이 데이터를 표현하는 언어입니다. 또한 CloudFormation 도 YAML(또는 JSON)으로 작성하도록 되어있습니다. 이와는 대조적으로 AWS CDK는 Typescript와 Python을 지원합니다.

단순한 인프라를 설계할 때는 굳이 프로그래밍 언어보단 구조 언어가 더 편리합니다. 하지만, 인프라가 조금만 복잡해지기 시작하면 개발자에게 익숙한 프로그래밍 언어를 사용하는게 훨씬 쉽습니다. 일단, 사용하면서 느껴보도록 하겠습니다. :-)

공식 가이드도 꽤 정리가 잘되어있으니, 한번쯤 실행해보는 것을 권해드립니다.

본 글에서는 공식가이드와는 다르게 설치할 예정이며 그 내용을 요약하면 다음과 같습니다.

  1. AWS CDK를 dev dependencies로 설치합니다.
  2. Typescript를 서버로 사용합니다.
  3. Typescript를 tsc가 아니라 babel로 빌드합니다.

이유는 중간중간마다 설명하겠습니다. :-)

설치하기

공식가이드에서는 aws-cdk 패키지를 npm 전역으로 설치하라고 하지만, 여러사람이서 하나의 프로젝트를 관리하고자 할 때, aws-cdk 버전이 다르면 문제가 발생할 수 있기 때문에 로컬설치로 진행합니다. 로컬설치로 진행하려면 간단한 프로젝트 폴더를 하나 생성해야합니다.

mkdir sample-aws-cdk
cd sample-aws-cdk
npm init # NPM 초기화 명령어, 이후 그냥 엔터를 막 눌러줍니다.
npm install aws-cdk -D

로컬로 설치된 패키지는 npx 명령어를 통해 실행할 수 있습니다.

npx cdk --version
# 1.21.0 (build bf6ab14)

타입스크립트 환경 설정을 위해 다음 패키지를 설치합니다. tsc가 아니라 babel을 통해 빌드하기 위해 다음 패키지를 설치합니다. (참고. 바벨과 타입스크립트의 아름다운 결혼)

npm install core-js
npm install @babel/cli @babel/core @babel/plugin-proposal-class-properties @babel/plugin-proposal-object-rest-spread @babel/preset-env @babel/preset-typescript typescript -D

.babelrc 파일은 다음과 같이 설정합니다.

{
  "presets": [
    ["@babel/preset-env", {
      "targets": "maintained node versions",
      "useBuiltIns": "usage",
      "corejs": 3
    }],
    "@babel/typescript"
  ],
  "plugins": [
    "@babel/proposal-class-properties",
    "@babel/proposal-object-rest-spread"
  ]
}

tsconfig.json 파일은 다음과 같이 설정합니다.

{
  "compilerOptions": {
    "target": "esnext",
    "module": "commonjs",
    "moduleResolution": "node",
    "lib": [
      "es2017",
      "esnext.asynciterable"
    ],
    "emitDeclarationOnly": true,
    "declaration": true,
    "pretty": true,
    "sourceMap": true,
    "strict": true,
    "esModuleInterop": true,
    "noImplicitReturns": true,
    "experimentalDecorators": true
  }
}

이제 간단한 서버쪽 앱을 작성해봅시다. HTTP 요청이 오면 간단한 메시지를 출력시켜주는 소스입니다. 추가로 다음 패키지를 설치합니다.

npm install @types/aws-lambda @types/node -D

src/entry.ts 파일을 생성합니다.

import { APIGatewayProxyHandler } from 'aws-lambda'

export const handler: APIGatewayProxyHandler = async (event) => {
  return {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json; charset=utf-8',
    },
    body: JSON.stringify({
      message: 'hello AWS CDK!',
    }),
  }
}

빌드를 위해 package.json 파일에 다음내용을 추가합니다.

{
  "scripts": {
    "build:server": "babel ./src --out-dir ./dist --extensions \".ts\""
  }
}

다음 명령어를 실행하면 dist 디렉토리에 빌드된 내용이 생성됩니다.

npm run build:server

이제 소스는 준비되었으니 이를 배포하기 위한 인프라를 작성해야합니다. 간단한 Rest API를 구현하기 위해서 필요한 AWS 자원은 다음과 같습니다.

  • AWS Lambda
  • AWS API Gateway

다음 패키지를 추가로 설치합니다.

npm install @aws-cdk/core @aws-cdk/aws-lambda @aws-cdk/aws-apigateway -D

AWS CDK 설정파일을 생성 /infra 폴더 하위에 두개의 파일을 생성할 예정입니다.

infra/entry.ts

#!/usr/bin/env node

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

import { RestApiStack } from './restapi-stack'

const app = new App()

new RestApiStack(app, 'WaniSampleRestApiStack')

infra/restapi-stack.ts

import { LambdaIntegration, RestApi } 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 RestApiStack extends Stack {
  public constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props)

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

    const api = new RestApi(this, 'SampleRestApi', {
      deployOptions: {
        stageName: 'dev',
      },
    })

    api.root.addMethod('GET', new LambdaIntegration(new LFunction(this, 'HomeHandler', {
      runtime: Runtime.NODEJS_12_X,
      code,
      handler: 'entry.handler',
    })))
  }
}

infra를 동작시키기 위해서 다음 빌드 스크립트를 추가해야합니다. 다음과 같이 package.json 파일을 수정합니다.

{
  "scripts": {
    "build:server": "babel ./src --out-dir ./dist --extensions \".ts\"",
    "build:infra": "babel ./infra --out-dir ./infra-dist --extensions \".ts\""
  }
}

다음 명령어를 실행하면 ./infra-dist 디렉토리 하위에 빌드된 파일들이 생성됩니다.

npm run build:infra

이제 이를 AWS CDK로 실행하려면 cdk.json 파일을 추가해야합니다.

{
  "app": "node infra-dist/entry"
}

그리고 이를 AWS CDK로 실행하기 위해서는 인증정보가 필요합니다. AWS CLI로 하는 방법도 있지만, 저는 관리하는 AWS 계정이 많아서 direnv를 사용하는 것을 선호합니다. (사용법 참고: OutSider’s Dev Story - 폴더별 환경 관리를 위한 direnv)

.envrc 파일은 다음과 같이 설정합니다. 주의, 이 파일은 AWS의 접근 권한을 가지고 있기 때문에 반드시 .gitignore에 등록해야합니다.

export AWS_DEFAULT_REGION='ap-northeast-2'
export AWS_ACCESS_KEY_ID='IAM에서생성한KEY'
export AWS_SECRET_ACCESS_KEY='IAM에서생성한SECRET'

AWS CDK를 사용하기 위해 다음 명령어를 입력합니다. 이 명령어는 프로젝트 생성후에 한번만 실행하면 되는 명령어입니다.

npx cdk bootstrap

배포하기

그리고 이제 배포를 위해 다음 명령어를 입력합니다.

npx cdk deploy

반영되는 CloudFormation 정보를 보여주고 반영할거냐고 묻는데, 확인 후 이상없으면 y를 입력합니다.

완료되면 다음과 같이 출력됩니다.

 ✅  WaniSampleRestApiStack

Outputs:
WaniSampleRestApiStack.SampleRestApiEndpoint7C3BED3C = https://o7sbiookie.execute-api.ap-northeast-2.amazonaws.com/dev/

Stack ARN:
arn:aws:cloudformation:ap-northeast-2:056997058944:stack/WaniSampleRestApiStack/93c7bc90-3dbf-11ea-8796-06574afea3ae

왠지 저 URL에 들어가면 될 것 같은 느낌이 듭니다. 브라우저로 들어가도 되고, curl 명령어를 통해서 보아도 됩니다.

curl https://o7sbiookie.execute-api.ap-northeast-2.amazonaws.com/dev/
# {"message":"hello AWS CDK!"}

위에 작성한 hello AWS CDK!라는 문구가 잘 출력됩니다. 여기까지가 가장 최소한의 AWS CDK 사용법입니다. :-)

전체 소스코드를 Github에 올려두었습니다. 다음에는 node_modules를 포함한 패키지를 배포하는 방법에 대해서 다루어보겠습니다.