CDK Toolkit Library が GA になり、私が気付いた CDK CLI との違いを書きました。
目次
CDK Toolkit Library とは
CDK Toolkit Library とは、TypeScript プログラム内に直接 CDK のデプロイなどの処理を組み込んで実行できるライブラリで、2025 年 5 月に GA になりました。
Build Custom CLI's, Deployment Automation, and more with the AWS CDK Toolkit Library
サンプルリポジトリ
本記事で紹介するサンプルコード、つまり CDK コードを含む CDK Toolkit Library の具体的なコードを GitHub にアップロードしています。
注意
※本記事で書いている内容は 2025 年 6 月現在、私が個人的に動作検証をして気付いたものであり AWS もしくは AWS CDK から公式で公開されている情報ではないため、正確性を保証するものではありません。内容が間違っている可能性や、今後仕様が変更される可能性があるのでご注意ください。
また、本記事は以下のバージョン/環境で検証しました。
- CDK Library (
aws-cdk-lib
): 2.200.1 - CDK CLI (
aws-cdk
): 2.1018.1 - CDK Toolkit Library (
@aws-cdk/toolkit-lib
): 1.1.1 - nodejs: v20.18.3
- OS: Mac OS
CDK Toolkit Library の使い方
基本的な使い方
以下の様な処理を TypeScript コードとして書いて実行することで、cdk deploy
などの CLI コマンドとしてではなく TypeScript のプログラムとして CDK のデプロイや合成処理などが行えます。
const cloudAssemblySource = await toolkit.fromAssemblyBuilder(async () => { const app = new cdk.App(); new MyStack(app, 'MyStack'); return app.synth(); }); await toolkit.deploy(cloudAssemblySource);
上記のtoolkit.deploy
の引数には Cloud Assembly Source というものを渡す必要がありますが、それを作成するには以下の 3 種類のメソッド(+Custom Source)があります。
※本記事ではそれぞれの細かい解説はしないため、詳細は公式ドキュメントをご覧ください。
- fromCdkApp
- fromAssemblyBuilder
- fromAssemblyDirectory
- (Custom Source)
fromCdkApp
/* * From an existing CDK app */ const toolkit = new Toolkit(); // TypeScript app const cloudAssemblySource = await toolkit.fromCdkApp('ts-node app.ts'); await toolkit.deploy(cloudAssemblySource);
fromAssemblyBuilder
/* * From an inline assembly builder */ const toolkit = new Toolkit(); // Create a cloud assembly source from an inline CDK app const cloudAssemblySource = await toolkit.fromAssemblyBuilder(async () => { const app = new cdk.App(); new MyStack(app, 'MyStack'); return app.synth(); }); await toolkit.deploy(cloudAssemblySource);
fromAssemblyDirectory
/* * From an inline assembly builder */ const toolkit = new Toolkit(); // Use an existing cloud assembly directory const cloudAssemblySource = await toolkit.fromAssemblyDirectory('cdk.out'); await toolkit.deploy(cloudAssemblySource);
クラウドアセンブリのキャッシュ
基本的には、ただデプロイするだけであれば以下の様にシンプルにdeploy
を呼ぶだけで実現できます。
const cloudAssemblySource = await toolkit.fromAssemblyBuilder(async () => { const app = new cdk.App(); new MyStack(app, 'MyStack'); return app.synth(); }); await toolkit.deploy(cloudAssemblySource);
しかし、複数の操作を実行する場合(例えばdeploy
だけでなくlist
なども実行する場合)では、toolkit.synth
の結果をtoolkit.deploy
などの引数に渡すことでそれぞれの操作を高速に実行できます。
それは、synth
の結果、つまりクラウドアセンブリ(CloudFormation テンプレートや Lambda のコードアセットなど)をキャッシュして使い回すことができるためです。
またその場合、最後にcloudAssembly.dispose()
を呼ばないとクラウドアセンブリディレクトリに作られるロックファイルを解放できず、次に実行するときにエラーになるので注意です。
// Synthesize once and reuse const cloudAssembly = await toolkit.synth(cloudAssemblySource); try { // Multiple operations use the same assembly await toolkit.deploy(cloudAssembly, { /* options */ }); await toolkit.list(cloudAssembly, { /* options */ }); } finally { // Clean up when done await cloudAssembly.dispose(); }
CDK Toolkit Library と CDK CLI の違い
私が実際に検証してわかった CDK Toolkit Library と CDK CLI の違いを記載します。
あらためて書きますが、本記事で書いている内容は 2025 年 6 月現在、私が個人的に動作検証をして気付いたものであり AWS もしくは AWS CDK から公式で公開されている情報ではないため、正確性を保証するものではありません。内容が間違っている可能性や、今後仕様が変更される可能性があるのでご注意ください。
RequireApproval
のデフォルト値
例えば CDK コードから以下の様な IAM ポリシー部分を削除するとします。
// role.addToPrincipalPolicy( // new iam.PolicyStatement({ // actions: ['s3:PutObject'], // resources: ['arn:aws:s3:::my-bucket/*'], // }), // );
CDK CLI ではこの様な場合、デフォルトではユーザーに承認を求めます。しかし、Toolkit は Do you wish to deploy these changes
というメッセージを出力しますが、ユーザーからのy
などの入力無しに実行を進めてしまいます。
この理由は、Toolkit のRequireApproval
(--require-approval
) のデフォルト値が NEVER
だからです。(CDK CLI の方は BROADENING
)
というのも、Toolkit はプロンプト処理のために内部で IO Host という仕組み(IIoHost
インターフェース)を使用するのですが、デフォルトの IO Host には NonInteractiveIoHost
というものが使われていて、これはユーザーとのやり取りを何もしないものになります。
❯ npx ts-node src/index.ts ... ... IAM Statement Changes ┌───┬──────────────────────────┬────────┬──────────────┬─────────────┬───────────┐ │ │ Resource │ Effect │ Action │ Principal │ Condition │ ├───┼──────────────────────────┼────────┼──────────────┼─────────────┼───────────┤ │ - │ arn:aws:s3:::my-bucket/* │ Allow │ s3:GetObject │ AWS:${Role} │ │ │ │ │ │ s3:PutObject │ │ │ ├───┼──────────────────────────┼────────┼──────────────┼─────────────┼───────────┤ │ + │ arn:aws:s3:::my-bucket/* │ Allow │ s3:GetObject │ AWS:${Role} │ │ └───┴──────────────────────────┴────────┴──────────────┴─────────────┴───────────┘ (NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299) "--require-approval" is enabled and stack includes security-sensitive updates. Do you wish to deploy these changes CdkToolkitCliComparisonStack: deploying... [1/1] ... ...
デフォルトでプロジェクトのルートディレクトリのcdk.out
をoutdir
として使うかどうか
CDK CLI では、実際にデプロイや合成をする際、プロジェクトのルートディレクトリのcdk.out
ディレクトリにクラウドアセンブリが出力されます。
しかし、例えば Toolkit で以下の様に fromAssemblyBuilder
を使って実装した場合、プロジェクトのルートディレクトリのcdk.out
でなく一時ディレクトリ(例えば私の MacOS では/private/var/folders/...
)が使われます。
const getCloudAssemblySource = async (toolkit: Toolkit): Promise<ICloudAssemblySource> => { return await toolkit.fromAssemblyBuilder(async (_props: AssemblyBuilderProps) => { const app = cdkApp(); const cloudAssembly = await app.synth(); return cloudAssembly; }); };
クラウドアセンブリをプロジェクトのルートディレクトリのcdk.out
に出力したい場合、以下の様にoutdir
にそのパスを指定する必要があります。
const getCloudAssemblySource = async (toolkit: Toolkit): Promise<ICloudAssemblySource> => { return await toolkit.fromAssemblyBuilder( async (_props: AssemblyBuilderProps) => { const app = cdkApp(); const cloudAssembly = await app.synth(); return cloudAssembly; }, { outdir: path.resolve(__dirname, '../cdk.out'), }, ); };
ただしfromCdkApp
ではデフォルトでプロジェクトのルートディレクトリのcdk.out
にクラウドアセンブリを出力します。
デフォルトでcdk.json
を読むかどうか
CDK CLI でデプロイをする際、cdk.json
というファイルが読み込まれます。
これはcontextなどの情報が格納されたファイルで、ユーザーが指定するcontext
の値であったり、機能フラグなどが格納されます。
※AWS CDK における機能フラグとは、バージョンアップの際に行われる変更のうち破壊的変更であるものが、ユーザーのオプトインを経て反映される仕組みです。cdk init
を実行した際に、その時点で導入されている機能フラグがcdk.json
ファイルに格納されます。
例えば、cdk.json
に@aws-cdk/aws-iam:minimizePolicies
というフラグが true
で設定されていたとします。これは、例えば別々の IAM ステートメントで違うアクションが定義されていても、それらのプリンシパル・リソースなどが同じ場合にそれらを統合してくれるというものです。
{ // ... "context": { // ... "@aws-cdk/aws-iam:minimizePolicies": true,
ここで、以下の様な CDK コードを書いていたとします。
role.addToPrincipalPolicy( new iam.PolicyStatement({ actions: ['s3:GetObject'], resources: ['arn:aws:s3:::my-bucket/*'], }), ); role.addToPrincipalPolicy( new iam.PolicyStatement({ actions: ['s3:PutObject'], resources: ['arn:aws:s3:::my-bucket/*'], }), );
また、Toolkit コードには以下のようにfromAssemblyBuilder
を使用したとします。
const getCloudAssemblySource = async (toolkit: Toolkit): Promise<ICloudAssemblySource> => { return await toolkit.fromAssemblyBuilder( async (_props: AssemblyBuilderProps) => { const app = cdkApp(); const cloudAssembly = await app.synth(); return cloudAssembly; }, { outdir: path.resolve(__dirname, '../cdk.out'), }, ); };
この Toolkit コードでプログラムを実行した場合、cdk.json
で@aws-cdk/aws-iam:minimizePolicies
が true
で設定されているにも関わらず以下の様に、IAM ポリシーが統合されずに生成されてしまいます。
これは、cdk.json
が読み込まれず、該当の機能フラグが適用されていないからです。
"Statement": [ { "Action": "s3:GetObject", "Effect": "Allow", "Resource": "arn:aws:s3:::my-bucket/*" }, { "Action": "s3:PutObject", "Effect": "Allow", "Resource": "arn:aws:s3:::my-bucket/*" } ],
cdk.json
を読み込みたい場合、fromAssemblyBuilder
もしくはfromCdkApp
の第 2 引数(props
)の contextStore
に CdkAppMultiContext
クラスのオブジェクトを指定することで実現できます。
その引数には、cdk.json
がある、プロジェクトのルートディレクトリのパスを指定してください。
※ただしfromCdkApp
ではデフォルトでcontextStore
にCdkAppMultiContext
が指定されるため、何も指定しなくてもcdk.json
は読まれます。
const getCloudAssemblySource = async (toolkit: Toolkit): Promise<ICloudAssemblySource> => { return await toolkit.fromAssemblyBuilder( async (_props: AssemblyBuilderProps) => { const app = cdkApp(); const cloudAssembly = await app.synth(); return cloudAssembly; }, { outdir: path.resolve(__dirname, '../cdk.out'), contextStore: new CdkAppMultiContext(path.resolve(__dirname, '..')), }, ); };
この場合、cdk.json
が読み込まれて機能フラグが適用され、次の様に IAM ポリシーのステートメントが統合されて出力されます。
"Statement": [ { "Action": [ "s3:GetObject", "s3:PutObject" ], "Effect": "Allow", "Resource": "arn:aws:s3:::my-bucket/*" } ],
この挙動の違いを知らないと、既存の CDK プロジェクトで Toolkit Library に移行する際に CDK アプリケーションの挙動が変わってしまうことがあるため、十分に注意してください。
デフォルトでcdk.context.json
を読み書きするかどうか
cdk.context.json
とは、合成(synthesize)中に AWS アカウントから取得した値をキャッシュしておくための格納ファイルになります。
L2 コンストラクトの持つ fromLookup
メソッド(実際はcontext
メソッドという)によって AWS 環境に AWS SDK によるアクセスが走り、CDK が自動でcdk.context.json
ファイルを生成し、値を書き込みます。また CDK 実行時にcdk.context.json
ファイルに該当の値がある場合は、SDK 実行が走らずその値が使用されます。
※cdk.context.json の詳細に関してはこちらの記事をご覧下さい。
例えば以下のような CDK コードがあったとします。
これは AWS 環境の KMS キーをfromLookup
で取得するコードです。
const key = kms.Key.fromLookup(this, 'Key', { aliasName: 'alias/dummy', returnDummyKeyOnMissing: true, }); new cdk.CfnOutput(this, 'IsLookupDummyOutput', { value: kms.Key.isLookupDummy(key).toString(), });
Toolkit コードは以下です。
const getCloudAssemblySource = async (toolkit: Toolkit): Promise<ICloudAssemblySource> => { return await toolkit.fromAssemblyBuilder( async (_props: AssemblyBuilderProps) => { const app = cdkApp(); const cloudAssembly = await app.synth(); return cloudAssembly; }, { outdir: path.resolve(__dirname, '../cdk.out'), }, ); };
この場合、つまりfromAssemblyBuilder
を使用した Toolkit による実行ではcdk.context.json
ファイルは読み書きされず、ファイルが生成されません。
cdk.context.json
を読み書きしたい場合、cdk.json
のケースと同じく、fromAssemblyBuilder
もしくはfromCdkApp
の第 2 引数(props
)の contextStore
に CdkAppMultiContext
クラスのオブジェクトを指定することで実現できます。
const getCloudAssemblySource = async (toolkit: Toolkit): Promise<ICloudAssemblySource> => { return await toolkit.fromAssemblyBuilder( async (_props: AssemblyBuilderProps) => { const app = cdkApp(); const cloudAssembly = await app.synth(); return cloudAssembly; }, { outdir: path.resolve(__dirname, '../cdk.out'), contextStore: new CdkAppMultiContext(path.resolve(__dirname, '..')), }, ); };
するとcdk.context.json
ファイルが生成され、以下の様な値が書き込まれます。また次回実行時、この値がキャッシュとして読み込まれます。
{ "key-provider:account=123456789012:aliasName=alias/dummy:region=us-east-1": { "keyId": "1234abcd-12ab-34cd-56ef-1234567890ab" } }
※先ほど説明したように、fromCdkApp
ではデフォルトでcontextStore
にCdkAppMultiContext
が指定されるため、何も指定しなくてもcdk.context.json
は読み書きされます。
最後に
CDK Toolkit Library はシェルコマンドを使わず、プログラムに CDK の処理を埋め込むことで CDK のユースケースが広がる画期的なライブラリです。
しかし GA されたばかりでまだ世には情報も少なく、CDK CLI と挙動が違う点があるため、利用する際には注意が必要です。