【完全初心者向け】AWS CDKに入門してみた

## 目次

- [目次](#目次)
- [はじめに](#はじめに)
- [AWS CDKの使い方](#aws-cdkの使い方)
  - [準備](#準備)
  - [CDK APPの作成](#cdk-appの作成)
  - [CDK AppへのS3バケットの追加](#cdk-appへのs3バケットの追加)
  - [CloudFormationテンプレートへの変換](#cloudformationテンプレートへの変換)
  - [スタックのデプロイ](#スタックのデプロイ)
  - [CDK Appの修正](#cdk-appの修正)
  - [CDK Appの削除](#cdk-appの削除)
- [まとめ](#まとめ)
- [参考](#参考)

## はじめに
業務でAWS CDKを使っていますが、すでに構築済のコードを使っているだけだったので、勉強のためにAWS CDKに入門してみました。

## AWS CDKの使い方

### 準備

typescriptのインストール
```bash
npm init
npm install typescript
```

バージョンの確認
```bash
$ node -v
v14.18.3
$ ./node_modules/.bin/tsc --version
Version 4.7.4
```


CDKのインストール
```bash
$ npm install -g aws-cdk
$ cdk --version
2.29.1 (build c42e961)
```

### CDK APPの作成

プロジェクトの作成
```bash
% mkdir hello-cdk
% cd hello-cdk
```

APPの作成
```bash
cdk init app --language typescript
```

空ではないフォルダで実行すると以下のエラーが発生するので注意
```bash
`cdk init` cannot be run in a non-empty directory!
```

### CDK AppへのS3バケットの追加

S3のリソース作成用のパッケージインストール
```bash
npm install @aws-cdk/aws-s3
```

以下コードを追加

```hello-cdk/lib/hello-cdk-stack.ts
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as cdk from 'aws-cdk-lib';

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

    const s3Bucket = new s3.Bucket(this, 's3-bucket')
  }
}
```
参考：https://bobbyhadz.com/blog/aws-cdk-s3-bucket-example

以下を実行
```bash
npm run build
```

特にエラーなく問題なさそうです。

### CloudFormationテンプレートへの変換

`cdk synth`で変換します。
特にエラーなく上手くいってそうです。

```yaml
$ cdk synth
Resources:
  s3bucket64CB25AF:
    Type: AWS::S3::Bucket
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
    Metadata:
      aws:cdk:path: HelloCdkStack/s3-bucket/Resource
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Analytics: v2:deflate64:H4sIAAAAAAAA/yXIQQ5FMBCA4bPYtyPFgi034ADitSMZZZqYloW4+yNW/5e/gKIBk02naOu8XukH1xAn69WzRinhapP1GFU386f7ZY8S0m7xdRfYUaTAt+LgEBbJD1OBqaHMFiHSe+JIG0L/9Q9kulgGcwAAAA==
    Metadata:
      aws:cdk:path: HelloCdkStack/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
              - me-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - sa-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-2
      - Fn::Or:
          - 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.
```

### スタックのデプロイ
以下コマンドでデプロイしてみます。

```bash
cdk deploy
```

以下エラーが発生しました
```bash
HelloCdkStack failed: Error: HelloCdkStack: SSM parameter /cdk-bootstrap/hnb659fds/version not found. Has the environment been bootstrapped? Please run 'cdk bootstrap'
```

以下コマンドを実行します。

```bash
cdk bootstrap
```

再度デプロイしてみます。
```bash
cdk deploy
```

リソースができていることを確認できました。
```bash
$ aws s3 ls
2022-06-25 20:34:34 hellocdkstack-s3bucket64cb25af-gjdjivmfijqh
```

### CDK Appの修正
現状、作成したバージョニングは無効となっています。
(以下CLIコマンドを実行しても、バージョニングの情報はでてきません)
```bash
$ aws s3api get-bucket-versioning --bucket hellocdkstack-s3bucket64cb25af-gjdjivmfijqh
```

以下のようにバージョニングを有効にしてみます。
```hello-cdk/lib/hello-cdk-stack.ts
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as cdk from 'aws-cdk-lib';

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

    const s3Bucket = new s3.Bucket(this, 's3-bucket', {
      versioned: true,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    })
  }
}
```

以下の部分は、`cdk destroy`時にバケットを削除するために必要です。

```ts
removalPolicy: cdk.RemovalPolicy.DESTROY
```

以下コマンドで差分を確認します。
デプロイ前に事前に差分を確認できます。

```bash
$ cdk diff
Stack HelloCdkStack
Resources
[~] AWS::S3::Bucket s3-bucket s3bucket64CB25AF 
 └─ [+] VersioningConfiguration
     └─ {"Status":"Enabled"}
```

デプロイしてみます。

```bash
cdk deploy
```

再度バージョニングを確認してみると、有効となっていることを確認できます。
```bash
$ aws s3api get-bucket-versioning --bucket hellocdkstack-s3bucket64cb25af-gjdjivmfijqh
{
    "Status": "Enabled"
}
```

### CDK Appの削除

以下コマンドで削除します。

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

 ✅  HelloCdkStack: destroyed
```

バケットにアクセスすると存在しない旨のエラーがでて、無事削除できました。
```bash
aws s3api get-bucket-versioning --bucket hellocdkstack-s3bucket64cb25af-gjdjivmfijqh

An error occurred (NoSuchBucket) when calling the GetBucketVersioning operation: The specified bucket does not exist
```

CDK Appのコードの中で以下のようにremovalPolicyでDESTROYを指定していない場合は、スタック削除後もリソースは残ります。

```ts
removalPolicy: cdk.RemovalPolicy.DESTROY
```

## まとめ
今回はAWS CDKに入門してみました。

CloudFormationと比べて少ないコードでリソースが自動生成されるので、便利だなと感じました。

これからも勉強を継続してIaCの知見を伸ばそうと思います。

## 参考
https://dev.classmethod.jp/articles/trying-aws-cdk-tutorial-with-typescript/

https://no-retire-no-life.com/aws-cdk-s3/