[AWS re:Invent 2018]Infrastructure as Codeを実現するAWS CDKを使ってみた

AWS最大のイベント[re:Invent2018](https://reinvent.awsevents.com/)に参加しています。
2018年8月に公開されたCloud Development Kit (CDK)について、下記のセッションでも紹介されていたので触ってみました。

DEV327 - Beyond the Basics: Advanced Infrastructure as Code Programming on AWS

# AWS CDKとは
AWS CDKはAWS上でInfrastructure as Codeを実現するためのフレームワークでOSS化されています。[CloudFormation](https://aws.amazon.com/cloudformation/)のようにDeclarativeにではなく、Imperativeにリソースを定義できるのが特徴です。最終的なリソースのプロビジョニングにはCloudFormationテンプレートが使われるため、CloudFormationの機能をラップするようなフレームワークです。

https://github.com/awslabs/aws-cdk

DeclarativeとImperativeの比較はセッションの中でも紹介されていました。

![IMG_0083.JPG](https://qiita-image-store.s3.amazonaws.com/0/317676/5bbdb32e-3f73-cd9e-24c3-6b121f43a693.jpeg)

プログラミング言語を使ってリソースを定義できるのが特徴で、執筆時点でサポートしている言語は下記の通りです。

- Java
- Javascript, TypeScript
- .NET

# 使ってみる
さっそくCDKを使ってリソースのプロビジョニングを行ってみたいと思います。
今回は[公式チュートリアル](https://awslabs.github.io/aws-cdk/getting-started.html)を参考にJavascriptを使ってリソースを定義していきます。

## 利用開始の準備
まずはCDKのコマンドラインツールキットをインストールします。インストールには[Node.js](https://nodejs.org/en/download)と[npm](https://www.npmjs.org/)の環境が必要です。

下記のコマンドでCDKをインストールします。

```shell
$ npm install -g aws-cdk
$ cdk --version
0.18.1 (build 9f7af21)
```

執筆時点のCDKのバージョンは0.18.1でした。

続いてCDKを使うためのプロジェクトを作成します。
下記のコマンドを実行して、プロジェクトを初期化します。

```shell
$ mkdir reinvent2018-cdk
$ cd reinvent2018-cdk
$ npm init -y
```

最後にCDKのスタックやアプリを作成するのに必要なCore Library（@aws-cdk/cdk）をインストールします。

```shell
$ npm install @aws-cdk/cdk
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN reinvent2018-cdk@1.0.0 No description
npm WARN reinvent2018-cdk@1.0.0 No repository field.

+ @aws-cdk/cdk@0.18.1
added 10 packages from 7 contributors and audited 10 packages in 2.102s
found 0 vulnerabilities
```

以上で利用開始の準備は完了です。

## リソースのプロビジョニング
AWS CDKではリソースをApplication、Stack(s)、Construct(s)の3層で定義します。

![IMG_0107.JPG](https://qiita-image-store.s3.amazonaws.com/0/317676/1d6c3979-589d-9205-5268-dc7ca7441540.jpeg)

さっそくリソースを定義していきます。

### Applicationの作成
まずはApplicationを作成していきます。プロジェクトフォルダに **bin/reinvent2018-cdk.js** ファイルを作成して、下記のようにApplicationの定義をします。

```javascript
const cdk = require('@aws-cdk/cdk');

class MyApp extends cdk.App {
    constructor() {
        super();
    }
}

new MyApp().run();
```

AWS CDKで作成するApplicationはcdk.Appクラスを継承して作成するようです。
CDKアプリケーションの実行にはJavascript以外の言語を利用する場合はコンパイルが必要です。今回はJavascriptなのでコンパイル不要です。

続いてCDKのコマンドラインツールが作成したCDK Applicationを実行できるように、プロジェクトフォルダのルートに **cdk.json** を作成し、下記のように記載します。

```json
{
  "app": "node bin/reinvent2018-cdk.js"
}
```

作成したら、下記のコマンドでApplication内のStackの一覧を取得します。
（ここではAWSの[Credentialsの設定](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/setup-credentials.html)が完了しているものとします。）

まだStackを作成していないので、空の配列が返ってきます。

```shell
$ cdk ls -l
[]
```


### Stackの作成
続いてApplicationの中にStackを作成します。
先ほど作成した **bin/reinvent2018-cdk.js** を下記のように更新してStackを定義します。

```javascript
const cdk = require('@aws-cdk/cdk');

class MyStack extends cdk.Stack {
    constructor(parent, id, props) {
        super(parent, id, props);
    }
}

class MyApp extends cdk.App {
    constructor(argv) {
        super(argv);

        new MyStack(this, 'reinvent2018-cdk');
    }
}

new MyApp().run();
```

Stackは **cdk.Stack** を継承して作成します。**parent**、**id**、**props** といったパラメータは[ドキュメント](https://awslabs.github.io/aws-cdk/getting-started.html#define-a-stack)を参照してください。

下記のコマンドを実行してスタックが定義されているか確認します。

```shell
$ cdk ls -l
- name: reinvent2018-cdk
  environment:
    name: <account-id>/ap-northeast-1
    account: "<account-id>"
    region: ap-northeast-1
```

うまく行っていれば、定義したスタックが表示されます。

### Constructの作成
最後にConstructの作成をします。ここではS3バケットを定義して作成します。
上記で作成した **bin/reinvent2018-cdk.js** のStack内にS3バケットを定義します。
S3のパッケージをインストールします。

```shell
$ npm install @aws-cdk/aws-s3
npm WARN reinvent2018-cdk@1.0.0 No description
npm WARN reinvent2018-cdk@1.0.0 No repository field.

+ @aws-cdk/aws-s3@0.18.1
added 6 packages from 1 contributor and audited 109 packages in 8.412s
found 0 vulnerabilities
```

**bin/reinvent2018-cdk.js** を下記のように更新します。

```javascript
const cdk = require('@aws-cdk/cdk');
const s3 = require('@aws-cdk/aws-s3');

class MyStack extends cdk.Stack {
    constructor(parent, id, props) {
        super(parent, id, props);
        new s3.Bucket(this, 'MyReInventBucket', {
            versioned: true,
            bucketName: 'reinvent2018-cdk'
        });
    }
}

class MyApp extends cdk.App {
    constructor(argv) {
        super(argv);

        new MyStack(this, 'reinvent2018-cdk');
    }
}

new MyApp().run();
```

Stackの定義の中にS3バケットのContructを加えました。

### リソースのデプロイ
CDKで定義したリソースを下記のコマンドでデプロイします。

```shell
$ cdk deploy
reinvent2018-cdk: deploying...
reinvent2018-cdk: creating CloudFormation changeset...
 0/3 | 15:48:07 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata | CDKMetadata 
 0/3 | 15:48:08 | CREATE_IN_PROGRESS   | AWS::S3::Bucket    | MyReInventBucket (MyReInventBucket83E9F87D) 
 0/3 | 15:48:10 | CREATE_IN_PROGRESS   | AWS::S3::Bucket    | MyReInventBucket (MyReInventBucket83E9F87D) Resource creation Initiated
 0/3 | 15:48:10 | CREATE_IN_PROGRESS   | AWS::CDK::Metadata | CDKMetadata Resource creation Initiated
 1/3 | 15:48:11 | CREATE_COMPLETE      | AWS::CDK::Metadata | CDKMetadata 
 2/3 | 15:48:30 | CREATE_COMPLETE      | AWS::S3::Bucket    | MyReInventBucket (MyReInventBucket83E9F87D) 

 ✅  reinvent2018-cdk

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:<account-id>:stack/reinvent2018-cdk/xxxxxxxxxxxxxxx
```

delpoyコマンドはCDKの定義からCloudFormationテンプレートを合成し、CloudFormation経由でリソースの作成を行います。
実際にAWSコンソールを確認するとCloudFormationのスタックとS3バケットが作成できているのが確認できます。

**CloudFormationのスタック**
![stack.png](https://qiita-image-store.s3.amazonaws.com/0/317676/6918142a-bb4a-53dc-9819-d99b7bdc03ee.png)

**S3バケット**
![s3bucket.png](https://qiita-image-store.s3.amazonaws.com/0/317676/039daf57-7048-bb22-3244-5f124d80d2ee.png)

またS3バケットのバージョニングの設定をtrueにしたので、バージョニングも有効化されています。

**バージョニングの設定**
<img width="987" alt="versioningOn.png" src="https://qiita-image-store.s3.amazonaws.com/0/317676/815e32f8-1424-17f2-4fa0-3f42b700f480.png">


## リソースの更新
一度デプロイしたリソースを更新してみます。
デプロイした **bin/reinvent2018-cdk.js** の一部を更新し、versioningの機能を無効化します。

```javascript
const cdk = require('@aws-cdk/cdk');
const s3 = require('@aws-cdk/aws-s3');

class MyStack extends cdk.Stack {
    constructor(parent, id, props) {
        super(parent, id, props);
        new s3.Bucket(this, 'MyReInventBucket', {
            versioned: false,
            bucketName: 'reinvent2018-cdk'
        });
    }
}

class MyApp extends cdk.App {
    constructor(argv) {
        super(argv);

        new MyStack(this, 'reinvent2018-cdk');
    }
}

new MyApp().run();
```

更新したら、**diff** コマンドを使うことでデプロイ済みのリソースとの差分を確認することができます。

```shell
$ cdk diff
Resources
[~] AWS::S3::Bucket MyReInventBucket MyReInventBucket83E9F87D 
 └─ [-] VersioningConfiguration
     └─ {"Status":"Enabled"}
```

再度 **deploy** コマンドを実行してスタックを更新します。

```shell
$ cdk deploy
reinvent2018-cdk: deploying...
reinvent2018-cdk: creating CloudFormation changeset...
 0/2 | 15:58:24 | UPDATE_IN_PROGRESS   | AWS::S3::Bucket    | MyReInventBucket (MyReInventBucket83E9F87D) 
 1/2 | 15:58:45 | UPDATE_COMPLETE      | AWS::S3::Bucket    | MyReInventBucket (MyReInventBucket83E9F87D) 
 1/2 | 15:58:47 | UPDATE_COMPLETE_CLEA | AWS::CloudFormation::Stack | reinvent2018-cdk 
 2/2 | 15:58:48 | UPDATE_COMPLETE      | AWS::CloudFormation::Stack | reinvent2018-cdk 

 ✅  reinvent2018-cdk

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:<account-id>:stack/reinvent2018-cdk/xxxxxxxxxxxxxxx
```

実際にコンソールからもCloudFormationのスタックとS3のバージョニングの設定が更新されているのが確認できました。

**CloudFormationのスタック**
![stackUpdate.png](https://qiita-image-store.s3.amazonaws.com/0/317676/4f86c8e6-c5f4-927b-af23-ac82feebc158.png)

**バージョニングの設定**
![verisioningOff.png](https://qiita-image-store.s3.amazonaws.com/0/317676/39f38f54-3934-8034-3cbc-02fc24c7becb.png)


# まとめ
馴染みのプログラミング言語を使ってImperativeにInfrastructure as Codeを実現できるAWS CDKを触ってみました。今回は基本の部分しか触れていませんが、プログラミング言語で記載できることからより柔軟なリソース定義ができるようになると思います。執筆時点ではDeveloper Previewですが、これから機能や対応言語が充実してくることが期待できるので楽しみですね。