はじめに
AWS CDKでインフラをコード管理(IaC)していると、手動やCLIで先に作成したリソースとCDKの管理対象が衝突するケースがあります。
本記事では、CDKデプロイ時にS3バケットの重複エラーが発生した場合の原因と、データを保持したまま解決する方法を紹介します。
問題
CDKでS3バケットを含むスタックをデプロイしたところ、以下のエラーが発生しました。
❌ my-project-cdk-stack-s3 failed: ToolkitError: ChangeSet 'cdk-deploy-change-set' on stack 'my-project-cdk-stack-s3' failed early validation:
- Resource of type 'AWS::S3::Bucket' with identifier 'my-project-bucket-data' already exists.
同名のS3バケットがすでにAWSアカウント上に存在しているため、CDK(CloudFormation)が新規作成しようとして失敗しています。
さらに、既存バケットにはデータが格納されているため、単純に削除して作り直すわけにもいきません。
原因
CDK(CloudFormation)は、スタック内で定義されたリソースを「新規作成」しようとします。しかし、以下のようなケースでは同名のリソースがすでに存在するため衝突が起きます。
- 過去にAWSコンソールやCLIで手動作成したバケットと同じ名前をCDKで定義した
- 以前のCDKスタックを削除した際に
RemovalPolicy.RETAINによりバケットだけが残った - 別のCloudFormationスタックで同名バケットが管理されている
CloudFormationは「自分が管理していないリソース」を認識できないため、作成時に重複エラーとなります。
解決案
方法A: データを一時退避してバケットを再作成する
最もシンプルな方法です。一時バケットにデータを退避し、既存バケットを削除してからCDKで再作成します。
# 1. 一時バケット作成
aws s3 mb s3://my-project-bucket-data-tmp --region ap-northeast-1
# 2. データ退避
aws s3 sync s3://my-project-bucket-data \
s3://my-project-bucket-data-tmp
# 3. 既存バケットを空にして削除
aws s3 rm s3://my-project-bucket-data --recursive
aws s3 rb s3://my-project-bucket-data
# 4. CDKデプロイ(同名バケットがCDK管理で再作成される)
cdk deploy my-project-cdk-stack-s3
# 5. データ復元
aws s3 sync s3://my-project-bucket-data-tmp \
s3://my-project-bucket-data
# 6. 一時バケット削除
aws s3 rm s3://my-project-bucket-data-tmp --recursive
aws s3 rb s3://my-project-bucket-data-tmp
メリット
- 手順がわかりやすい
デメリット
- データ量が多いと時間がかかる
- sync中に新たなデータが書き込まれると不整合が起きる可能性がある
- 一時的にバケットが存在しない期間が発生する
方法B: CloudFormationのリソースインポートで既存バケットを取り込む
CloudFormationの リソースインポート 機能を使うと、既存のバケットをデータごとそのままCDKスタックの管理下に入れることができます。
リソースインポートは2019年からリリースされている機能です。
https://aws.amazon.com/jp/blogs/news/new-import-existing-resources-into-a-cloudformation-stack/
# 1. CDKテンプレートを生成
cdk synth my-project-cdk-stack-s3 > template.yaml
# 2. インポート用チェンジセットを作成
aws cloudformation create-change-set \
--stack-name my-project-cdk-stack-s3 \
--change-set-name import-s3-bucket \
--change-set-type IMPORT \
--template-body file://template.yaml \
--resources-to-import "[{
\"ResourceType\": \"AWS::S3::Bucket\",
\"LogicalResourceId\": \"s3BucketDataXXXXXXXX\",
\"ResourceIdentifier\": {
\"BucketName\": \"my-project-bucket-data\"
}
}]"
LogicalResourceIdはcdk synthで出力されるテンプレート内の論理IDを確認して置き換えてください。
# 3. チェンジセットを実行
aws cloudformation execute-change-set \
--stack-name my-project-cdk-stack-s3 \
--change-set-name import-s3-bucket
# 4. インポート完了を確認
aws cloudformation describe-stacks \
--stack-name my-project-cdk-stack-s3 \
--query "Stacks[0].StackStatus"
メリット
- データ移動が不要
- ダウンタイムなし
- 事故のリスクが低い
デメリット
- CDKテンプレート内のバケット設定(暗号化、バージョニングなど)が既存バケットの実際の設定と一致している必要がある
- 論理IDの特定が必要(
cdk synthの出力から確認する)
CDKでもcdk importにより、他メソッドで作成した既存リソースをCDKを利用して管理できます。
https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/ref-cli-cmd-import.html
方法Bの注意点
インポート時にテンプレートの設定と既存リソースの設定に差異があると、次回の cdk deploy でドリフト修正が走り意図しない変更が入る可能性があります。事前に以下を確認しておくと安全です。
# 既存バケットの設定を確認
aws s3api get-bucket-versioning --bucket my-project-bucket-data
aws s3api get-bucket-encryption --bucket my-project-bucket-data
CDK側のS3定義を既存バケットの設定に合わせてからインポートすると、差分なくスムーズに管理下に入れられます。
まとめ
| 観点 | 方法A(データ退避) | 方法B(リソースインポート) |
|---|---|---|
| データ移動 | 必要 | 不要 |
| ダウンタイム | あり | なし |
| 手順の複雑さ | シンプル | やや複雑 |
| 安全性 | △(sync中の不整合リスク) | ◎ |
既存リソースにデータが入っている場合は、方法B(リソースインポート) を使うのが安全です。データ移動もダウンタイムも発生せず、既存バケットをそのままCDKの管理下に入れることができます。
CDKで既存リソースとの衝突に遭遇した際は、削除・再作成の前にまずインポートを検討してみてください。