Logo

타입스크립트로 AWS CDK 시작하기

코드로 인프라를 구성하는 Infrastructure as code의 장점이 많이 알려지면서 AWS CDK(Cloud Development Kit)를 도입하는 회사들이 늘어나고 있습니다. 이번 포스팅에서는 타입스크립트(TypeScript)로 클라우드 인프라를 정의하고 배포할 수 있게 해주는 도구인 AWS CDK에 대해서 알아보겠습니다.

AWS CDK의 등장 배경

AWS와 같은 클라우드 서비스가 처음에 등장했을 때 많은 사람들은 AWS Management Console과 같은 웹 기반 UI에서 일일이 서버 자원을 프로비전(provision)하고 관리했었습니다. 하지만 아무래도 사람이 직접 하는 일이다보니 실수할 위험도 높고 여러 서버 리소스를 관리하려면 반복되는 작업이 많아서 큰 회사에서는 적합한 방법이 아니었습니다.

그래서 AWS CLI나 AWS SDK와 같은 도구를 이용해서 인프라 구성 단계를 스크립트로 작성하여 관리하기 시작합니다. 이 방법은 인프라가 정상 운영될 때는 효과적이었지만 장애 상황이 발생하면 역시 사람의 개입 필요로 했습니다.

그러다가 Terraform이나 AWS CloudFormation과 같이 클라우드 인프라를 YAML이나 JSON 파일에 선언적으로 명시하게 됩니다. 하지만 이 방법은 일반 애플리케이션 개발자들에게 큰 장벽처럼 느껴졌습니다. 인프라를 선언적으로 명시하는 문법과 API를 별도로 배워야하는 학습 곡선이 컸기 때문입니다.

그래서 등장하는 것이 범용 프로그래밍 언어로 인프라를 코딩해버리는 AWS CDK와 같은 Infrastructure as code 도구입니다. AWS CDK를 통해서 DevOps에게 인프라 설정을 요청하지 않고 개발자 스스로 인프라를 정희하고 배포할 수 있게 됩니다. 게다가 범용 프로그래밍 언어로 작성이 되어 있기 때문에 인프라의 구성 내용을 테스트까지 할 수 있게 됩니다.

AWS CDK 툴킷 설치

AWS CDK 툴킷(toolkit)은 자바스크립트 패키지 매니저를 통해서 터미널에서 쉽게 설치할 수 있습니다.

Node.js를 사용하는 프로젝트에서는 터미널에서 npm 명령어를 사용하여 설치합니다.

$ npm i -g aws-cdk

added 2 packages in 726ms

차세대 자바스크립트 런타임인 Bun을 사용하는 프로젝트에서는 bun 명령어로 설치합니다.

$ bun i -g aws-cdk
bun add v1.0.25 (a8ff7be6)

 installed aws-cdk@2.126.0 with binaries:
  - cdk

 2 packages installed [612.00ms]

다음과 같이 CDK 버전이 확인되면 설치가 끝난 것입니다.

$ cdk --version
2.126.0 (build fb74c41)

CDK 프로젝트 생성

간단한 실습을 위해서 새로운 디렉토리를 만들고 그 디렉토리로 진입하겠습니다.

$ mkdir our-cdk && cd our-cdk

cdk init 명령어를 사용하여 app 템플릿으로 실습 프로젝트를 생성합니다. 사용할 언어는 타입스크립트로 지정해주겠습니다.

$ cdk init app --language typescript
Applying project template app for typescript
# Welcome to your CDK TypeScript project

This is a blank project for CDK development with TypeScript.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

## Useful commands

* `npm run build`   compile typescript to js
* `npm run watch`   watch for changes and compile
* `npm run test`    perform the jest unit tests
* `npx cdk deploy`  deploy this stack to your default AWS account/region
* `npx cdk diff`    compare deployed stack with current state
* `npx cdk synth`   emits the synthesized CloudFormation template

Initializing a new git repository...
Executing npm install...
✅ All done!

참고로 -l 또는 --list 옵션과 함께 cdk init 명령어를 사용하면 어떤 템플릿을 사용할 수 있는지 나옵니다. lib는 CDK 라이브러릴 개발용 템프릿이고, sample-app은 예제 코드가 함께 들어있는 애플리케이션 템플릿입니다.

$ cdk init --list
Available templates:
* app: Template for a CDK Application
   └─ cdk init app --language=[csharp|fsharp|go|java|javascript|python|typescript]
* lib: Template for a CDK Construct Library
   └─ cdk init lib --language=typescript
* sample-app: Example CDK Application with some constructs
   └─ cdk init sample-app --language=[csharp|fsharp|go|java|javascript|python|typescript]

프로젝트 구조

생성된 프로젝트는 대략적인 구조를 파악하면 도움이 됩니다.

$ tree -L 1
.
├── README.md
├── bin
├── cdk.json
├── jest.config.js
├── lib
├── node_modules
├── package-lock.json
├── package.json
├── test
└── tsconfig.json

5 directories, 6 files

bin 디렉토리에는 CDK 앱(app)을 정의하는 파일이 들어가고, lib 디렉토리에는 CDK 스택(stack)을 정의하는 파일들이 들어갑니다. test 디렉토리에는 CDK 코드에 대한 테스트 코드가 들어갑니다. cdj.json 파일은 CDK 설정 파일입니다.

나머지 파일이나 디렉토리는 전형적인 타입스크립트 프로젝트에서 흔히 볼 수 있는 것들이라 따로 설명은 하지 않겠습니다.

CDK 스택 정의

CDK는 컨스트럭트(construct)라는 기본 단위로 구성이 됩니다. 최상위 단위인 AWS 앱(app)도 Construct, 중간 단위인 AWS 스택(stack)도 Construct, 최하위 단위인 AWS 서비스(service)도 Construct가 됩니다. 그리고 AWS 입과 AWS 서비스 사이에 AWS 스택(stack)이라는 논리적인 단위를 통해서 하나 이상의 AWS 서비스를 묶어서 AWS 앱에게 제공합니다.

각 AWS 서비스는 aws-cdk-lib/aws-xxx로 모듈화가 되어있습니다. 그래서 EC2 서비스가 필요하다면 aws-cdk-lib/aws-ec2 모듈을 불러오면 되고, S3 서비스가 필요하면 aws-cdk-lib/aws-s3 모듈을 불러오면 됩니다.

실습 프로젝트에서는 정말 단순하게 하나의 SQS 큐(queue)로만 이루어진 스택을 한 번 정의해보겠습니다.

lib/our-cdk-stack.ts
import * as cdk from "aws-cdk-lib";
import * as sqs from "aws-cdk-lib/aws-sqs";

export class OurCdkStack extends cdk.Stack {
    constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    new sqs.Queue(this, "OurCdkQueue", {
      visibilityTimeout: cdk.Duration.seconds(300),
    });
  }
}

CDK 스택을을 포함한 모든 컨스트런트의 생성자는 항상 동일한 2개의 필수 인자와 1개의 선택 인자를 받도록 약속되어 있습니다.

  • scope(필수): 해당 컨스트럭트가 생성되는 상위 범위를 의미하는데, 대부분의 경우 this를 넘긴다고 보시면 됩니다.
  • id(필수): 생성된 컨스트럭트를 상위 범위내에서 식별할 수 있는 유일한 ID를 의미합니다.
  • props(선택): 컨스트럭트를 생성할 때 설정을 위해 필요한 옵션들을 담는 객체입니다.

CDK 앱 정의

위에서 정의한 CDK 스택을 애플리케이션 진입 지점인 CDK 앱에서 불러와서 생성하겠습니다.

bin/our-cdk.ts
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { OurCdkStack } from "../lib/our-cdk-stack";

const app = new cdk.App();
new OurCdkStack(app, "OurCdkStack");

CloudFormation 템플릿으로 변환

AWS CDK를 통해 코드로 정의된 인프라를 배포할 때는 내부적으로 AWS CloudFormation이 사용됩니다. 따라서 AWS CloudFormation에 익숙하시다면 어떻게 변환이될지 확인을 해보는 것이 도움이 될 수 있습니다.

cdk synth 명령어를 사용하여 CDK 코드를 CloudFormation 템플릿으로 변환하여 출력할 수 있습니다. 여기서 synth는 synthesize의 줄임말인데 한국말로 “합성하다”라는 뜻입니다.

$ cdk synth
Resources:
  OurCdkQueue33A6ACB9:
    Type: AWS::SQS::Queue
    Properties:
      VisibilityTimeout: 300
    UpdateReplacePolicy: Delete
    DeletionPolicy: Delete
    Metadata:
      aws:cdk:path: OurCdkStack/OurCdkQueue/Resource
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Analytics: v2:deflate64:H4sIAAAAAAAA/yWMOQ6EMAwA37J9YggF0POCZR+AIDGSgXUETqBA/J2rmtEUk4HJckg/7SbaulFP1MH+C60d1ZUamQX2b8SIqur5keO2GsXHxT618uwokOdDsXcIgySrKcEU13YQIr1EDvRHqF+eQPxK5XMAAAA=
    Metadata:
      aws:cdk:path: OurCdkStack/CDKMetadata/Default
    Condition: CDKMetadataAvailable
Conditions:
  CDKMetadataAvailable:
    Fn::Or:
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - af-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ca-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-northwest-1
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-2
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-3
          - Fn::Equals:
              - Ref: AWS::Region
              - il-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - me-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - me-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - sa-east-1
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-2
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-2
Parameters:
  BootstrapVersion:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /cdk-bootstrap/hnb659fds/version
    Description: Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]
Rules:
  CheckBootstrapVersion:
    Assertions:
      - Assert:
          Fn::Not:
            - Fn::Contains:
                - - "1"
                  - "2"
                  - "3"
                  - "4"
                  - "5"
                - Ref: BootstrapVersion
        AssertDescription: CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.

부트스트랩(bootstrap)

지금부터 과정은 본인의 AWS 계정과 연동이 필요하기 때문에 CDK 명령어를 실행하는 컴퓨터에 인증 정보가 설정이 되어 있어야 합니다. 이 부분에 대해서는 별도 포스팅에서 자세히 다루고 있으니 참고 바랍니다.

실습 프로젝트에서는 우리는 한 번도 AWS에 배포를 한 적이 없기 때문에, 우선 부트스트랩(bootstrap)을 거쳐야 하며, 이 과정은 맨 처음에 배포하기 전에 딱 한 번만 필요합니다.

부트스트랩을 하려면 cdk bootstrap 명령어를 실행합니다.

$ cdk bootstrap
 ⏳  Bootstrapping environment aws://123456789012/us-east-1...
Trusted accounts for deployment: (none)
Trusted accounts for lookup: (none)
Using default execution policy of 'arn:aws:iam::aws:policy/AdministratorAccess'. Pass '--cloudformation-execution-policies' to customize.
CDKToolkit: creating CloudFormation changeset...
 ✅  Environment aws://123456789012/us-east-1 bootstrapped.

인프라 배포

이제 AWS CDK로 정의한 클라우드 인프라를 AWS 계정에 배포해보겠습니다. cdk deploy 명령어를 실행하면 됩니다.

$ cdk deploy

✨  Synthesis time: 2.85s

OurCdkStack:  start: Building a7df762112a531c48ae8c7f8acde167764f18623e58d461e9232984ddcc5164c:current_account-current_region
OurCdkStack:  success: Built a7df762112a531c48ae8c7f8acde167764f18623e58d461e9232984ddcc5164c:current_account-current_region
OurCdkStack:  start: Publishing a7df762112a531c48ae8c7f8acde167764f18623e58d461e9232984ddcc5164c:current_account-current_region
OurCdkStack:  success: Published a7df762112a531c48ae8c7f8acde167764f18623e58d461e9232984ddcc5164c:current_account-current_region
OurCdkStack: deploying... [1/1]
OurCdkStack: creating CloudFormation changeset...

 ✅  OurCdkStack

✨  Deployment time: 17.24s

Stack ARN:
arn:aws:cloudformation:us-east-1:123456789012:stack/OurCdkStack/88fd7dc0-c6f7-11ee-885f-0a75ee166963

✨  Total time: 20.09s

생성된 SQS 큐 확인

정말로 SQS 큐가 잘 만들어졌는데 AWS CLI를 통해서 확인해볼 수 있습니다.

$ aws sqs list-queues
QUEUEURLS       https://sqs.us-east-1.amazonaws.com/233081777157/OurCdkStack-OurCdkQueue33A6ACB9-YbvCITYWwPcN

CDK 스택 변경

이 번에는 우리 실습 스택에 다른 AWS 서비스를 추가해볼까요? SQS 큐와 궁합이 딱 맞는 SNS 토픽(topic)를 추가하고, SNS 큐가 SNS 토픽을 구독(subscribe)하도록 설정해주겠습니다.

lib/our-cdk-stack.ts
import * as cdk from "aws-cdk-lib";
import * as sns from "aws-cdk-lib/aws-sns";import * as subs from "aws-cdk-lib/aws-sns-subscriptions";import * as sqs from "aws-cdk-lib/aws-sqs";

export class OurCdkStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const queue = new sqs.Queue(this, "OurCdkQueue", {
      visibilityTimeout: cdk.Duration.seconds(300),
    });

    const topic = new sns.Topic(this, "OurCdkTopic");
    topic.addSubscription(new subs.SqsSubscription(queue));  }
}

변경 내역 확인

cdk diff 명령어를 사용하면 CDK 코드를 배포하기 전에 기존 인프라에서 어떤 부분이 바뀔지를 미리 확인할 수 있습니다.

$ cdk diff
Stack OurCdkStack
Hold on while we create a read-only change set to get a diff with accurate replacement information (use --no-change-set to use a less accurate but faster template-only diff)
IAM Statement Changes
┌───┬────────────────────┬────────┬─────────────────┬───────────────────────────┬────────────────────────────────────────────────────┐
│   │ Resource           │ Effect │ Action          │ Principal                 │ Condition                                          │
├───┼────────────────────┼────────┼─────────────────┼───────────────────────────┼────────────────────────────────────────────────────┤
│ + │ ${OurCdkQueue.Arn} │ Allow  │ sqs:SendMessage │ Service:sns.amazonaws.com │ "ArnEquals": {                                     │
│   │                    │        │                 │                           │   "aws:SourceArn": "${OurCdkTopic}"                │
│   │                    │        │                 │                           │ }                                                  │
└───┴────────────────────┴────────┴─────────────────┴───────────────────────────┴────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Resources
[+] AWS::SQS::QueuePolicy OurCdkQueue/Policy OurCdkQueuePolicyAB91D65F
[+] AWS::SNS::Subscription OurCdkQueue/OurCdkStackOurCdkTopic26BAF067 OurCdkQueueOurCdkStackOurCdkTopic26BAF067EC71333E
[+] AWS::SNS::Topic OurCdkTopic OurCdkTopic03E28823


✨  Number of stacks with differences: 1

IAM 설정 항목이 하나 추가되고, 리소스가 세개가 추가가 될 거라고 합니다. CDK 코드에서 제가 직접 IAM 설정은 해준 적이 없지만, SNS 토픽이 SQS 큐로 메세지를 보매려면 권한이 필요하기 때문에 자동으로 추가된 것을 볼 수 있습니다.

이 작업을 AWS Management Console이나 AWS CLI를 통해서 설정했더라면, 시행착오를 겪어 가면서 제가 일일이 IAM 설정까지 해줘어야 했을 것입니다. AWS CDK를 사용하니 이런 부분이 자동으로 해결이 되서 너무 편리합니다.

인프라 재배포

CDK로 변경한 인프라를 AWS에 다시 배포해보겠습니다.

역시 cdk deploy 명령어를 사용하는데, 처음에 배포했을 때와 달리 이번에는 보안 관련 변경 사항이 있다고 정말로 진행할지 한 번 더 물어보는 것을 볼 수 있습니다.

$ cdk deploy

✨  Synthesis time: 2.04s

OurCdkStack:  start: Building 9a0994ee54ae5ef2f1263ccfe62bfa16065a331a9370fae4e639556b4eca47e1:current_account-current_region
OurCdkStack:  success: Built 9a0994ee54ae5ef2f1263ccfe62bfa16065a331a9370fae4e639556b4eca47e1:current_account-current_region
OurCdkStack:  start: Publishing 9a0994ee54ae5ef2f1263ccfe62bfa16065a331a9370fae4e639556b4eca47e1:current_account-current_region
OurCdkStack:  success: Published 9a0994ee54ae5ef2f1263ccfe62bfa16065a331a9370fae4e639556b4eca47e1:current_account-current_region
This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

IAM Statement Changes
┌───┬────────────────────┬────────┬─────────────────┬───────────────────────────┬────────────────────────────────────────────────────┐
│   │ Resource           │ Effect │ Action          │ Principal                 │ Condition                                          │
├───┼────────────────────┼────────┼─────────────────┼───────────────────────────┼────────────────────────────────────────────────────┤
│ + │ ${OurCdkQueue.Arn} │ Allow  │ sqs:SendMessage │ Service:sns.amazonaws.com │ "ArnEquals": {                                     │
│   │                    │        │                 │                           │   "aws:SourceArn": "${OurCdkTopic}"                │
│   │                    │        │                 │                           │ }                                                  │
└───┴────────────────────┴────────┴─────────────────┴───────────────────────────┴────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Do you wish to deploy these changes (y/n)?

터미널에서 y를 입력하면 AWS로 변경된 인프라가 배포되기 시작합니다.

Do you wish to deploy these changes (y/n)? y
OurCdkStack: deploying... [1/1]
OurCdkStack: creating CloudFormation changeset...

 ✅  OurCdkStack

✨  Deployment time: 17.65s

Stack ARN:
arn:aws:cloudformation:us-east-1:233081777157:stack/OurCdkStack/88fd7dc0-c6f7-11ee-885f-0a75ee166963

✨  Total time: 19.69s

생성된 SNS 토픽 확인

역시 AWS CLI를 통해 SNS 토픽이 잘 생성된 것을 확인할 수 있습니다.

$ aws sns list-topics
TOPICS  arn:aws:sns:us-east-1:233081777157:OurCdkStack-OurCdkTopic03E28823-5uGPC8UQQaWT

마치면서

이상으로 AWS CDK를 통해서 타입스크립트로 클라우드 인프라를 정의하고 배포하는 방법에 대해서 실습해보았습니다.

AWS를 사용 경험이 없으시다면 본 포스팅을 따라오기가 어려우셨을 것 같습니다. 느끼셨겠지만 AWS CDK를 제대로 활용하려면 AWS 서비스에 대한 전반적인 지식이 어느 정도는 있어야 합니다.

후속 포스팅에서는 CDK 코드를 테스트하는 방법에 대해서 알아보도록 하겠습니다.