kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

〜スマートスピーカーやVoiceflowの記事は右メニューのカテゴリからどうぞ。〜

AWS CDKことはじめ

新年らしくことはじめで。

AWSへのリソース作成はTerraformを使うことが多いのですが、ちょっと他のチョイスも見ておきたい、ということでAWS CDKをかじってみます。

単に以下を写経する感じなので、あくまでも個人のメモです。

目次

前提

$ node -v
v18.12.0

$ aws --version
aws-cli/2.9.13 Python/3.11.1 Darwin/21.6.0 source/x86_64 prompt/off

AWS CDKのインストール

$ npm install -g aws-cdk

$ cdk --version
2.59.0 (build b24095d)

CDKブートストラップの設定

AWS CDKを使ったデプロイに必要なリソースを作成する(あくまでもCDKを使えるようにするためのリソースであって、自分がCDKで作成するリソースとは別)。アカウント+リージョンで1回だけ実行する必要がある。アカウントIDとリージョンを指定して以下のように実行する。

$ cdk bootstrap aws://123456789012/ap-northeast-1

CDKプロジェクトの作成

今回はTypeScriptでやってみる

$ mkdir hello-cdk & cd hello-cdk

$ 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
* `cdk deploy`      deploy this stack to your default AWS account/region
* `cdk diff`        compare deployed stack with current state
* `cdk synth`       emits the synthesized CloudFormation template

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

アカウント・リージョンの設定

us-east1以外の場合、CDKプロジェクトからbootstrapで作成したCloudFormationスタックを使うように指定する必要がある様子(指定しなければus-east1がデフォルトになった)。binの下にあるTSファイルに記載する。今回の場合だとbin/hello-cdk.ts。

import { HelloCdkStack } from '../lib/hello-cdk-stack';

const app = new cdk.App();
new HelloCdkStack(app, 'HelloCdkStack', {
  env: { account: '123456789012', region: 'ap-northeast-1' },
});

リソースを作成する ①S3の場合

作成したいリソースは、libディレクトリ以下に記載する。今回の場合だとlib/hello-cdk-stack.tsになる。デフォルトだとこんな感じ。

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
// import * as sqs from 'aws-cdk-lib/aws-sqs';

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

    // The code that defines your stack goes here

    // example resource
    // const queue = new sqs.Queue(this, 'HelloCdkQueue', {
    //   visibilityTimeout: cdk.Duration.seconds(300)
    // });
  }
}

モジュールから作成したいリソースのクラスをインポートする。

import { aws_s3 as s3 } from 'aws-cdk-lib';

S3バケットを作成する。The code that defines your stack goes hereの箇所に書いていく

    new s3.Bucket(this, 'MyFirstBucket', {
      versioned: true
    });

設定するパラメータなどはAPIリファレンスを見るとよい。

こんな感じになる。コメントアウト部分は全部はずしてある。

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { aws_s3 as s3 } from 'aws-cdk-lib';

export class HelloCdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    new s3.Bucket(this, 'MyFirstBucket', {
      versioned: true
    });
  }
}

生成されるCloudFormationテンプレート

AWS CDKでは、書いたコードが直接実行されてリソースが作成されるのではなく、ここからCloudFormationテンプレートが生成されてそれを元にリソースが作成される。 どのようなCloudFormationテンプレートが生成されるかはsynthで確認できる。

$ cdk synth
Resources:
  MyFirstBucketB8884501:
    Type: AWS::S3::Bucket
    Properties:
      VersioningConfiguration:
        Status: Enabled
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
    Metadata:
      aws:cdk:path: HelloCdkStack/MyFirstBucket/Resource
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Analytics: v2:deflate64:H4sIAAAAAAAA/zPSM7XUM1BMLC/WTU7J1s3JTNKrDi5JTM7WAQrFFxvrVTuVJmenlug4p+VBWLUgZlBqcX5pUXJqrU5efkqqXlaxfpmhhZ6hEdCorOLMTN2i0rySzNxUvSAIDQC9CLZrZgAAAA==
    Metadata:
      aws:cdk:path: HelloCdkStack/CDKMetadata/Default
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.

最後のAssertDescriptionがこうエラーっぽく見えるけど気にしなくて良い。

デプロイ

ではデプロイする。

$ cdk deploy

✨  Synthesis time: 7.4s

HelloCdkStack: building assets...

(snip)

HelloCdkStack: creating CloudFormation changeset...

 ✅  HelloCdkStack

✨  Deployment time: 39.02s

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:123456789012:stack/HelloCdkStack/01b19fe0-9043-11ed-8442-0a5df6f0b7c7

✨  Total time: 46.41s

作成されているかどうか確認

$ cdk ls
HelloCdkStack

HelloCdkStackというCloudFormationのスタックが作成されている。ここでリソースは管理される。

実際のリソースを確認してみる。

$ aws s3 ls | grep -i MyFirstBucket
2023-01-10 02:28:31 hellocdkstack-myfirstbucketb8884501-8hiaovkccah6

$ aws s3api get-bucket-versioning --bucket hellocdkstack-myfirstbucketb8884501-8hiaovkccah6
{
    "Status": "Enabled"
}

ちゃんと作成されている。

リソースの変更

次にリソースの変更を試してみる。手元のコードとスタックとの差分をチェックする。

$ cdk diff
Stack HelloCdkStack
There were no differences

当然変更はない。ではS3バケットのバージョニングを無効にする。

    new s3.Bucket(this, 'MyFirstBucket', {
      versioned: false
    });

再度diffしてみる。

$ cdk diff
Stack HelloCdkStack
Resources
[~] AWS::S3::Bucket MyFirstBucket MyFirstBucketB8884501 
 └─ [-] VersioningConfiguration
     └─ {"Status":"Enabled"}

デプロイして変更する。

$ cdk deploy
(snip)
HelloCdkStack: creating CloudFormation changeset...
[··························································] (0/3)

2:55:32 | UPDATE_IN_PROGRESS   | AWS::CloudFormation::Stack | HelloCdkStack
2:55:37 | UPDATE_IN_PROGRESS   | AWS::S3::Bucket    | MyFirstBucket
(snip)
 ✅  HelloCdkStack
(snip)
✨  Total time: 45.97s

では確認してみる。

$ aws s3api get-bucket-versioning --bucket hellocdkstack-myfirstbucketb8884501-8hiaovkccah6
{
    "Status": "Suspended"
}

バージョニングが停止された。

リソースの削除

作成したリソースを削除する。

$ cdk destroy
Are you sure you want to delete: HelloCdkStack (y/n)? y
HelloCdkStack: destroying... [1/1]

 ✅  HelloCdkStack: destroyed

これでスタックが削除されて、そこで管理されるリソースも削除される。ただしS3バケットの場合はデフォルトで削除されないので手動で削除する。(CDKの定義で自動で削除させることもできるが手動のほうがいいと思う。)

おまけ:VPCの場合

さっきのS3バケットに加えて、2AZでパブリック/プライベートのVPCを作ってみる場合はこんな感じ。

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { aws_s3 as s3 } from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';

export class HelloCdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    const s3bucket = new s3.Bucket(this, 'MyFirstBucket', {
      versioned: true
    });
    const vpc = new ec2.Vpc(this, 'MyFirstVpc', {
      ipAddresses: ec2.IpAddresses.cidr('10.1.0.0/16'),
      maxAzs: 2,
      subnetConfiguration: [
        {
          subnetType: ec2.SubnetType.PUBLIC,
          name: 'Public',
          cidrMask: 24,
        },
        {
          cidrMask: 24,
          name: 'Private',
          subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
        }
      ]
    });
  }
  get availabilityZones(): string[] {
    return ['ap-northeast-1c', 'ap-northeast-1d'];
  }
}

ちょっとAZの指定とかやや独特感。別の書き方もできるみたいだけど。この辺はAPIリファレンス参照。

まとめ

Terraformのような宣言型のほうが学習コストも低いしかんたんではあるのだけど、ちょっとしたロジックを書こうと思うとTerraform独自の関数で記載しないといけない。TypeScriptなど言語を使って書ければそのあたりは楽になるかもしれない。

ただ、ちょっと学習コストは高めな印象。

  • ググって見た感じ、AWS CDK v1の記事が多くて、v2の場合と違うところもあってちょっと混乱した。公式ドキュメント見て迷うより、workshopやるのが一番速い。
  • パラメータをいちいち記載しなくても作れたりするのはいいんだけど、ちょいちょいエラーが出た。例えばNAT Gatewayが作れないAZもあったりとか。ちゃんと明示的に記載するほうが良い。
  • CloudFormationほとんどさわったことない人なので、エラーが出たときとかにちょいちょいタイムアウトするのはそういうものなのかなぁと。なんかこう

APIリファレンスをしっかり読むのは大事。TerraformのAWS Providerもそれは同じなんだけど、TerraformはAWSリソースの設定をやってるという感じなのに対して、CDKのほうはなんというか「CDKを触ってる」って感じ。うまく言えないんだけど、初動のところでTerraformよりも「わかった」感がなんか少ないかな。使い込んでいけば変わるのかもしれないけど。