こんにちは。ディベロップメントサービス3課の千本松です。
AWS CloudFormation(以下、CloudFormation)でデプロイ済みのスタックをAWS CDK(以下、CDK)に移行する機会があり、CDK Migrateを使った変換を試してみました。
CDK Migrateを使用することで、CloudFormationからCDKへの移行がスムーズに行えることが確認できました。
- CDK Migrateとは
- 主な検証のポイント
- 検証環境について
- 検証に使用したCloudFormationテンプレート(EC2スタック)
- 実際にCDK Migrateを試してみた
- まとめ
- 付録:検証に使用したCloudFormationテンプレート(VPCスタック)
CDK Migrateとは
CDK Migrateは、CloudFormationのスタックやテンプレートをCDKコードに変換するための機能であり、CDK内のコマンドとして提供されています
ただし、現状実験的な機能として提供されており、将来的に破壊的な変更が加わる可能性がある点に注意が必要です。(2025年7月時点)
The cdk migrate command is experimental and may have breaking changes in the future.
主な検証のポイント
- CDK Migrateの実行手順
- 値のインポートが、正しくCDKコードに変換されるか
- パラメータを使用した動的な設定が、正しくCDKコードに変換されるか
- Outputsセクションが、正しくCDKコードに変換されるか
検証環境について
今回の検証で使用した環境は以下の通りです。
- Bash(コマンド実行)
- AWS CDK v2.1020.2
- Python 3.13.5
- リージョン: ap-northeast-1
既存環境の構成
Amazon VPC(以下、VPC)とAmazon EC2(以下、EC2)を別スタックで管理している既存環境を想定しました。
今回は、EC2のスタックのみをCDKに変換します。
| スタック | 説明 | 値の受け渡し | 今回の操作 |
|---|---|---|---|
| Dev-VPC-Stack | ネットワーク関連のリソースを管理 | VPC IDやサブネットIDをパラメータで出力 | 何もしない |
| Dev-EC2-Stack | EC2インスタンスを管理 | VPCスタックの出力値をパラメータとして受け取り | CDKに変換 |
検証に使用したCloudFormationテンプレート(EC2スタック)
このテンプレートには、前述した以下のポイントが含まれています:
- VPCスタックでエクスポートしたVPC IDやサブネットIDを、インポートして使用
- EnvironmentNameパラメータを使用して、環境ごとにリソース名を変更
- インスタンスIDをOutputsセクションで出力
※ VPCスタックは記事の末尾に記載しています。
AWSTemplateFormatVersion: '2010-09-09' Description: 'EC2 Instance Stack that depends on VPC Stack' Parameters: EnvironmentName: Description: Environment name prefix Type: String Default: 'Dev' Resources: SecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Security group for EC2 instance VpcId: !ImportValue Fn::Sub: ${EnvironmentName}-VPC-ID Tags: - Key: Name Value: !Sub ${EnvironmentName}-EC2-SecurityGroup EC2Instance: Type: AWS::EC2::Instance Properties: ImageId: ami-xxxxxxxxxxxxxxxxx # Amazon Linux 2023 AMI (最新版) InstanceType: t3.micro SubnetId: !ImportValue Fn::Sub: ${EnvironmentName}-PublicSubnet1-ID SecurityGroupIds: - !Ref SecurityGroup Tags: - Key: Name Value: !Sub ${EnvironmentName}-EC2-Instance - Key: Environment Value: !Ref EnvironmentName Outputs: InstanceId: Description: EC2 Instance ID Value: !Ref EC2Instance Export: Name: !Sub ${EnvironmentName}-EC2-Instance-ID
実際にCDK Migrateを試してみた
ここからが本題です。
以下のコマンドを実行して、デプロイ済みのEC2スタックをCDKコードに変換します。
cdk migrate --from-stack --stack-name Dev-EC2-Stack --language python --region ap-northeast-1
実行してみると、すぐにCDKプロジェクトが生成され、変換されたコードが出力されました!
生成されたプロジェクト構造
以下のようなCDKプロジェクトが生成されました:
Dev-EC2-Stack/
├── app.py # メインアプリケーションファイル
├── cdk.json # CDK設定ファイル
├── migrate.json # CDK Migrate設定ファイル
├── requirements.txt # Python依存関係
├── requirements-dev.txt # 開発用依存関係
├── README.md # プロジェクト説明
├── source.bat # Windows用の仮想環境起動スクリプト
├── .venv/ # Python仮想環境(自動作成)
├── .gitignore # Git無視ファイル
├── dev_ec2_stack/ # スタック定義ディレクトリ
│ └── dev_ec2_stack_stack.py
└── tests/ # テストファイル
└── unit/
生成されたCDKコードの確認
生成されたコードは以下のようになりました(コード内コメントを一部変更しています)。
app.py(メインアプリケーション)
#!/usr/bin/env python3 import os import aws_cdk as cdk from dev_ec2_stack.dev_ec2_stack_stack import DevEc2StackStack app = cdk.App() DevEc2StackStack(app, "Dev-EC2-Stack") app.synth()
dev_ec2_stack_stack.py(スタック定義)
from aws_cdk import Stack import aws_cdk as cdk import aws_cdk.aws_ec2 as ec2 from constructs import Construct """ EC2 Instance Stack that depends on VPC Stack """ class DevEc2StackStack(Stack): def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) # パラメータの定義 props = { 'environmentName': kwargs.get('environmentName', 'Dev'), } # セキュリティグループの定義 securityGroup = ec2.CfnSecurityGroup(self, 'SecurityGroup', group_description = 'Security group for EC2 instance', vpc_id = cdk.Fn.import_value(f"""{props['environmentName']}-VPC-ID"""), tags = [ { 'key': 'Name', 'value': f"""{props['environmentName']}-EC2-SecurityGroup""", }, ], ) # EC2インスタンスの定義 ec2Instance = ec2.CfnInstance(self, 'EC2Instance', image_id = 'ami-xxxxxxxxxxxxxxxxx', # Amazon Linux 2023 AMI (最新版) instance_type = 't3.micro', subnet_id = cdk.Fn.import_value(f"""{props['environmentName']}-PublicSubnet1-ID"""), security_group_ids = [ securityGroup.ref, ], tags = [ { 'key': 'Name', 'value': f"""{props['environmentName']}-EC2-Instance""", }, { 'key': 'Environment', 'value': props['environmentName'], }, ], ) # Outputs """ EC2 Instance ID """ self.instance_id = ec2Instance.ref cdk.CfnOutput(self, 'CfnOutputInstanceId', key = 'InstanceId', description = 'EC2 Instance ID', export_name = f"""{props['environmentName']}-EC2-Instance-ID""", value = str(self.instance_id), )
検証ポイントの確認
検証のポイントとして挙げた箇所は、それぞれ以下のように変換されました。
| 検証ポイント | 結果 | 変換方法 | 備考 |
|---|---|---|---|
| VPC IDやサブネットIDのインポート | 〇 | cdk.Fn.import_value に変換 |
|
| 環境ごとのリソース名変更 | △ | f"{props['environmentName']}-EC2-Instance" のようなf-stringに変換 |
値を外部から直接渡すにはコード修正が必要 |
| インスタンスIDのOutputsセクション出力 | 〇 | cdk.CfnOutput に変換 |
既存スタックとの差分を確認
以下のコマンドを実行して変更セットを生成し、変換されたCDKコードと既存のCloudFormationスタックとの差分を確認します。
# 生成されたプロジェクトディレクトリに移動 cd Dev-EC2-Stack # 仮想環境を有効化(CDK Migrateで自動作成済み) source .venv/bin/activate # 依存関係のインストール pip install -r requirements.txt # 変更セットを生成 cdk deploy --region ap-northeast-1 --no-execute
変更セットの内容
以下のような変更セットが生成されました:

既存リソースであるEC2インスタンスやセキュリティグループは、置換の項目がFalseになっており、既存のリソースをそのまま利用できることが確認できます。
また、AWS::CDK::Metadataのリソースが追加されます。
変更内容の詳細
EC2インスタンスのプロパティレベルの変更は以下の通りです:

メタデータとして"aws:cdk:path"が追加されていますが、リソースの設定値に変更はありませんでした。
セキュリティグループのプロパティも同様に、設定値に変更はありませんでした。
まとめ
CDK Migrateを使用することで、CloudFormationからCDKへの移行をスムーズに行えることが確認できました。
これからCloudFormationからCDKへの移行を検討されている方は、まずCDK Migrateを試してみることをお勧めします。
今後の記事では、生成したCDKコードを改良して差分を最小限に抑える方法や、パラメータを外部から渡す方法について深堀りしていく予定です。
最後までお読みいただきありがとうございました!
本ブログが誰かの参考になれば幸いです。
付録:検証に使用したCloudFormationテンプレート(VPCスタック)
AWSTemplateFormatVersion: '2010-09-09' Description: 'VPC Stack with public subnets' Parameters: EnvironmentName: Description: Environment name prefix Type: String Default: 'Dev' Resources: VPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 EnableDnsHostnames: true EnableDnsSupport: true Tags: - Key: Name Value: !Sub ${EnvironmentName}-VPC InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub ${EnvironmentName}-IGW InternetGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: !Ref InternetGateway VpcId: !Ref VPC PublicSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 0, !GetAZs '' ] CidrBlock: 10.0.1.0/24 MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub ${EnvironmentName}-Public-Subnet-AZ1 PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${EnvironmentName}-Public-Routes DefaultPublicRoute: Type: AWS::EC2::Route DependsOn: InternetGatewayAttachment Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PublicSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnet1 Outputs: VpcId: Description: VPC ID Value: !Ref VPC Export: Name: !Sub ${EnvironmentName}-VPC-ID PublicSubnet1Id: Description: Public subnet ID in AZ1 Value: !Ref PublicSubnet1 Export: Name: !Sub ${EnvironmentName}-PublicSubnet1-ID