AWS Cloud Developement Kit : TypeScript Tutorial編

AWS Cloud Development Kit (以下CDK)はプログラミング言語を使用してAWS環境の定義、デプロイができるフレームワークです。CDKのコードは最終的にCloudFormationのテンプレートに変換されてデプロイされるため、宣言型のCloudFormationの持つ冪等性(繰り返し実行しても同じ結果になる)の恩恵を受けつつ、様々なデータ構造や繰り返しなどの制御構造を持つプログラミング言語でインフラストラクチャを記述できるというメリットがあります。2019/1/19時点ではPreviewで、C#/JavaScript/TypeScript/Javaで記述することができます。
[前回](https://qiita.com/f-daiki/items/c41867b10ef4342ae3db)はCDKのインストールを実施したので今回はTypeScriptを使って公式のTutorialを実施します。

https://awslabs.github.io/aws-cdk/tutorial.html

# 環境
* OS: macOS Sierra (10.12.6)
* AWS CDK: 0.21.0  
Node.jsが動く環境であればどの環境でも動作するはずです。

# 前提条件
以下は予めインストールしておく必要があります。

* Node.js (>= 8.11.x)
* AWS CLI
* git
* AWS CDK
   
# CDKプロジェクトの初期化
CDKプロジェクトをデフォルトテンプレートから初期化します。

### プロジェクトフォルダの作成

```sh:コマンド
$ mkdir hello-cdk
$ cd hello-cdk
```

Tutrialではここで<code>git init</code>していますが 、次の<code>cdk init</code>が空のディレクトリでないと実行できませんでした。また<code>cdk init</code>を実行すると自動的に.gitフォルダや.gitignoreファイルが作成されます。

### プロジェクトの初期化

```sh:コマンド
$ cdk init --language typescript
```
以下のようなディレクトリ構成となります。

```
total 208
drwxr-xr-x   13 xxxxx  1896053708    442  1  4 22:10 .
drwxr-xr-x    4 xxxxx  1896053708    136  1  4 22:10 ..
drwxr-xr-x   12 xxxxx  1896053708    408  1  4 22:10 .git
-rw-r--r--    1 xxxxx  1896053708     25  1  4 22:10 .gitignore
-rw-r--r--    1 xxxxx  1896053708     13  1  4 22:10 .npmignore
-rw-r--r--    1 xxxxx  1896053708    320 12 20 22:02 README.md
drwxr-xr-x    3 xxxxx  1896053708    102  1  4 22:10 bin
-rw-r--r--    1 xxxxx  1896053708     37  1  4 22:10 cdk.json
drwxr-xr-x    3 xxxxx  1896053708    102  1  4 22:10 lib
drwxr-xr-x  239 xxxxx  1896053708   8126  1  4 22:10 node_modules
-rw-r--r--    1 xxxxx  1896053708  78741  1  4 22:10 package-lock.json
-rw-r--r--    1 xxxxx  1896053708    345  1  4 22:10 package.json
-rw-r--r--    1 xxxxx  1896053708    558 12 20 22:02 tsconfig.json
```

# サンプルアプリケーションのビルド
デフォルトテンプレートに含まれるサンプルアプリケーションをビルドします。


### サンプルコードの確認
<code>cdk init</code>を実行した時点で、サンプルコードが出力されています。以下の前者がCDKアプリケーション、後者はCDKアプリケーションでimportしているHelloCdkStackスタックです。ファイルが分かれているのは単純に再利用性を考慮してのことと思われます。

```sh:コマンド
$ cat bin/hello-cdk.ts
```

```typescript:bin/hello-cdk.ts
#!/usr/bin/env node
import cdk = require('@aws-cdk/cdk');
import { HelloCdkStack } from '../lib/hello-cdk-stack';

const app = new cdk.App();
new HelloCdkStack(app, 'HelloCdkStack');
app.run();
```

```sh:コマンド
$ cat lib/hello-cdk-stack.ts
```

```typescript:lib/hello-cdk-stack.ts
import cdk = require('@aws-cdk/cdk');

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

    // The code that defines your stack goes here
  }
}
```

### tsファイルのビルド

```sh:コマンド
$ npm run build
```

<code>cdk ls</code>コマンドでStackをリストするとHelloCdkStackが表示されますが、この時点ではAWS環境には何も展開されていません。

```sh:コマンド
$ cdk ls -l
```

```:Output例
- name: HelloCdkStack
  environment:
    name: xxxxxxxxxxxx/ap-northeast-1
    account: "xxxxxxxxxxxx"
    region: ap-northeast-1
```

# AWSリソースの追加とデプロイ
サンプルスタックにはAWSリソースが定義されていないため、S3バケットを1つ追加します。その後実際にAWS環境にデプロイします。

### @aws-cdk/aws-s3パッケージのインストール

```sh:コマンド
$ npm install @aws-cdk/aws-s3
```
### S3バケットの追加

<code>lib/hello-cdk-stack.ts</code>を編集し、S3バケットをスタックに追加します。</li>

```typescript:lib/hello-cdk-stack.ts
import cdk = require('@aws-cdk/cdk');
import s3 = require('@aws-cdk/aws-s3');    //s3パッケージをimport

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

        new s3.Bucket(this, 'MyFirstBucket', {    //s3バケットを追加
            versioned: true
        });
    }
}
```

### ビルド

```sh:コマンド
$ npm run build
```

### リソースの確認
CDKによって作成されるCloudFormationスタックのテンプレート(Resources)を確認します。

```sh:コマンド
$ cdk synth HelloCdkStack
```

```yaml:Output例
Resources:
  MyFirstBucketB8884501:
    Type: AWS::S3::Bucket
    Properties:
      VersioningConfiguration:
        Status: Enabled
    DeletionPolicy: Retain
    Metadata:
      aws:cdk:path: HelloCdkStack/MyFirstBucket/Resource
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Modules: aws-cdk=0.21.0,@aws-cdk/aws-codepipeline-api=0.21.0,@aws-cdk/aws-events=0.21.0,@aws-cdk/aws-iam=0.21.0,@aws-cdk/aws-kms=0.21.0,@aws-cdk/aws-s3=0.21.0,@aws-cdk/aws-s3-notifications=0.21.0,@aws-cdk/cdk=0.21.0,@aws-cdk/cx-api=0.21.0,jsii-runtime=node.js/v10.2.1
```
S3バケットが定義されています。Logical Nameは<code>s3.Bucket</code>オブジェクト作成時の引数<code>MyFirstBucket</code>の後にランダム文字列が追加されたものとなっています。<code>DeletionPolicy: Retain</code>のためStackを削除してもバケットが残るようになっています。

<code>cdk diff</code>コマンドを実行すると現状のリソースとの差分が表示されます。</li>
  
```sh:コマンド
$ cdk diff
```
```:Output例
Resources
[+] AWS::S3::Bucket MyFirstBucket MyFirstBucketB8884501
```

### スタックのデプロイ

```sh:コマンド
$ cdk deploy
```

```:Output例
HelloCdkStack: deploying...
HelloCdkStack: creating CloudFormation changeset...
 0/3 | 11:19:16 | REVIEW_IN_PROGRESS   | AWS::CloudFormation::Stack | HelloCdkStack User Initiated
 0/3 | 11:19:21 | CREATE_IN_PROGRESS   | AWS::CloudFormation::Stack | HelloCdkStack User Initiated
 0/3 | 11:19:26 | CREATE_IN_PROGRESS   | AWS::S3::Bucket    | MyFirstBucket (MyFirstBucketB8884501)
 0/3 | 11:19:26 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata | CDKMetadata
 0/3 | 11:19:28 | CREATE_IN_PROGRESS   | AWS::S3::Bucket    | MyFirstBucket (MyFirstBucketB8884501) Resource creation Initiated
 0/3 | 11:19:29 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata | CDKMetadata Resource creation Initiated
 1/3 | 11:19:29 | CREATE_COMPLETE      | AWS::CDK::Metadata | CDKMetadata
 2/3 | 11:19:48 | CREATE_COMPLETE      | AWS::S3::Bucket    | MyFirstBucket (MyFirstBucketB8884501)
 3/3 | 11:19:50 | CREATE_COMPLETE      | AWS::CloudFormation::Stack | HelloCdkStack

 ✅  HelloCdkStack

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:stack/HelloCdkStack/4cf8a750-10
90-11e9-a609-0671757157c6
```

### スタックの確認
 
作成されたスタックを確認します。

```sh:コマンド
$ aws cloudformation describe-stacks \
    --stack-name HelloCdkStack
```

```:Output例
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:stack/HelloCdkStack/4cf8a750-1090-11e9-a609-0671757157c6",
            "StackName": "HelloCdkStack",
            "ChangeSetId": "arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:changeSet/CDK-0f39e1e5-0f1b-43fc-9a04-c1570dcd75ff/03917ebe-6886-4123-aa06-e7ef0139be3f",
            "CreationTime": "2019-01-05T02:19:16.463Z",
            "LastUpdatedTime": "2019-01-05T02:19:21.942Z",
            "RollbackConfiguration": {},
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false,
            "NotificationARNs": [],
            "Capabilities": [
                "CAPABILITY_IAM",
                "CAPABILITY_NAMED_IAM"
            ],
            "Tags": [],
            "EnableTerminationProtection": false,
            "DriftInformation": {
                "StackDriftStatus": "NOT_CHECKED"
            }
        }
    ]
}
```

作成されたS3バケットのPhysical ID(バケット名)を確認します。
 
```sh:コマンド
$ aws cloudformation describe-stack-resources \
    --stack-name HelloCdkStack \
    --query StackResources[?ResourceType==\`AWS::S3::Bucket\`]
```

```:Output例
[
    {
        "StackName": "HelloCdkStack",
        "StackId": "arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:stack/HelloCdkStack/4cf8a750-1090-11e9-a609-0671757157c6",
        "LogicalResourceId": "MyFirstBucketB8884501",
        "PhysicalResourceId": "hellocdkstack-myfirstbucketb8884501-sakucck9x6ae",
        "ResourceType": "AWS::S3::Bucket",
        "Timestamp": "2019-01-05T02:19:48.651Z",
        "ResourceStatus": "CREATE_COMPLETE",
        "DriftInformation": {
            "StackResourceDriftStatus": "NOT_CHECKED"
        }
    }
]
```
<code>hellocdkstack-myfirstbucketb8884501-sakucck9x6ae</code>となっています。[stack名]-[Logical ID]-[ランダム文字列]のようです。

コード内で指定したS3バケットのバージョニング設定を確認します。

```sh:コマンド
aws s3api get-bucket-versioning \
    --bucket "hellocdkstack-myfirstbucketb8884501-sakucck9x6ae"
```
```:Output例
{
    "Status": "Enabled"
}
```

# AWSリソースの変更
次はS3バケットの設定を変更し、差分をデプロイします。

### コードの編集
<code>lib/hello-cdk-stack.ts</code>を編集し、KMS暗号化を有効にします。

```typescript:lib/hello-cdk-stack.ts
    new s3.Bucket(this, 'MyFirstBucket', {
      versioned: true,                             //カンマを追加
      encryption: s3.BucketEncryption.KmsManaged   //追加
    });
```

### ビルド

```sh:コマンド
$ npm run build
```

### 差分の確認

```sh:コマンド
$ cdk diff
```
```:Output例
Resources
[~] AWS::S3::Bucket MyFirstBucket MyFirstBucketB8884501
 └─ [+] BucketEncryption
     └─ {"ServerSideEncryptionConfiguration":[{"ServerSideEncryptionByDefault":{"SSEAlgorithm":"aws:kms"}}]}
```

### スタックのデプロイ

```sh:コマンド
$ cdk deploy
```
```:Output例
HelloCdkStack: deploying...
HelloCdkStack: creating CloudFormation changeset...
 0/2 | 11:48:31 | UPDATE_IN_PROGRESS   | AWS::CloudFormation::Stack | HelloCdkStack User Initiated
 0/2 | 11:48:37 | UPDATE_IN_PROGRESS   | AWS::S3::Bucket    | MyFirstBucket (MyFirstBucketB8884501)
 1/2 | 11:48:58 | UPDATE_COMPLETE      | AWS::S3::Bucket    | MyFirstBucket (MyFirstBucketB8884501)
 1/2 | 11:49:00 | UPDATE_COMPLETE_CLEA | AWS::CloudFormation::Stack | HelloCdkStack
 2/2 | 11:49:01 | UPDATE_COMPLETE      | AWS::CloudFormation::Stack | HelloCdkStack

 ✅  HelloCdkStack

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:stack/HelloCdkStack/4cf8a750-1090-11e9-a609-0671757157c6
```

### 設定の確認

S3バケットの暗号化設定を確認します。

```sh:コマンド
$ aws s3api get-bucket-encryption \
    --bucket "hellocdkstack-myfirstbucketb8884501-sakucck9x6ae"
```
```:Output例
{
    "ServerSideEncryptionConfiguration": {
        "Rules": [
            {
                "ApplyServerSideEncryptionByDefault": {
                    "SSEAlgorithm": "aws:kms"
                }
            }
        ]
    }
}
```

# スタックの削除
展開されたスタックをAWS環境から削除します。
プロジェクトフォルダで<code>cdk destroy</code>コマンドを実行します。

```sh:コマンド
$ cdk destroy
```
```:Output例
Are you sure you want to delete: HelloCdkStack (y/n)? y
HelloCdkStack: destroying...
   0 | 12:02:25 | DELETE_IN_PROGRESS   | AWS::CloudFormation::Stack | HelloCdkStack User Initiated
   0 | 12:02:27 | DELETE_IN_PROGRESS   | AWS::CDK::Metadata | CDKMetadata
   0 | 12:02:27 | DELETE_SKIPPED       | AWS::S3::Bucket    | MyFirstBucket (MyFirstBucketB8884501)
   1 | 12:02:30 | DELETE_COMPLETE      | AWS::CDK::Metadata | CDKMetadata

 ✅  HelloCdkStack: destroyed
```

削除されたか確認します。
 
```sh:コマンド
$ aws cloudformation describe-stacks \
      --stack-name "HelloCdkStack" 
```
```:Output例
An error occurred (ValidationError) when calling the DescribeStacks operation: Stack with id HelloCdkStack does not exist
```

#おまけ
各CDKコマンドでCallされたAWS APIです。今回はCloudTrailから取得しておりソースコードを全て読んだわけではないので漏れはあるかも。予想通りCloudFormationのAPIをCallしていました。cdk diffはソースコードも少し見てみましたがchange setを作っているわけではなく、GetTemplateで既存のテンプレートを取得してローカルとの差分を見ているようです。

|CDKコマンド|サービス|アクション|
|:--------|:-------|:-------|
|cdk init --language typescript|なし|なし|
|cdk ls -l|なし|なし|
|cdk diff|CloudFormation|GetTemplate|
|cdk deploy|CloudFormation|DescribeStackEvents<br>DescribeStacks<br>ExecuteChangeSet<br>DescribeChaneSet<br>CreateChangeSet<br>GetTemplate|
|cdk destroy|CloudFormation|DescribeStackEvents<br>DescribeStacks<br>DeleteStack|