はじめに
CDK Pipelines で、東京リージョン(ap-northeast-1)のパイプラインから バージニア北部(us-east-1) にデプロイする構成を組みました。
別リージョンに出すなら、関係するアカウントの us-east-1 は全部 bootstrap するんだ!と思い込んでいて、PR レビューで「パイプラインアカウントの us-east-1 も bootstrap すべき」と指摘してしまいました。
しかし、実際に流してみたら、未 bootstrap のままでもパイプラインが最後まで通ることが確認できました。
今回はなぜ、未 bootstrap のままでもパイプラインが完走できたのかを説明します。
結論
冒頭の話を整理すると、この構成の us-east-1 側で bootstrap が効いているのは デプロイ先アカウント × us-east-1 の1箇所 です。
パイプラインアカウント側の us-east-1 に作られる中継用のサポートスタックはアセットを持たないので、bootstrap が無くても作成できます。
つまり、 bootstrap が必要なのは「アセットを置く先」だけ
これが今回の結論です。
用語
この記事では、紛らわしい3つを次の意味で使い分けます。
| 用語 | 意味 | 作る主体 | 中身(S3 バケット) |
|---|---|---|---|
| CDKToolkit |
cdk bootstrap が作る CloudFormation スタック。アカウント×リージョンごとに1つ |
人が cdk bootstrap
|
アセットバケット cdk-hnb659fds-assets-...(+ ECR / IAM ロール / SSM) |
| サポートスタック | クロスリージョン時に CDK が自動生成する <PipelineStack>-support-<region>
|
CDK が自動 | レプリケーションバケット(アーティファクト中継用・+ KMS) |
| アセット | Lambda コードや Docker イメージなど、テンプレート外で配る実ファイル | 本命スタックが参照 | 置き場は CDKToolkit のアセットバケット(サポートスタック側ではない) |
| 本命スタック | 実際にデプロイしたいアプリのスタック | 自分で定義 | アセットを CDKToolkit のアセットバケットから参照する |
図の色が緑のサポートスタックは 未 bootstrap でも作成できた箇所で、赤の CDKToolkit / 本命スタックは bootstrap が必須な箇所です。
CDKToolkit は cdk bootstrap が、サポートスタックは CDK が自動で作ります。
前提構成
検証したのは、現行の aws-cdk-lib/pipelines の CodePipeline を使ったクロスリージョン・クロスアカウント構成で、具体的にはap-northeast-1のパイプラインから別アカウントの us-east-1 へデプロイします。
| 項目 | 値 |
|---|---|
| パイプライン |
<pipeline-account> × ap-northeast-1(bootstrap 済み) |
| デプロイ先 |
<deploy-target-account> × us-east-1 |
| パイプライン設定 |
selfMutation: true / crossAccountKeys: true
|
| 本命スタック | Lambda アセットを含むスタック(<AppStack>) |
| バージョン | aws-cdk-lib 2.238.0 / aws-cdk 2.1025.0 / Node.js 18.20.8 |
| 検証日 | 2026-10-01 |
表の aws-cdk 2.1025.0 は手元で synth / bootstrap した端末の CLI バージョンです。この記事で引用するログはセルフミューテーション(パイプラインが CodeBuild 上で自分を更新するステージ)のもので、cliVersion を明示していないため、CodeBuild 側では別バージョンの CLI が動いている可能性があります。
bootstrap 要否の非対称という結論はバージョンに依存しませんが、ログの細部を厳密に再現したい場合は CodeBuild の CLI バージョンを別途固定してください。
本命スタックの中身はたまたま Transit Gateway まわりでしたが、この記事の話には関係しません。
効いてくるのは「Lambda のコードという S3 に上げるファイル(CDK ではこれをアセットと呼びます)を持っているか」だけです。
クロスリージョンが発生しているかは cdk list で分かります。synth だけで出るので、デプロイ前に確認できます。
npx cdk list --profile <profile>
# cross-region-stack-<pipeline-account>:us-east-1 が出ていればクロスリージョン構成
ネット上の古い記事は、旧 API の CdkPipeline(@aws-cdk/pipelines)を前提にしていることがあります。bootstrap の説明が食い違う記事を見つけたら、まず API の世代を疑ってください。
パイプラインアカウントの us-east-1 は未 bootstrap でもサポートスタックが作れた
パイプラインが別リージョンにデプロイする場合、CodePipeline はアーティファクト(ビルド成果物やテンプレート)をそのリージョンのバケットへ複製してから使います。
CDK はその受け皿を自動で用意し、<PipelineStack>-support-us-east-1 というスタックとしてパイプラインアカウントの us-east-1 にデプロイします。これがクロスリージョンのサポートスタックです。
作られたリソースは次の4つだけでした。
-
AWS::KMS::Key(CrossRegionCodePipelineReplicationBucketEncryptionKey) AWS::KMS::Alias-
AWS::S3::Bucket(CrossRegionCodePipelineReplicationBucket) AWS::S3::BucketPolicy
どれもテンプレートに直接書かれていて、Lambda のコードのように CDKToolkit のアセットバケットへ上げるファイル(アセット)を参照していません。
そのため、このスタックは CDKToolkit を必要とせず、パイプラインアカウントの us-east-1 は未 bootstrap のままでも CREATE_COMPLETE になりました。
ただし、ログは一見すると失敗しているように見えます。セルフミューテーション(パイプラインが自分自身の定義を更新するステージ)のログには、次の行が出ていました。
Stack CDKToolkit does not exist
The environment aws://<pipeline-account>/us-east-1 doesn't have the CDK toolkit stack (CDKToolkit) installed.
Error determining cross account asset publishing: ToolkitStackInfoError ...
Defaulting to allowing cross account asset publishing
...
✅ cross-region-stack-<pipeline-account>:us-east-1 (<PipelineStack>-support-us-east-1)
この CDKToolkit does not exist は「パイプラインアカウントの us-east-1 に CDKToolkit(bootstrap スタック)が無い」という意味です。
Error と ToolkitStackInfoError が目に入った時点で「bootstrap が無いから落ちた」と読みたくなります。
ところが次の行で Defaulting to allowing cross account asset publishing と既定動作に切り替わり、最終行は ✅ です。サポートスタックはアセットを持たず CDKToolkit を要しないので、その不在は問題になりませんでした。パイプラインも成功しました。
結局、フォールバックの行と最後の ✅ / ❌ まで読めば判断できました。
これはたまたま通ったのではなく、設計どおりの挙動です。
サポートスタックは DefaultStackSynthesizer ではなく BootstraplessSynthesizer で合成され、別リージョン用のロールを再利用しつつアセットをサポートしません(公式 API リファレンスより。要約。Content was rephrased for compliance with licensing restrictions)。
IAM はグローバルなので東京側 CDKToolkit のロールを us-east-1 で使い回せ、アセットを持たないサポートスタックは us-east-1 の CDKToolkit を必要としません。
本当に必須なのはデプロイ先アカウント × us-east-1 の bootstrap
一方で、本命スタックのほうは事情が違います。
CDK Pipelines は、Assets ステージで Lambda のコードをデプロイ先環境の CDKToolkit のアセットバケット(既定名 cdk-hnb659fds-assets-<ACCOUNT>-<REGION>)に上げます。
続く Deploy ステージでは、デプロイ先の deploy-role を引き受けて CloudFormation を動かします。
置き場所も実行ロールも、デプロイ先アカウント × us-east-1 の bootstrap(=CDKToolkit)が作るものです。サポートスタックのレプリケーションバケットとは別の S3 バケットです。
今回はここを事前に bootstrap してあったので、本命スタックは CREATE_COMPLETE になりました。使ったコマンドは次のとおりです。
npx cdk bootstrap aws://<deploy-target-account>/us-east-1 \
--trust <pipeline-account> \
--cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess \
--profile <deploy-target-profile>
--trust には、この環境へのデプロイを許すアカウント、つまりパイプラインアカウントの ID を渡します。qualifier はデフォルトの hnb659fds のまま、東京側と揃えています。
bootstrap が無い場合に何が起きるかは、今回は実際には起こしていません。公式ドキュメントによれば、未 bootstrap の環境へデプロイすると SSM parameter /cdk-bootstrap/hnb659fds/version not found. Has the environment been bootstrapped? で失敗します。bootstrap 済みかどうかは、この SSM パラメータの有無で判定されています。
もう一点、手元の cdk synth や cdk deploy が通っても、パイプラインが成功するとは限りません。
手元の実行には、CodePipeline がアーティファクトを別リージョンへ複製する中継ステージ(サポートスタックのレプリケーションバケット)がそもそも登場せず、クロスリージョン固有の問題はこの中継経路で初めて現れるので、手元では再現できません。
bootstrap 対象ごとの整理
| bootstrap 対象 | 必須か | 無いと何が起きるか | どの段で露見するか |
|---|---|---|---|
| パイプラインアカウント × ap-northeast-1 | 必須 | パイプライン自体を作れない | 初回のパイプラインデプロイ |
| パイプラインアカウント × us-east-1 | 今回は不要 | エラー行(CDKToolkit does not exist / ToolkitStackInfoError)が出るが既定動作にフォールバックし、サポートスタックは作成された(実測) |
露見しない |
| デプロイ先アカウント × us-east-1 | 必須 | アセットを持つ本命スタックが /cdk-bootstrap/hnb659fds/version not found 系で落ちる(公式仕様。今回は未発生) |
Assets / Deploy ステージ(サポートスタック段では出ない) |
デプロイ先の bootstrap では、合わせて --trust <パイプラインアカウント> の指定も必要です。これが抜けると bootstrap 自体は通っても、パイプラインがデプロイ先の deploy-role を引き受けられず、Deploy ステージで assume-role に失敗します(今回は未発生)。
3行目は、サポートスタックが緑になった後で初めて問題として現れます。
サポートスタックの ✅ が保証するのは「中継の箱」まで
サポートスタックが CREATE_COMPLETE になって分かるのは、us-east-1 にアーティファクトの受け皿ができたことだけですが、本命スタックのデプロイ可否は別の問題です。
最後は CloudFormation コンソールで、本命スタックが us-east-1 で CREATE_COMPLETE になったかを見ます。
サポートスタックが未 bootstrap でも作れるのは、それが BootstraplessSynthesizer で合成されアセットを持たないという現行の設計に依存しています。
将来 CDK がサポートスタックにアセット(あるいは CDKToolkit のアセットバケットを要する別のクロスリージョン補助リソース)を含める構成になれば、パイプラインアカウント側の bootstrap も必要になりえます。
「クロスリージョンだから常に不要」ではなく「アセットを持たないから不要」と理解しておくのが安全です。