AWS CloudFormationスタックをAWS CDKで管理!CDK Migrateを使ってみた

記事タイトルとURLをコピーする

こんにちは。ディベロップメントサービス3課の千本松です。

AWS CloudFormation(以下、CloudFormation)でデプロイ済みのスタックをAWS CDK(以下、CDK)に移行する機会があり、CDK Migrateを使った変換を試してみました。

CDK Migrateを使用することで、CloudFormationからCDKへの移行がスムーズに行えることが確認できました。

CDK Migrateとは

CDK Migrateは、CloudFormationのスタックやテンプレートをCDKコードに変換するための機能であり、CDK内のコマンドとして提供されています

ただし、現状実験的な機能として提供されており、将来的に破壊的な変更が加わる可能性がある点に注意が必要です。(2025年7月時点)

The cdk migrate command is experimental and may have breaking changes in the future.

docs.aws.amazon.com

主な検証のポイント

  1. CDK Migrateの実行手順
  2. 値のインポートが、正しくCDKコードに変換されるか
  3. パラメータを使用した動的な設定が、正しくCDKコードに変換されるか
  4. 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のリソースが追加されます。

参考:AWS CDK 使用状況データレポートを設定する

変更内容の詳細

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

千本松 輝(記事一覧)

アプリケーションサービス部ディベロップメントサービス3課

CloudFormation・CDKなどIaCが好きです。AWS SAP資格勉強中。好物は生ガキと焼酎。