AWS CDK で Infrastructure as Code する


こんにちは。ただいま絶賛AWSのナレッジを整理中です。今回は AWS CDK([AWS Cloud Development Kit](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html))を使ってみます。

AWS CDK をつかうことで、イチから作成するとまあまあメンドクサイCloudFormationの定義ファイルを生成したり、VPCとかNLBとかいわゆる**AWSのインフラをコードから構築(Infrastructure as Code)** したりできます。


例えばこんな感じにコードを書いて、EC2インスタンスを作成したりできるわけですね。

```typescript
  return new CfnInstance(stack, `EC2${name}`, {
    imageId: 'ami-00d101850e971728d',
    keyName: 'temp_private',
    instanceType: 't2.micro',
    networkInterfaces: [
      {
        associatePublicIpAddress,
        deviceIndex: '0',
        subnetId: subnet.attrSubnetId,
        groupSet,
      },
    ],
    tags: [{ key: 'Name', value: `${name}-ec2` }],
  })
```

今回はWSL上に、このコーディング環境の構築から、サンプルを実行するところまでやってみます。



## 事前準備

- [WSL環境にAWS コマンドラインインターフェイス(AWS CLI)を構築する](https://qiita.com/masatomix/items/7a1af1f6fc28258927ae)  でAWS CLIが利用可能
- [WSL環境にNode.js環境を構築する](https://qiita.com/masatomix/items/d69ac664a2fcbdb98f6c) でNode.jsが利用可能
- ``aws configure`` などで認証情報がセットアップ済み

## 環境構築

### AWS CDKをインストールする

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

added 1 package in 4s
$ cdk --version
Command 'cdk' not found...

$ exec $SHELL -l
$ cdk --version
2.89.0 (build 2ad6683)
$
```

WSLへAWS CDKのインストールが完了しました。

### Node.jsのプロジェクトを作成

```console
$ mkdir cdk-samples &&  cd $_

$ npx aws-cdk@2 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!

$
```

つづいて、おまじないコマンドを実行します(AWSアカウントとリージョン毎に、CDKが使う領域をS3につくるっぽい。なのでアカウントとリージョン毎に最初の1回だけやる)。

```console
$ yarn cdk bootstrap
yarn run v1.22.19
warning package.json: No license field
$ cdk bootstrap
 ⏳  Bootstrapping environment aws://xxxxxx/ap-northeast-1...
Trusted accounts for deployment: (none)
Trusted accounts for lookup: (none)
Using default execution policy of 'arn:aws:iam::aws:policy/AdministratorAccess'. Pass '--cloudformation-execution-policies' to customize.

CDKToolkit: creating CloudFormation changeset...
 ✅  Environment aws://xxxxxx/ap-northeast-1 bootstrapped.

Done in 10.93s.

$
```

プロジェクトの作成が完了しました！VScodeで開いて見ましょう。

```console
$ code ./
$ 
```

こんな画面が表示されればOKです！

![image-20230804222142014](https://imgur.com/IrXmibO.png)

## やってみる

``cdk-samples-stack.ts`` にコードを追加していきます。こんな感じにしてみました。

```typescript:lib/cdk-samples-stack.ts
$ cat lib/cdk-samples-stack.ts 
import * as cdk from 'aws-cdk-lib'
import { CfnVPC } from 'aws-cdk-lib/aws-ec2'
import { Construct } from 'constructs'
// import * as sqs from 'aws-cdk-lib/aws-sqs';

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

    new CfnVPC(this, 'MyVPC', {
      cidrBlock: '192.168.0.0/16',
      tags: [{ key: 'Name', value: `test-vpc` }],
    })
  }
}
$ 
```

192.168.0.0/16のcidrをもつVPCを作っています。

さて、コードを実行してみようと思いますが、その前にVPCを一覧しておきます。

```console
$ aws ec2 describe-vpcs \
    --query "Vpcs[*].[(Tags[?Key=='Name'])[0].Value, VpcId, CidrBlock]" --output  table
-----------------------------------------------------------------
|                         DescribeVpcs                          |
+------------------+-------------------------+------------------+
|  None            |  vpc-0c2e5bxxxxxxxxxxx  |  172.16.0.0/16   | 
|  defaultVPC      |  vpc-9dxxxxxx           |  172.31.0.0/16   |
|  tmp-vpc         |  vpc-0c29xxxxxxxxxxxxx  |  192.168.0.0/16  |
|  stg-VPCStack-VPC|  vpc-0cdbxxxxxxxxxxxxx  |  192.168.0.0/16  |
|  abcd-efghij-VPC |  vpc-08fxxxxxxxxxxxxxx  |  192.168.0.0/16  |
+------------------+-------------------------+------------------+
```

当たり前ですが ``Name = test-vpc`` なVPCはまだなさそうです。

さて、コードを実行するには下記のコマンドで。

```console
$ yarn cdk deploy        
yarn run v1.22.19
warning package.json: No license field
$ cdk deploy

✨  Synthesis time: 3.2s

CdkSamplesStack:  start: Building 71486aaf4aaf1612d6f83d23ea9dfac43922f2241f9ad0bcdf5220f3e3fcd88f:current_account-current_region
CdkSamplesStack:  success: Built 71486aaf4aaf1612d6f83d23ea9dfac43922f2241f9ad0bcdf5220f3e3fcd88f:current_account-current_region
CdkSamplesStack:  start: Publishing 71486aaf4aaf1612d6f83d23ea9dfac43922f2241f9ad0bcdf5220f3e3fcd88f:current_account-current_region
CdkSamplesStack:  success: Published 71486aaf4aaf1612d6f83d23ea9dfac43922f2241f9ad0bcdf5220f3e3fcd88f:current_account-current_region
CdkSamplesStack: deploying... [1/1]
CdkSamplesStack: creating CloudFormation changeset...

 ✅  CdkSamplesStack

✨  Deployment time: 26.56s

Stack ARN:
arn:aws:cloudformation:ap-northeast-1:xxxxx:stack/CdkSamplesStack/xxxxx-32e2-11ee-90b7-xxxxx

✨  Total time: 29.77s

✨  Done in 31.65s.
```

実行が完了しました！もう一度、VPCを見てみましょう。

```console
$ aws ec2 describe-vpcs \
    --query "Vpcs[*].[(Tags[?Key=='Name'])[0].Value, VpcId, CidrBlock]" --output  table
-----------------------------------------------------------------
|                         DescribeVpcs                          |
+------------------+-------------------------+------------------+
|  None            |  vpc-0c2e5bxxxxxxxxxxx  |  172.16.0.0/16   |
|  defaultVPC      |  vpc-9dxxxxxx           |  172.31.0.0/16   |
|  tmp-vpc         |  vpc-0c29xxxxxxxxxxxxx  |  192.168.0.0/16  |
|  stg-VPCStack-VPC|  vpc-0cdbxxxxxxxxxxxxx  |  192.168.0.0/16  |
|  test-vpc        |  vpc-0055xxxxxxxxxxxxx  |  192.168.0.0/16  |  ←増えています!
|  abcd-efghij-VPC |  vpc-08fxxxxxxxxxxxxxx  |  192.168.0.0/16  |
+------------------+-------------------------+------------------+
```

確かに増えていますね！コンソールでも見ておきましょう。

![Cfn](https://imgur.com/kZzI92K.png)


CloudFormationでインフラを作成したときのイベントのログが残っていますね。
追加したコード上の ``MyVPC`` などの引数や、`` bin/cdk-samples.ts`` に書いてあった ``new CdkSamplesStack(app, 'CdkSamplesStack', {`` などの引数が、論理IDってところに対応しているようですね。なるほど。

最後に作った物を消しておきましょう。以下のコマンドで。

```console
$ yarn cdk destroy
yarn run v1.22.19
warning package.json: No license field
$ cdk destroy

Are you sure you want to delete: CdkSamplesStack (y/n)? y
CdkSamplesStack: destroying... [1/1]

 ✅  CdkSamplesStack: destroyed

✨  Done in 24.84s.
% 
```

消えたようです。確認しておきましょう。

```console
$ aws ec2 describe-vpcs \
    --query "Vpcs[*].[(Tags[?Key=='Name'])[0].Value, VpcId, CidrBlock]" --output  table
-----------------------------------------------------------------
|                         DescribeVpcs                          |
+------------------+-------------------------+------------------+
|  None            |  vpc-0c2e5bxxxxxxxxxxx  |  172.16.0.0/16   | 
|  defaultVPC      |  vpc-9dxxxxxx           |  172.31.0.0/16   |
|  tmp-vpc         |  vpc-0c29xxxxxxxxxxxxx  |  192.168.0.0/16  |
|  stg-VPCStack-VPC|  vpc-0cdbxxxxxxxxxxxxx  |  192.168.0.0/16  |
|  abcd-efghij-VPC |  vpc-08fxxxxxxxxxxxxxx  |  192.168.0.0/16  |
+------------------+-------------------------+------------------+
```

ちゃんと消えていますね。

## まとめ

VPCを作って消す、というだけのとっても簡単なサンプルでしたが「**インフラをプログラミングで制御する**」って感覚がなんだか新鮮です。CloudFormationを直接用いる場合は、たとえばロードバランサを5コ構築しなくちゃなんてとき、定義をコピペしつつ 5ブロックyamlファイルを書くことになりそうですが、CDKを使うとプログラミングなので for文とかでかけちゃうわけですね。

スタック間のリソースのExport/Import などもクラスのpropertyにするだけだったりで、めちゃくちゃ直感的でした。また改めて記事にしようと思います。

以上おつかれさまでしたー



### 蛇足1: bootstrap もCloudFormationで動いてた

```console
$ aws cloudformation list-stacks --stack-status-filter CREATE_COMPLETE \
 --query "StackSummaries[*].[StackName,CreationTime]" \
 --output table
 
 0件
$ 

$ yarn cdk bootstrap
 ⏳  Bootstrapping environment aws://xxxxxx/ap-northeast-1...
CDKToolkit: creating CloudFormation changeset...
 ✅  Environment aws://xxxxxx/ap-northeast-1 bootstrapped.

Done in 10.93s.


$ aws cloudformation list-stacks --stack-status-filter CREATE_COMPLETE \
 --query "StackSummaries[*].[StackName,CreationTime]" \
 --output table
----------------------------------------------------
|                    ListStacks                    |
+-------------+------------------------------------+
|  CDKToolkit |  2023-08-05T00:53:20.288000+00:00  |
+-------------+------------------------------------+

$

```

CloudFormationのStackの一覧に行が追加されたようです。S3にBucketを作成するのとかもCloudFormationが動いていたようですね。


### 蛇足2: AWS CDKが動いたときのCloudFormationのログをAWS CLIで確認してみる


```console
$ aws cloudformation list-stacks --stack-status-filter CREATE_COMPLETE \
 --query "StackSummaries[*].[StackName,CreationTime]" \
 --output table
---------------------------------------------------------
|                      ListStacks                       |
+------------------+------------------------------------+
|  CdkSamplesStack |  2023-08-05T11:06:46.358000+00:00  |
|  CDKToolkit      |  2023-08-05T00:53:20.288000+00:00  |
+------------------+------------------------------------+
$
```
Stack名を確認して、、、

```console
$ aws cloudformation list-stack-resources --stack-name CdkSamplesStack \
 --query "StackResourceSummaries[*].[LogicalResourceId,ResourceType,PhysicalResourceId]" \
 --output table
-------------------------------------------------------------------------------
|                             ListStackResources                              |
+-------------+----------------------+----------------------------------------+
|  CDKMetadata|  AWS::CDK::Metadata  |  2ab94740-3380-11ee-8d5c-0e3c28701bab  |
|  MyVPC      |  AWS::EC2::VPC       |  vpc-0055xxxxxxxxxxxxx                 |
+-------------+----------------------+----------------------------------------+
$ 
```

```console
$ aws cloudformation describe-stack-events --stack-name CdkSamplesStack \
 --query "StackEvents[*].[Timestamp,LogicalResourceId,ResourceStatus,ResourceStatusReason]" \
 --output table
--------------------------------------------------------------------------------------------------------------
|                                             DescribeStackEvents                                            |
+-----------------------------------+------------------+---------------------+-------------------------------+
|  2023-08-05T11:07:08.897000+00:00 |  CdkSamplesStack |  CREATE_COMPLETE    |  None                         |
|  2023-08-05T11:07:07.908000+00:00 |  MyVPC           |  CREATE_COMPLETE    |  None                         |
|  2023-08-05T11:06:57.356000+00:00 |  CDKMetadata     |  CREATE_COMPLETE    |  None                         |
|  2023-08-05T11:06:57.218000+00:00 |  CDKMetadata     |  CREATE_IN_PROGRESS |  Resource creation Initiated  |
|  2023-08-05T11:06:56.971000+00:00 |  MyVPC           |  CREATE_IN_PROGRESS |  Resource creation Initiated  |
|  2023-08-05T11:06:55.410000+00:00 |  CDKMetadata     |  CREATE_IN_PROGRESS |  None                         |
|  2023-08-05T11:06:55.406000+00:00 |  MyVPC           |  CREATE_IN_PROGRESS |  None                         |
|  2023-08-05T11:06:51.818000+00:00 |  CdkSamplesStack |  CREATE_IN_PROGRESS |  User Initiated               |
|  2023-08-05T11:06:46.358000+00:00 |  CdkSamplesStack |  REVIEW_IN_PROGRESS |  User Initiated               |
+-----------------------------------+------------------+---------------------+-------------------------------+
$
```

CloudFormationの情報もAWS CLIで確認できました!



## 関連リンク

- [今回のソースコード](https://github.com/masatomix/cdk-samples/tree/feature/init)
- [AWS CDKを始めるハンズオン ─ IaCの第一歩をAWS LambdaとDynamoDBのシンプルな仕組みで学ぶ ](https://eh-career.com/engineerhub/entry/2023/04/27/093000) セットアップ部分はココがわかりやすい
- [AWS CDK Toolkit ](https://www.npmjs.com/package/aws-cdk)公式のnpm。コマンドの説明多数。
- [コンテキスト変数から値を取得](https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/get_context_var.html) 環境によって値を切り替えたいときに設定ファイルに外出しする方法。
- [AWS CDK で外部パラメーターを扱う（コンテキスト・バリューと環境変数）](https://maku.blog/p/vx5ta85/) 同じような話
- [AWS Command Line Interface の使用](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-using-cli.html)
- [公式のgetting_started](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html)
- [公式のベストプラクティス](https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/best-practices.html)
- [WSL環境にNode.js環境を構築する](https://qiita.com/masatomix/items/d69ac664a2fcbdb98f6c) 
- [WSL環境にAWS コマンドラインインターフェイス(AWS CLI)を構築する](https://qiita.com/masatomix/items/7a1af1f6fc28258927ae) 
- [AWS CDKのTIPS集](https://qiita.com/masatomix/items/7087e7813637ff5e9c36)