【AWS】用語を整理しながら学ぶAWS CDK（TypeScriptを利用）

## この記事で伝えたいこと（ポイント）

- IaCとは何かを説明しているよ
- AWS CDKとは何かを説明しているよ
- 実際の使い方や概念に触れているよ

## はじめに

この記事では 用語を整理しながらAWSが提供するAWS CDK（以下、本文ではCDK）を学習していく内容になっています。主な内容としては実践したときのメモを中心に書きます。
（忘れやすいことなど）誤りなどがあれば書き直していく予定です。

## AWS CDKとは

まず、CDKとはなんでしょうか。AWSの公式docsを見てみましょう。

> AWS Cloud Development Kit (AWS CDK) は、コードでクラウドインフラストラクチャを定義し、 AWS CloudFormationを通じてプロビジョニングするためのオープンソースのソフトウェア開発フレームワークです。

参考：[AWS CDK とは - AWS クラウド開発キット (AWS CDK) v2](https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/home.html)

併せて公式の紹介サイトを見てみましょう。

> 使い慣れたプログラミング言語を使用したクラウドアプリケーションリソースの定義

[AWS クラウド開発キット – アマゾン ウェブ サービス](https://aws.amazon.com/jp/cdk/)

コードでインフラを管理？AWS CloudFormationでプロビジョニング？フレームワーク？
プログラミング言語でクラウドアプリケーションリソースを定義？

ちょっと何言っているかわからないですね。順番に見ていきましょう。

## 前提：コードでインフラを管理するとは

簡単に説明するとインフラストラクチャーの状態がコードで説明できる状態にあること
またはその手法、技術的手段のことです。

なお、AWS公式による説明では以下のように説明されています。

> Infrastructure as Code (IaC) とは、手動のプロセスや設定の代わりにコードを使用してコンピューティングインフラストラクチャをプロビジョニングおよびサポートできることをいいます。

[Infrastructure as Code とは - IaC の説明 - AWS](https://aws.amazon.com/jp/what-is/iac/)

このIaCという概念は昔から存在しているものであり、近年になって初めて登場したものではありません。源流を辿ると1993年に登場したCFEngineにまで遡ります。
※諸説あります。言葉自体は最近になって提唱されたもの

CFEngineはPuppetやChefといったサーバー管理ツールと呼ばれるのツールの元祖です。
現在のIaCに大きな影響を与えたものだと言っても良いでしょう。

また、AnsibleもIaCのひとつです。いずれにしても重要なこととしてはシステム管理の自動化を主な目的としていることです。加えて、IaCはシステム管理にソフトウェア開発のベストプラクティスを適用したものとも言えます。

### 補足：IaCは誰が実践するのか

では、この便利なIaCは誰が実践するのでしょうか。
組織、役割、担当範囲によって異なると考えられますが、多くの場合においてインフラストラクチャーに詳しい者が実践することでしょう。

ですが、クラウドネイティブの世界ではなかなかそうもいきません。システム開発に携わる者でもインフラストラクチャーに携わる時代です。この後、AWS CloudFormationやAWS CDKの概要を通してIaCは誰が実践すべきなのかを説明できたらと思います。

## AWSのIaCでインフラをプロビジョニング

AWSが提供しているIaCでインフラを定義する場合は最初の選択肢としてAWS CloudFormationがあります。（それ以外だとSAMなどもありますが、ここではCloudFormationの一部として扱います）

AWS CloudFormationはYAML形式またはjson形式でスタックを定義してスタックをクラウドインフラ上に展開することによりコードによるインフラ構築を可能にしています。

ただし、AWS CloudFormationを実際に運用しようと考えた場合は以下のような課題があります。

- 独自のテンプレート構文を覚える必要がある
- 設定を細かくすればするほど、コードの記述量が増えてしまう
- AWSだけでなく、CloudFormationのベストプラクティスにも従う必要が出てくる。認知負荷が高い

AWS CloudFormationは文法さえ間違っていなければ問題なく動作し、そして際限なく細かく定義が書けてしまうため実際に扱うには相当なスキルを必要とします。

ちなみにAWS CloudFormationの解説は過去に記事を書いていますので興味のある方は参照していただけますと幸いです。[リンク](https://qiita.com/ymd65536/items/a4f3e6fbbd4983de7926)

### 補足：AWS SDKとはどう違うのか

ある意味似たようなことができてしまうAWS SDKについて見ていきます。

AWS SDKでも「インフラを構築する」ということが可能です。では、AWS CloudFormationとこの後紹介するCDKでは何が違うのでしょうか。

結論から先に説明するとAWS SDKにはAWS CloudFormationやCDKにおいて定義されるスタックという概念が存在しません。
AWS SDKはAPIリクエストベースの実行であり、アプリケーションの延長にすぎません。

また、AWS CLIとShellによるインフラの構築でスクリプトを書いた場合、いわゆるランブックと言われるものになります。要するに手順書の自動化にすぎません。

### 補足：さまざまなIaC

AWSにフォーカスしてIaCを見ていきましたが、他にもさまざまなIaCがあります。

ここでは詳しく取り扱いませんので興味のある方はAIに聞いてみましょう。

マルチクラウドで扱えるIaC

- [Terraform](https://developer.hashicorp.com/terraform)
    - さまざまなクラウド、SaaSのバックエンド活用されるIaC
- [OpenTofu](https://opentofu.org/)
    - OSS版 Terraform、いろいろあって本家TerraformからForkされたもの、Linux Foundationを中心に継続してメンテナンスされている
- [Pulumi](https://www.pulumi.com/)
    - マルチクラウドそして使い慣れた言語でインフラを定義できるIaC

Azure

- Bicep
    - AzureにおけるIaC、読んで字の如く上腕二頭筋が由来、抽象度の低いIaCが後述のARMだからという話も（諸説あり）
- ARM（正確にはARMテンプレート）
    - AzureにおけるIaC、Azure Resource Managerが解釈して実行する

Google Cloud

- Cloud Deployment Managerを使うとjsonやyaml、jinjaで定義できる
→ Terraformが推奨されている。

## AWS CDKの基本

さまざまな方法でインフラを定義できることや他のクラウドでも似たようなものがあるということがわかりました。では、CDKはどのようなものなのでしょうか。

簡単に説明するとAWS CloudFormationにプログラミングの概念を導入したフレームワークになります。

別の表現をするとプログラミングでAWS CloudFormationのスタックを作成できるフレームワークです。

ただし、スタックという概念以外にコンストラクトという概念があります。

### コンストラクトとは

コンストラクトはスタック内で定義される単位のことです。
CDKでインフラを定義することはスタックを定義することであり、コンストラクトを定義することでもあります。

このコンストラクトには抽象度ごとにレベルづけがされています。
数値が低くなるほど、抽象度は低くなり、設定に必要な記述が多くなる傾向にあります。

- レベル 1 (L1) コンストラクト
- レベル 2 (L2) コンストラクト
- レベル 3 (L3) コンストラクト

参考：[AWS CDK 構築 - AWS Cloud Development Kit (AWS CDK) v2](https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/constructs.html)

また、スタックには複数のコンストラクトを定義することが可能です。

## AWS CDKでインフラを定義する

なんとなくわかってきたところで実際に手を動かしていきたいところですが
その前に`スタックをどこで定義してどこで実行するのか`というところを見ていきましょう。

CDKのプロジェクトを`cdk init`で作成すると以下の3つのディレクトリが作成されます。

|名前|役割|
|:---|:---|
|bin|cdkのエントリポイント、ここに定義したスタックを書くことでスタックを展開できる|
|lib|スタックを定義するディレクトリ|
|tests|定義したスタックの設定をテストできる。テストの方法はプログラミング言語によってさまざま。TypeScriptの場合は標準でjestが使われる。|

説明だけではイメージが難しいのでここで実際にどんなものか見ていきましょう。

### bin

まずはCDK実行時にエントリーポイントとして使われる部分です。
エントリーポイントというのはプログラムが実行されるとき、最初に実行されるスタートラインのようなものです。CDKに限らず、プログラミングの世界における用語になりますので覚えておきましょう。

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

const app = new cdk.App();
new CdkStack(app, 'CdkStack', {
  /* If you don't specify 'env', this stack will be environment-agnostic.
   * Account/Region-dependent features and context lookups will not work,
   * but a single synthesized template can be deployed anywhere. */

  /* Uncomment the next line to specialize this stack for the AWS Account
   * and Region that are implied by the current CLI configuration. */
  // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },

  /* Uncomment the next line if you know exactly what Account and Region you
   * want to deploy the stack to. */
  // env: { account: '123456789012', region: 'us-east-1' },

  /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
});
```

重要なポイントとしては`aws-cdk-lib`をインポートして`App`を呼び出しているところです。

[class App · AWS CDK](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.App.html)

このAppから全てが始まります。このAppはCDKの中心となる存在です。この後説明するスタックを制御します。

### lib

`lib`ではbinで展開されるスタックを定義します。binにあるコードを読むと`import`で以下のコードが見つかると思います。

```ts:bin/cdk.ts
import { CdkStack } from '../lib/cdk-stack';
```

これはlibで定義したスタックをbinで使うという定義になります。
そして、そのスタックの定義は以下のような書き方になります。

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

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

ここで重要なポイントとしては`Construct`というものが`import`されているということでしょう。このコードからもわかるとおり、スタックにはコンストラクトが内包されています。
また、`CdkStack`は`cdk.Stack`を継承していることも確認できます。

### tests

最後にtestsです。`bin`と`lib`までなら想像ができると思います。「IaCでテスト？」と思うかもしれません。

結論から先に説明するとCDKにおけるテストとはパラメータのチェックになります。

```ts:/tests/cdk.test.ts
// import * as cdk from 'aws-cdk-lib';
// import { Template } from 'aws-cdk-lib/assertions';
// import * as Cdk from '../lib/cdk-stack';

// example test. To run these tests, uncomment this file along with the
// example resource in lib/cdk-stack.ts
test('SQS Queue Created', () => {
//   const app = new cdk.App();
//     // WHEN
//   const stack = new Cdk.CdkStack(app, 'MyTestStack');
//     // THEN
//   const template = Template.fromStack(stack);

//   template.hasResourceProperties('AWS::SQS::Queue', {
//     VisibilityTimeout: 300
//   });
});

```

ここで再び、`import`文を読んでみましょう。すると以下のようにインポートが見つかると思います。

```ts
// import { Template } from 'aws-cdk-lib/assertions';
```

assertions（アサーション）というのはアプリケーション開発に携わるものであれば、よく目にすることかと思いますが、主にアプリケーションのテストで使われる用語です。

CDKにおけるアサーションとはCDKで作成したスタックのパラメータが期待通りになっているかを確認することです。

参考：[aws-cdk-lib.assertions module · AWS CDK](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.assertions-readme.html)

厳密にはAWS上で定義されたAWS CloudFormationのスタックが期待通りに作成されているかを確認することです。

CDKあるいはIaCにおいて重要なこととしては記述通りの設定になっているかがあります。設定漏れや誤りを防ぐための機能とも言えるでしょう。

CDKのすごいところとしてはテストの仕組みにjestを採用しているところです。

jestが扱える人であれば、テストを書くことが可能であるということになります。
※今回はTypeScriptを前提に説明しているのでテストフレームワークはjestになっています。

## 具体例：VPC Lambdaを定義する

もっとイメージを深めるために少し複雑なスタックを見てみましょう。
今回はVPC上でLambdaを作成する例を見てみます。

```ts
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';
import * as iam from 'aws-cdk-lib/aws-iam';
import path = require('path');


interface StackProps extends cdk.StackProps {
  stage: string;
}

export class SampleStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: StackProps) {
    super(scope, id, props);
    const Env = props.stage;

    // 引き受けるタスクロールを環境ごとに選択
    let TaskRoleName = 'devRole';
    const lambdaRole = iam.Role.fromRoleName(this, 'TaskRole', TaskRoleName);

    const LambdaLayer = new lambda.LayerVersion(this, `${Env}LambdaLayer`, {
      code: lambda.Code.fromAsset(path.join(__dirname, '/lambda/layer')),
      compatibleRuntimes: [lambda.Runtime.PYTHON_3_11],
      description: 'Lambda Layer',
      layerVersionName: 'lambda_layer',
    });

    const vpc = ec2.Vpc.fromLookup(this, `${Env}Vpc`, {
      vpcName: `${Env}-vpc`
    });

    const subnets = vpc.selectSubnets({
      subnetType: ec2.SubnetType.PRIVATE_ISOLATED
    });

    const LambdaStack = new lambda.Function(this, `${Env}Lambda`, {
      functionName: `${Env}Lambda`,
      runtime: lambda.Runtime.PYTHON_3_11,
      handler: 'lambda_function.lambda_handler',
      code: lambda.Code.fromAsset(path.join(__dirname, '/lambda/function')),
      memorySize: 128,
      timeout: cdk.Duration.seconds(900),
      architecture: lambda.Architecture.X86_64,
      description: 'Sample Lambda',
      role: lambdaRole,
      layers: [LambdaLayer],
      vpc: vpc,
      vpcSubnets: subnets,
    });

    new lambda.Alias(this, `${Env}LambdaAlias`, {
      aliasName: Env,
      version: LambdaStack.currentVersion,
    });
  }
}
```

CDKによるLambdaの定義は非常に洗練されており、定数値やランタイムなどがライブラリから参照できるようになっています。また、配置するネットワークは`ec2.SubnetType.PRIVATE_ISOLATED`といった形でどのようなネットワークであるかをコードで意識できるようになっています。


## プログラミング言語でIaCを定義できるメリット

ここまでいろんなIaCを見つつ、CDKの使い方を見てきました。では、プログラミング言語でIaCを実現するメリットはなんでしょうか。3つほど具体例を挙げてみると以下のとおりです。

- 習得済みのプログラミング言語の概念を使ってインフラを定義できる
- ソフトウェア開発のベストプラクティスをインフラストラクチャーに適用できる
- 高度な条件分岐やテンプレート言語では実現の難しい反復処理が実装できる

ソフトウェアに落とし込むことでインフラをより正確に定義できるということになるのと同時に
アプリケーション開発者がインフラを扱えるようになります。

## まとめ

今回はCDKについてチェックしました。うまく扱えると非常に強力なツールであることは間違いありません。なお、今回は説明しませんでしたが、実際に使っている人に話を聞くと採用されている言語ではTypeScriptが多いです。

今回の記事もTypeScriptをベースに書いているのはTypeScriptの利用者が多いからというのもありますが、[公式ガイド](https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/stages.html)もTypeScriptを採用していることも理由に挙げられます。

実際に扱う際はどんな言語でやっていくかチーム内などで話し合って決めると良いでしょう。

## ここもみておきたい

- [マスタリング AWS CDK](https://techbookfest.org/product/wEWtvT8aRZgzAcVSnE7wS8)
    - 入門に最適です
- [AWS CDK は AWS CDK CLI と CDK コンストラクトライブラリを分離します | Amazon Web Services ブログ](https://aws.amazon.com/jp/blogs/news/aws-cdk-is-splitting-construct-library-and-cli/)

## おわり