背景・目的
今まで、AWSでIaCを書く際は、TerraformやAWS CloudFormation(以降、Cfnという)等を使ってきました。
最近、AWS CDK(以降、CDKという)を使う機会が増えてきたので、このタイミングで基本的な知識を整理します。
まとめ
下記に特徴をまとめます。
| 特徴 | 説明 |
|---|---|
| 定義 | プログラミング言語でAWSインフラを定義し、Cfnを通じてデプロイするオープンソースフレームワーク |
| 構成要素 | コンストラクトライブラリ(再利用可能なコード)+ CDK Toolkit(CLI・ライブラリ) |
| 対応言語 | TypeScript, JavaScript, Python, Java, C#/.Net, Go |
| 基本構造 | App → Stack → Constructのツリー構造。StackはCfnスタックに1:1対応 |
| コンストラクトレベル | L1(Cfn直接対応)、L2(ベストプラクティス付き、最も使われる)、L3(複数リソースのパターン) |
| Cfnとの関係 | Cfnの上位レイヤー。CDKコードは合成でCfnテンプレートに変換され、Cfnがデプロイする |
| ブートストラップ | デプロイ前にAWS環境を準備するプロセス。アカウント×リージョンごとに1回必要 |
| 開発の流れ | cdk init → コード → cdk synth → cdk diff → cdk deploy → cdk destroy |
概要
CDKとは
下記を基に整理します。
CDKは、プログラミング言語でAWSインフラを定義し、Cfnを通じてデプロイするオープンソースフレームワークである。下記にその構成要素と特徴を整理する。
- CDK アプリケーションをCfnを介してデプロイし、リソースをプロビジョニングする
- 2つの主要な部分で構成される
- AWS CDK コンストラクトライブラリ
- 事前記述されたモジュール式で再利用可能なコード(コンストラクト)のコレクション
- AWSサービスの定義・統合に必要な複雑さを低減する
- AWS CDK Toolkit
- CDKアプリの合成やデプロイを実行するツール
- コマンドラインツール(CDK CLI)とプログラムによるライブラリ(CDK Toolkit Library)で構成される
- AWS CDK コンストラクトライブラリ
- 対応言語
- TypeScript
- JavaScript
- Python
- Java
- C#/.Net
- Go
- 対応する言語を通じて、コンストラクトと呼ばれる再利用可能なクラウドコンポーネントを定義する

出典:AWS CDKとは
AWS CDK の利点
CDKを使用することで、下記の利点がある
- 高い自由度
- 高信頼性
- スケーラブル
- 高いコスト効率
Infrastructure as Code (IaC) の開発と管理
- インフラをコードで管理でき、コードレビュー・ユニットテスト・ソースコントロール等のソフトウェアエンジニアリングのベストプラクティスを適用できる
- インフラ、アプリケーションコード、設定をすべて1か所に配置できる
汎用プログラミング言語を使用してクラウドインフラストラクチャの定義
- 条件分岐・ループ・継承等のプログラミング要素をそのまま使える
- IDEの補完やシンタックスハイライトも利用可能
- インフラとアプリケーションロジックを同じ言語で定義できる
AWS CloudFormation を介してインフラストラクチャをデプロイする
- エラー時のロールバック等、Cfnの機能をそのまま活用できる。そのため、Cfnを理解していれば、新しいデプロイ基盤を学ぶ必要がない
コンストラクトを使用してアプリケーションの開発をすばやく開始
- 再利用可能なコンポーネント(コンストラクト)により、少ないコードで多くのインフラを定義できる
- 自作コンストラクトを組織内やパブリックに共有することも可能
主要概念
下記を基に整理します。
CDKアプリケーションは、App・Stack・Constructの3層のツリー構造で構成される。それぞれの役割を下記に整理する。
App(アプリケーション)
└── Stack(スタック)= Cfnスタックに対応
└── Construct(コンストラクト)= AWSリソースの定義単位
App
CDKアプリケーションのルート。1つ以上のStackを含む
Stack
- デプロイの単位
- 1つのStackが1つのCfnスタックに対応する
- Stack単位でデプロイ・削除・更新が行われる
Construct
- CDKの最も基本的な構成要素
- AWSリソースの定義単位
- Constructには抽象度の異なる3つのレベルがあり、用途に応じて使い分ける
| レベル | 説明 | 特徴 |
|---|---|---|
| L1 | ・Cfnリソースと1:1に対応する。抽象化なし 別名: CFN Resources |
全プロパティを直接制御できるが、設定量が多い |
| L2 | ・プロパティ、アクセス許可、イベントベースのインタラクションを簡単に定義できるヘルパーメソッドを有している。 ・通常は最も広く使用されているコンストラクトタイプ ・単一のCfnリソースに対応し、直感的なAPIとベストプラクティスのデフォルト設定を提供 ・使いたいサービスによってはL2が存在せず、L1で書く必要がある場合がある 別名: Curated |
最もよく使われる。 セキュリティのデフォルト設定やgrantメソッド等が組み込まれている |
| L3 | ・複数リソースを組み合わせたアーキテクチャパターン・ アプリケーションの特定のユースケースの AWS アーキテクチャ全体を作成するために使用する ・入力とコードの量を最小限に抑えながら、複数のリソースをすばやく作成して設定可能 別名: Patterns |
数行のコードでVPC + ECS + ALBのような構成を作れる。 ただし柔軟性は低い |
コンストラクトの定義
コンポジション
- コンストラクトを組み合わせて、より高レベルの抽象化を定義できる
- 例えば、DynamoDBテーブルにバックアップ・自動スケーリング・モニタリングを含めた構成を1つのコンストラクトとして定義できる
- 定義したコンストラクトはライブラリとしてチーム間で共有・再利用でき、バージョン管理も他のパッケージと同様に行える
初期化
コンストラクトのインスタンス化時に、必ず下記の3つを渡す
| パラメータ | 説明 |
|---|---|
| scope | ・親となるコンストラクト ・コンストラクトツリー内でのこのコンストラクトの場所を決定する ・通常は this(Pythonではself) |
| id | ・スコープ内で一意の識別子。 ・Cfnの論理IDやリソース名の生成に使われる |
| props | ・コンストラクトの設定(プロパティ) ・高レベルほどデフォルトが多く、省略可能な場合もある |
設定
ほとんどのコンストラクトは3番目の引数としてpropsを受け入れる。コンストラクトの設定を定義する名前と値のコレクション。
new s3.Bucket(this, 'MyEncryptedBucket', {
encryption: s3.BucketEncryption.KMS,
websiteIndexDocument: 'index.html'
});
- 上記の例では、propsとして下記を指定している
- encryption(KMS暗号化を有効化)
- websiteIndexDocument(静的ウェブサイトホスティングを有効化)
- 一方、どのKMSキーを使うか(encryptionKey)は指定していない
- この場合、Bucketコンストラクトが新しいKMSキーを自動生成して関連付ける
- 上記に記載したL2コンストラクトの「セキュリティのデフォルト設定やgrantメソッド等が組み込まれている」のこと
コンストラクトの操作
- コンストラクトは、基底クラスである Construct クラスを拡張したクラス
- コンストラクトをインスタンス化すると、コンストラクトオブジェクトは一連のメソッドとプロパティを公開する
- 自作コンストラクトのAPIは自由に定義できるが、AWS公式のコンストラクト(s3.Bucket等)はガイドラインに従って統一されている
- どのAWSサービスでもgrantメソッドやpropsの使い方が同じで、1つ覚えれば他のサービスでも同じ感覚で使える
権限付与
ほとんどのAWSコンストラクトには、IAMアクセス許可を付与するgrantメソッドがある。
const rawData = new s3.Bucket(this, 'raw-data');
const dataScience = new iam.Group(this, 'data-science');
rawData.grants.read(dataScience);
- 上記では、IAM グループ data-science に対し、Amazon S3 バケット raw-data から読み取りを行うためのアクセス許可を付与している
- S3バケットraw-dataに対して、IAMグループdata-scienceに読み取り権限を付与している
- IAMポリシーのJSONを自分で書く必要がない
リソース属性の参照
あるコンストラクトの属性(ARN、名前、URLなど)を別のコンストラクトに渡せる
const jobsQueue = new sqs.Queue(this, 'jobs');
const createJobLambda = new lambda.Function(this, 'create-job', {
runtime: lambda.Runtime.NODEJS_18_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('./create-job-lambda-code'),
environment: {
QUEUE_URL: jobsQueue.queueUrl // SQSキューのURLをLambdaの環境変数に渡す
}
});
- jobsQueue.queueUrlでSQSキューのURLを取得し、Lambda関数の環境変数に設定している
- リソース間の連携をコード上で直接表現できる
Mixins で機能を追加する
- .with()メソッドで、L1・L2どちらのコンストラクトにも機能を追加できる仕組み
- L1は本来Cfnのプロパティを全部自分で書く必要があるが、Mixinを使えばよく使う設定を
.with()一行で追加できる - L2はpropsが既に簡潔なため、Mixinを使う意味はほぼないと思われる (私感)
- L1は本来Cfnのプロパティを全部自分で書く必要があるが、Mixinを使えばよく使う設定を
// L1コンストラクトにMixinを適用
new s3.CfnBucket(this, 'MyBucket')
.with(new s3.mixins.BucketVersioning())
.with(new s3.mixins.BucketBlockPublicAccess());
// L2コンストラクトにMixinを適用
new s3.Bucket(this, 'MyL2Bucket', { removalPolicy: cdk.RemovalPolicy.DESTROY })
.with(new s3.mixins.BucketAutoDeleteObjects());
- 各サービスモジュールのmixins名前空間(s3.mixins等)から利用できる
- L2に適用した場合、内部のL1リソースに自動的に適用される
- L1でもL2と同様の機能を簡単に追加できるのがメリット
CloudFormationとの関係
下記を基に整理します。
- CDKはCfnの「上位レイヤー」。(代替ではない。)
- CDKで書いたコードは最終的にCfnテンプレートに変換される
- デプロイの実行基盤はCfnそのもの
- そのため、ロールバック等のCfnの機能はそのまま使える
- CDKスタックをデプロイする前に、まずはスタックを合成する
- 合成とは、CDKスタックからCfnテンプレートとデプロイアーティファクトを生成するプロセス
【初回※1】
cdk bootstrap
↓
AWS環境にCDKToolkitスタック作成(S3バケット、ECR、IAMロール)
【開発サイクル】
CDKコード(TypeScript等)
↓ cdk synth(合成)
クラウドアセンブリ(Cfnテンプレート + デプロイアーティファクト※2)
↓ cdk deploy
アセットをS3バケットにアップロード → CfnがAWSリソースをプロビジョニング
↑
ブートストラップで作成したリソースを使用
※1 CDKのブートストラップテンプレートは定期的に新バージョンが公開されるため、その際はcdk bootstrapの再実行が推奨される(出典)。再実行しても、必要なら更新され、不要なら何も起きない。
※2 デプロイアーティファクトとは、Cfnテンプレート以外にデプロイに必要なファイル(Lambdaの関数コード、Dockerイメージ、S3にアップロードするファイル等)。これらがブートストラップで作られたS3バケットやECRリポジトリにアップロードされる。
合成とは
生成されたテンプレートとアーティファクトはクラウドアセンブリと呼ばれる
- 実際のデプロイはCfnが行う
- CDKアプリでL2/L3コンストラクトを使っている場合、合成時には数行のCDKコードから数百行のCfnテンプレートが生成される
ブートストラップとは
- CDKスタックをデプロイする前に、AWS環境を準備するプロセス
-
cdk bootstrapコマンドを実行すると、下記のブートストラップリソースがAWS環境に作成される
| リソース | 用途 |
|---|---|
| S3バケット | CDKプロジェクトのファイル(Lambdaコード、アセット等)を保存 |
| ECRリポジトリ | Dockerイメージを保存 |
| IAMロール | CDKがデプロイ時に使う権限 |
- これらはCfnスタック「CDKToolkit」として管理される
-
cdk bootstrapを実行すると、裏側ではCfnスタックが1つ作られる- このスタックの名前がデフォルトでCDKToolkit
- ブートストラップリソース(S3バケット、ECRリポジトリ、IAMロール)はこのスタック内に定義される
- ブートストラップ自体もCfnで管理されている
- AWSコンソールのCfn画面で「CDKToolkit」として確認できる
- 再実行時はこのスタックの更新(Update)として処理される。ブートストラップリソース自体は更新される可能性があるが、CDKでデプロイしたアプリケーションのリソース(別スタック)には影響しない
- ブートストラップスタックの削除は非推奨。削除するとCDKデプロイ用のリソースも消え、復旧手段がない。--termination-protectionオプションで誤削除を防止できる
- AWS環境(アカウント×リージョン)ごとに1回ブートストラップが必要
- 東京とバージニアにデプロイするなら、それぞれで
cdk bootstrapを実行する
- 東京とバージニアにデプロイするなら、それぞれで
- CDKのバージョンアップやブートストラップ設定のカスタマイズ時には再実行が必要。再実行すると既存のCDKToolkitスタックが更新される
合成とブートストラップの連携
- 合成時に、CDKがブートストラップリソース(S3バケット名等)への参照をCfnテンプレートに埋め込む
- デプロイを成功させるには、合成が正しいブートストラップリソースを参照している必要がある
- CDKにはデフォルトのシンセサイザー※1とブートストラップ設定が付属しており、両方デフォルトなら意識する必要はない
- cdk synth実行時に、シンセサイザーが「アセットの置き場所はS3のXXXバケット」とテンプレートに書き込む
- cdk deploy実行時に、アセットをAWS上のXXXバケットにアップロードし、Cfnがデプロイする
- このXXXバケットはブートストラップで作られたものであり、シンセサイザーが書くバケット名と一致している必要がある
- 片方をカスタマイズする場合はもう一方も合わせる必要がある
※1 シンセサイザーとは、合成を行うCDKライブラリ内のコンポーネント。デフォルトでDefaultStackSynthesizerが使われ、通常は意識する必要がない。
開発の流れ
下記を基に整理します。
CDKでは、CDK CLIを使ってプロジェクトの作成からデプロイ・削除までを行う。基本的な流れは下記のとおり。
cdk init(プロジェクト作成)
↓
コードを書く
↓
cdk synth(合成)
↓
cdk diff(差分確認)
↓
cdk deploy(デプロイ)
↓
cdk destroy(削除)
cdk init(プロジェクト作成)
新しいCDKアプリを作成する。ディレクトリを作り、その中で実行する。
mkdir my-cdk-app
cd my-cdk-app
cdk init app --language typescript
テンプレートはapp(デフォルト、空のアプリ)とsample-app(SQS + SNSのサンプル付き)がある。
cdk synth(合成)
CDKコードからCfnテンプレートを生成する。生成されたテンプレートはcdk.outディレクトリに保存される。
cdk synth # スタックが1つの場合
cdk synth MyStack # スタックを指定
cdk deploy等の他のコマンドも内部でまず合成を実行するため、明示的にcdk synthを実行するのはテンプレートの内容を確認したいときが主な用途。
cdk diff(差分確認)
現在のCDKコードとデプロイ済みのスタックの差分を表示する。デプロイ前に何が変わるかを確認できる。
cdk diff MyStack
IAMポリシーの変更やリソースの追加・削除が一覧で表示される。
cdk deploy(デプロイ)
CDKスタックをAWSにデプロイする。内部でまず合成が実行され、その後Cfnを通じてリソースが作成・更新される。
cdk deploy # スタックが1つの場合
cdk deploy MyStack # スタックを指定
cdk deploy "*" # 全スタック
- セキュリティに関わる変更(IAMポリシーの拡大等)がある場合、デフォルトで承認を求められる
-
--no-rollbackオプションで、開発中にロールバックを無効化できる(本番非推奨)
cdk destroy(削除)
デプロイしたスタックとそのリソースを削除する。
cdk destroy MyStack
考察
下記のようなことがわかりました。
- CDKはCfnの代替ではなく上位レイヤーであり、最終的にはCfnテンプレートに変換されてデプロイされる。そのため、CDKを使う上でもCfnの基本的な理解は必要
- L2コンストラクトがCDKの主な利点であり、セキュリティのデフォルト設定やgrantメソッドにより、Cfnを直接書くよりも安全かつ少ないコードでインフラを定義できる。ただし、L2が存在しないサービスではL1で書く必要があり、その場合はCfnとほぼ同じ記述量になる
- ブートストラップはアカウント×リージョンごとに必要で、削除すると復旧手段がない。本番環境では--termination-protectionを付けるべき
- 合成とブートストラップの連携(シンセサイザー)はデフォルト設定なら意識不要だが、カスタマイズする場合は両方の整合性を取る必要がある。この仕組みを理解していないとデプロイ失敗時のトラブルシューティングが難しくなる
- TerraformやCfnと比較すると、CDKはプログラミング言語の機能(条件分岐、ループ、型チェック等)をそのまま使える点が最大の差別化要因。一方で、合成というステップが挟まるため、デバッグ時にCDKコードとCfnテンプレートの両方を見る必要がある場面がある
参考