AWSのCDKに入門してみた

みなさんこんにちは！

AWS CDKは使われたことはありますでしょうか？

私自身、現在はTerraformを使ってGCPのリソース管理の業務があるため、Terraformを触ることはあるのですが、AWS CDKについてほとんど知見がありませんでした。

ただ、次の職場ではインフラのリソース管理をTerraformではなく、AWS CDKを使って管理しているので、今回はその入門として備忘録的な記事として作成します。


## AWS CDKとは

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255267/678c2549-cf12-923c-120e-3fc9fa023dbf.png)


AWS CDKとは`AWS Cloud Development Kit`の略です。

現在バージョン2がリリースされており、1のサポート期限は2023/6/1となっております。

ざっくり説明しますと、「プログラミング言語を用いてクラウド上にアプリケーションを構築することができるIaC」です。

IaC？？？

IaCは、`Infrastructure as Code`の略で、コードやスクリプトを使用してインフラのプロビジョニングと設定を自動的に行う方法のことを指しています。

類似サービスとして**Terraform**などがあります。

AWS CDKを用いることでインフラの構築が可能となります。

### メリット

1. 高いレベルでの抽象化を使用しているため、より少ないコードで多くのインフラ構築が可能
1. プログラミングの記法を使って柔軟に設計可能
1. インフラをコードで管理することでテスト等もでき、より堅牢にできる
1. 既存のCloud Formationテンプレートをインポートして利用できる
1. 得意な言語(Python ,JavaScript、Java、C#)で管理できる
1. サービス構築のためのテンプレートが500以上ある
1. 現在運用中のインフラとコード変更による差分を確認できる
1. GUIからやると時間がかかるし、ミスが起きるし、内容がばらつくのを回避できる

`高いレベルでの抽象化`というのは、本来CloudFormationで記述しないといけない分の半分以下まで削って記述できることを指しています。

一番大きなメリットとしてはプログラミング言語が利用できることかと思います。

JavaScriptを例にとると、JavaScript特有の構文(変数定義や繰り返し等)をリソース作成で利用できるのはコード記述の削減にもつながるのでとても便利に感じます。
(まだ使ったことないのでメリットはそこまで感じていませんが...この後実際に利用してみる予定です)


ちなみに、CDKのデプロイによってCloudFormationのテンプレートを作成することになるため、CloudFormationの仕組みについては理解しておく必要はありますが、CloudFormationのYaml記述については把握する必要はないです。

### 料金など

CloudFormationも無料である通り、AWS CDKも無料で利用できます。

ただ、デプロイによってリソースを作成したことで発生する料金はかかってきます。

### 別IaCであるTerraformとの違い

私自身、Terraformをよく利用していたのですが、その違いについては記述量かなと思います。

結構Terraformは各設定について記述をしていかないといけないのに対し、CDKだと記述しなくても設定できます。

一方、細かな設定変更ができるのはTerraformであるので、カスタマイズ性で言うならTerraformかなと思います。

しかし、CDKもConstructのレイヤーが３つに分かれており、Low Level Constructで記述すると記述量が増えますが細かな設定ができるようになるので、CDKでも不可能ではありません。

Constructのレイヤーについては後ほどご説明いたします。

## CloudFormationとは？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255267/8ba13768-bc53-6408-e3fe-d95b0bd6eada.png)


Yaml形式のファイルで、テンプレートと呼ばれています。

このテンプレートを書くことによってスタックというものが作成され、VPCやEC2などのリソースが作られます。

AWS CDK自体、最終的にCloudFormationのテンプレートが作成される仕組みとなっているため、AWS CDKはCloudFormationのプログラミング言語版と思えば良いのかなと。

リソース管理として利用する人もいらっしゃるかと思いますが、実際のテンプレートの記述はかなりの記述量となっており、結構大変です。

VPCのサブネット4つ作るだけで約200行近い記述が必要になるため、LoadBalancer周りを作ることを考えるとゾッとします...

こういった大きな記述量を高レベルでの抽象化をすることで記述を減らして手軽にしたのはAWS CDKとなります。


参考：https://aws.amazon.com/jp/cloudformation/

## AWS CDKの構成

CDKの構成には主に３つの構成になっています。

先程の画像をもう一度掲載いたします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255267/678c2549-cf12-923c-120e-3fc9fa023dbf.png)

- App
- Stack
- Construct

順に説明していきます。

### App

Appは、最上位要素、複数スタックの依存関係を管理・定義します。Appには一つ以上のStackを定義することになります。

### Stack

CloudFormationのStackに相当するので、テンプレートとなります。
Stackには一つ以上のConstructを定義することになります。

このStack単位でAWSへデプロイされることになります。

### Construct

こちらはS3やLambda関数、DynamoDBなどのリソース単位での管理となります。

上層のStack層にConstruct Libraryを使用してリソース定義をすることになります。

Construct Libraryは、さらに三つのレイヤーに分かれており、レイヤーごとで

- High Level Construct
- Low Level Construct
- Patterns

と呼ばれています。

基本私たちが記述する際は、High Level Constructで書くのが良いでしょう。High Level Constructだと、デフォルト値や便利なメソッドを定義したAWSリソースを表すクラスを利用できます。

もし細かなカスタマイズや設定をしたいということでしたらLow Level Constructで記述するのが良いでしょう。

Low Level Constructは、CloudFormationと1:1での記述となり、各リソースを表すクラスにCFnというプレフィックスがついています。

CFnと同等の制御が可能となることから、カスタマイズ性が上がります。

一方、Low Level Constructになると相当分記述量が増えることになることはご理解いただきたいです。

## 使い方と流れ

ここからは実際にAWS CDKを触って構築していきます。

とりあえず動かす！的な感覚ならこちらだとサクッと体験できます。
https://aws.amazon.com/jp/getting-started/guides/setup-cdk/

もしガッツリ取り組みたいということでしたら、下記はとても参考になります。
https://cdkworkshop.com/

まずはサクッと体験してみましょう！

### 準備

実際に利用するためには、「AWS CDK CLI」のインストールが必要となります。

既にインストール済みの方はスキップしていただければと。
まだの方は、下記の手順でインストールを進めてください。

```zsh
$ npm install -g aws-cdk
```

正常にインストールされたことを確認するには、次のコマンドで可能です。

```zsh
cdk --version
2.43.0 (build 487870a)
```

### アカウントのブートストラップ

AWS CDKを初めて利用する際は、アカウントのブートストラップというものが必要になります。

例えば、AWS LambdaやDockerイメージを作成する際に、スタックと同時に外部ファイルなどが生成します。

これらの外部ファイルは、S3に置くことになるのですが、S3にアクセスできる(アップロードできる)ように事前にしておかなければなりません。

その作業をブートストラップと呼んでおります。

ですので、ここでいうブートストラップはAWS CDKを利用するための初期化みたいなものと思ってもらえたらと思います。

ブートストラップにおいて必要な情報が二つです。

- アカウントID
- リージョン

アカウントIDとリージョンは下記コマンドで確認可能です。

```zsh
# Get the account ID
$ aws sts get-caller-identity
{
    "UserId": "xxxxxxxxxxxxxxxxx",
    "Account": "yyyyyyyyyyyy",
    "Arn": "arn:aws:iam::yyyyyyyyyyyy:user/terraform"
}


# Get the Region
$ aws configure get region
ap-northeast-1
```

ここでアカウントIDは`Account`の部分になります。


ブートストラップは下記コマンドで実行します。

```zsh
# Bootstrap the account
$ cdk bootstrap aws://ACCOUNT-NUMBER/REGION

 ✅  Environment aws://yyyyyyyyyyyy/ap-northeast-1 bootstrapped. とでたら完了
```

アカウントとリージョンが変更になる場合は都度ブートストラップする必要があります。

### プロジェクト作成

まずはプロジェクトディレクトリを作成し、cdkをTypeScriptで作成するための設定をしましょう。


```zsh
$ mkdir cdk-demo
$ cd cdk-demo
$ cdk init --language typescript
```

出力結果で下記のようにどういったコマンドが使えるかを示してくれます。

```zsh
## 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
```

そしてinitコマンドを実行したことで、下記ファイル群が一斉に作成されました。

```zsh
├── bin
│   └── cdk-demo.ts
├── cdk.json
├── jest.config.js
├── lib
│   └── cdk-demo-stack.ts
├── package.json
├── package-lock.json
├── README.md
├── test
│   └── cdk-demo.test.ts
└── tsconfig.json
```

package.jsonやtsconfigのファイルはTypexScriptだとお馴染みのファイルですので説明は割愛します。

ここでポイント(💡)となるのはここになりそうです。

```zsh
├── bin ← 💡
│   └── cdk-demo.ts
├── cdk.json ← 💡
├── jest.config.js
├── lib ← 💡
│   └── cdk-demo-stack.ts
├── package.json
├── package-lock.json
├── README.md
├── test
│   └── cdk-demo.test.ts
└── tsconfig.json
```

binディレクトリ ： アプリケーションへのエントリポイントとなるファイルが作成されます。

libディレクトリ ： スタックやリソースの定義などのファイルが作成されます。

cdk.jsonファイル ： アプリケーションの実行方法、およびCDKとプロジェクトに関連する追加設定とパラメータをツールキットに指示します。

### アプリケーションベース作成

まずは全体となるアプリケーションの定義・作成を進めます。

チュートリアルにも書いてあるのですが、アプリケーション定義として先程ブートストラップ時に使用したアカウントIDやリージョンを定義します。

```ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from '@aws-cdk/core';
import { CdkDemoStack } from '../lib/cdk-demo-stack';

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

});
```

これでリソース作成の準備が整いました。


#### VPCについて

今回初めてのリソースとして二つのアベイラビリティーゾーンにまたがるVPCを作成します。

AWSを構築する上で、ベースとなるのがVPCです。

VPCというAWS上に仮想ネットワーク空間を作成し、その中でリソースを動かすようにします。

一つのVPCを論理的なまとまりとして分離することが可能となり、複数間のVPC接続も可能です。

また、VPCもプライベート空間とパブリック空間で構築できます。

細かいお話はしませんが、そのVPCの中に下記のような機能を持っております。

- サブネット
- ルーティング
- ゲートウェイとエンドポイント
- ピアリング接続
- VPN接続


### スタック作成

VPCを作成するには、ライブラリの構築モジュールをインストールしてそれを使って実装していきます。

最終的にはEC2を構築するため、下記をインストールします。

```zsh
$ npm install @aws-cdk/aws-ec2 or yarn add @aws-cdk/aws-ec2
```

stack関係はlibディレクトリの中のファイルで構築していくとお話ししましたので、実際に`cdk-demo-stack.ts`を触っていきます。

初期状態

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

export class CdkDemoStack 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, 'CdkDemoQueue', {
    //   visibilityTimeout: cdk.Duration.seconds(300)
    // });
  }
}

```


VPC作成のコード

```ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
// import * as sqs from 'aws-cdk-lib/aws-sqs';
import { Vpc, SubnetType } from 'aws-cdk-lib/aws-ec2';

export class CdkDemoStack 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, 'CdkDemoQueue', {
    //   visibilityTimeout: cdk.Duration.seconds(300)
    // });

    const vpc = new Vpc(this, 'MainVPC', {
    // CHANGE: this is where we define how many AZs to use
    maxAzs: 2,
   // CHANGE: We define a single subnet configuration per AZ.
      subnetConfiguration:  [
        {
          // CHANGE: this is it's CIDR mask so 255.255.255.0
          cidrMask: 24,

          // CHANGE: a name for each of these subnets
          name: 'public-subnet',

          // CHANGE: and the subnet type to be used - here we will have
          // a public subnet. There are other options available here.
          subnetType: SubnetType.PUBLIC
        },
      ]
    })
  }
}
```

constructorでありますが、三つの引数を用意します。

- scope
- id
- props


まずscopeに関しては、cdkのconstructを指しているので、`this`で指定します。

idに関しては、同じスコープ内で一意にするIDですので、特に命名に決まりはありません。

propsに関しては、特に指定なければデフォルトが設定されますが、カスタマイズしたい時はここに値を指定していきます。

例えば、VPCのconstructは下記ドキュメントで確認できます。

https://docs.aws.amazon.com/cdk/api/v1/docs/@aws-cdk_aws-ec2.Vpc.html#construct-props

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255267/bea4ab93-8c46-b7a4-2553-8ea197c8370a.png" width=600 />


見ていくと`cidr`やAZの最大値を示す`maxAzs`や`subnetConfiguration`にてサブネットの設定ができるようです。

今回のコードでは、2つのアベイラビリティーゾーンを作成し、サブネットはpublicという設定です。






### CloudFormationテンプレート作成

`cdk synth`を実行するとCloudFormationのテンプレートがターミナルに出力されます。

その出力情報から実際に作成されるテンプレートが確認可能です。


### デプロイ

デプロイ前に差分を確認する方法として`cdk diff`があります。

diffを取ることで現在の環境とCDKコードによる差分を抽出してくれるため、実行前の最終確認ができます。

```zsh
$ cdk diff

Stack CdkDemoStack
Parameters
[+] Parameter BootstrapVersion 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]"}

Resources
[+] AWS::EC2::VPC MainVPC MainVPC83A193D2
[+] AWS::EC2::Subnet MainVPC/public-subnetSubnet1/Subnet MainVPCpublicsubnetSubnet1Subnet42A164FD
[+] AWS::EC2::RouteTable MainVPC/public-subnetSubnet1/RouteTable MainVPCpublicsubnetSubnet1RouteTableB23EC393
[+] AWS::EC2::SubnetRouteTableAssociation MainVPC/public-subnetSubnet1/RouteTableAssociation MainVPCpublicsubnetSubnet1RouteTableAssociation7604B236
[+] AWS::EC2::Route MainVPC/public-subnetSubnet1/DefaultRoute MainVPCpublicsubnetSubnet1DefaultRouteAF36E86E
[+] AWS::EC2::Subnet MainVPC/public-subnetSubnet2/Subnet MainVPCpublicsubnetSubnet2Subnet8A4E2978
[+] AWS::EC2::RouteTable MainVPC/public-subnetSubnet2/RouteTable MainVPCpublicsubnetSubnet2RouteTableA1404C13
[+] AWS::EC2::SubnetRouteTableAssociation MainVPC/public-subnetSubnet2/RouteTableAssociation MainVPCpublicsubnetSubnet2RouteTableAssociation0BE8879F
[+] AWS::EC2::Route MainVPC/public-subnetSubnet2/DefaultRoute MainVPCpublicsubnetSubnet2DefaultRoute73C6B9C7
[+] AWS::EC2::InternetGateway MainVPC/IGW MainVPCIGWEFDF0B26
[+] AWS::EC2::VPCGatewayAttachment MainVPC/VPCGW MainVPCVPCGW7218CC97

Other Changes
[+] Unknown 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."}]}}


NOTICES

21902	apigateway: Unable to serialize value as aws-cdk-lib.aws_apigateway.IModel

	Overview: Users of CDK in any language other than TS/JS cannot use
	          values that return an instance of a deprecated class.

	Affected versions: framework: >=2.41.0 <=2.43.0

	More information at: https://github.com/aws/aws-cdk/issues/21902


If you don’t want to see a notice anymore, use "cdk acknowledge <id>". For example, "cdk acknowledge 21902".
```

この辺りは同じサービスとして存在するTerraformの`terrafrom plan`と似たような挙動ですね。

それでは問題ないと判断しましたらデプロイしてみましょう。

### コードに変更がある時

### 削除する時


```zsh
$ cdk destroy
```


## テストについて

三種類あります。

- SnapShot tests
- Fine-grained assertions
- Validation tests

### SnapShot tests

スナップショットでのテストでは、CFnテンプレートが前回生成したものと同一であるかをチェックすることになります。

言い換えますと、コード変更あれば失敗するということです。

このテストではリソースの変更が入る場合は必ず失敗するため、ソースコードのリファクタリングなどで有効的だと考えます。

### Fine-grained assertions

こちらは構築するリソースに対して意図した設定がされているかをテストするものになります。

先程のスナップショットテストとは異なり、リソースの変更が入る場合はこちらでテストすることが可能です。

### Validation tests

名前の通りリソース設定に対するバリデーションテストとなります。


## 参考記事

https://docs.aws.amazon.com/cdk/v2/guide/home.html
https://dev.classmethod.jp/articles/aws-cdk-construct-explanation/

実際に作成するなら細かい設定を見る時に役立ちます↓
https://docs.aws.amazon.com/cdk/api/v2/