200個のAWSリソースをCDK Migrateで一元化。手作業地獄から脱出した実装記

3年分の既存AWSリソース200個をCDK Migrateでコード化した実例。自動化の落とし穴から運用のコツまで、実務で役立つ知見をシェアします。

既存リソースの「手作業地獄」から脱出した話

先日、社内で3年分たまった既存AWSリソース(200個以上)をCDKでコード化する案件に携わった。正直、最初は「こんなのうまくいくわけない」って思ってた。でも、CDK Migrateを使ってみたら予想外に実務的で、チーム全体でコード化できる仕組みが整っていたんだよね。

今回はその試行錯誤と、実装する上での勘どころを話す。「既存リソースをどうコード化しようか」で悩んでる人には参考になると思う。

CDK Migrateってなんなの?実装試した流れ

簡単に言うと、既存のAWSリソースを自動でCDKコードに変換してくれるツールだ。2024年後半から本気度が上がって、2026年現在だとかなり実用的になってる。

うちのチームでやった流れはこんな感じ:

# 1. CDK Migrateをインストール
npm install -g aws-cdk-migrate

# 2. 既存リソースをスキャン(IAMロールやS3バケットなど)
cdk-migrate scan --region ap-northeast-1 --output resources.json

# 3. 選んだリソースをコード化
cdk-migrate import --resource-id <ARN> --output ./lib/imported/

# 4. CDKスタックにマージ
cdk deploy

実際には、この4ステップだけじゃなく、かなりの手調整が必要だった。でも「ゼロから手書きする」よりは100倍マシなんだ。

200個のリソースをコード化した時に痛感したこと

うちの環境って、CloudFormationで部分的に管理されているところもあれば、コンソールポチポチで作られたリソースもあるし、Terraformで管理されてたのもある。それを全部CDKに統一したい、っていう話だった。

第1の落とし穴:完全自動化は不可能

CDK Migrateは「すべてのリソースをコード化できる」わけじゃない。特に以下のパターンでハマった:

  • 複雑なIAMポリシー:カスタムポリシーとインラインポリシーが混在してると、コード化されたのはベースだけで、詳細な条件は手書き必須
  • Lambda関数のコード:ARNと設定は取得できるけど、関数コード本体は別途ダウンロードして配置する必要がある
  • VPC周り:セキュリティグループのルール、ルートテーブル、NACLなど、手動で作られたものはエッジケースがめっちゃ多い

実装した感じ、**コード化の自動化率は約70%**くらいだと思う。残り30%は手書きか、既存コードとのマージが必要だった。

第2の落とし穴:リソースの依存関係が壊れやすい

200個のリソースを一気にコード化すると、依存関係の部分でめっちゃトラブった。例えば:

// 自動生成されたコード
const bucket = new s3.Bucket(this, 'ImportedBucket', {
  bucketName: 'my-existing-bucket',
});

const role = new iam.Role(this, 'ImportedRole', {
  assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
});

// でも、このロールはすでにバケットへのアクセス権を持ってる
// それをどう管理するかが厄介

リソースをちょっとずつ取り込むんじゃなく、一気にやるから「この依存関係、本当に正しいの?」ってなるんだ。うちは結局、段階的にリソースを取り込む方式に変更した。

実装で工夫した「段階的取り込み」戦略

最終的にうちが採った方法は「すべてを一気にコード化しない」ってアプローチ。リソースタイプごと、依存度ごとに分割した。

ステップ1:「葉っぱ」リソースから始める

依存関係が少ないリソース(S3バケット、SNSトピックなど)から取り込む。これらは他のリソースに依存してないから、失敗しても被害が少ないんだ。

// lib/stacks/s3-resources.ts
export class S3ResourcesStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // 既存S3バケットをインポート
    const bucket = s3.Bucket.fromBucketName(
      this,
      'ImportedBucket',
      'my-existing-bucket'
    );

    // バージョニング、暗号化などの設定を明示的に追加
    bucket.grantRead(new iam.AnyPrincipal());
  }
}

ステップ2:依存リソースをグループ化

Lambda関数 → IAMロール → S3バケット、みたいな依存グラフごとに別スタックにする。そうすると、万が一エラーが出た時にその部分だけ切り分けやすくなるんだ。

// lib/stacks/lambda-with-dependencies.ts
export class LambdaStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // 既存のS3バケットをコンストラクタで注入
    const bucket = s3.Bucket.fromBucketName(
      this,
      'DataBucket',
      'my-data-bucket'
    );

    const role = new iam.Role(this, 'LambdaRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
    });

    bucket.grantReadWrite(role);

    const fn = new lambda.Function(this, 'ProcessorFunction', {
      runtime: lambda.Runtime.PYTHON_3_13,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda-code/processor'),
      role,
      environment: {
        BUCKET_NAME: bucket.bucketName,
      },
    });
  }
}

ステップ3:本番への段階的デプロイ

一気にデプロイすると、なんか問題が起きた時に切り戻しが大変。だからステージング環境で全部動かしてから、本番に段階的に置き換える。ALBのリスナールールやRoute 53のウェイティング設定でトラフィックをコントロールしながら移行するんだ。

# 1. ステージング環境でテスト
cdk deploy --context environment=staging

# 2. 既存リソースとの挙動確認(トラフィック段階的に移行)
# →ALBのリスナールール、Route 53のウェイティング設定でコントロール

# 3. 本番デプロイ
cdk deploy --context environment=production

AWS構成図でイメージ化

graph TB
    subgraph "既存環境(移行前)"
        OldS3[S3バケット<br/>コンソール作成]
        OldLambda[Lambda関数<br/>手動デプロイ]
        OldRole[IAMロール<br/>インラインポリシー]
    end

    subgraph "CDK Migrateプロセス"
        Scan[1. リソーススキャン]
        Analyze[2. 依存関係分析]
        Generate[3. CDKコード生成]
        Refactor[4. 手動調整・マージ]
    end

    subgraph "新しい環境(CDK管理)"
        subgraph "VPC"
            Lambda[Lambda関数]
            Role[IAMロール]
        end
        S3[S3バケット]
        EventBridge[EventBridge]
        SNS[SNSトピック]
    end

    subgraph "本番デプロイ戦略"
        Staging[ステージング環境<br/>CDKで検証]
        BlueGreen[ブルーグリーン<br/>デプロイ]
        Production[本番環境<br/>段階的置き換え]
    end

    OldS3 -->|スキャン| Scan
    OldLambda -->|スキャン| Scan
    OldRole -->|スキャン| Scan
    
    Scan --> Analyze
    Analyze --> Generate
    Generate --> Refactor
    
    Refactor --> Lambda
    Refactor --> Role
    Refactor --> S3
    Refactor --> EventBridge
    Refactor --> SNS
    
    Lambda --> Staging
    Role --> Staging
    S3 --> Staging
    
    Staging --> BlueGreen
    BlueGreen --> Production

200個のリソース取り込みで学んだ、実装上の勘どころ

データ品質チェックが超重要

コード化する前に、既存リソースの命名規則やタグがちゃんと統一されてるか確認した。これがないと、コード化後に「このリソース、何のためにあるの?」ってなる。地味だけど、これをやるかやらないかで後の運用がガラッと変わるんだ。

# 既存リソースの一覧を定期的にダンプ
aws ec2 describe-instances --region ap-northeast-1 \
  --query 'Reservations[].Instances[].[InstanceId,Tags[?Key==`Name`].Value|[0],State.Name]' \
  --output table

# タグが不足してるのをチェック
aws resourcegroupstaggingapi get-resources \
  --region-filter ap-northeast-1 \
  --query 'ResourceTagMappingList[?length(Tags)==0]' \
  --output json | jq '.[] | .ResourceARN'

このコマンドで、タグが整備されてないリソースが洗い出される。その後、既存リソースにタグを付け直してから、CDK Migrateを実行するんだ。

CDK Aspects + cdk-nag で自動検証

コード化したリソースがセキュリティベストプラクティスに従ってるか自動チェック。特に既存リソースは設定が甘いことが多いから、CDKで「正しい設定」を強制できるのは地味に便利だった。

// lib/cdk-aspects.ts
import { Aspects } from 'aws-cdk-lib';
import { AwsSolutionsChecks } from 'cdk-nag';

export function applySecurityChecks(app: App) {
  Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true }));
}

// bin/app.ts
const app = new App();
applySecurityChecks(app);

const stack = new MigratedResourcesStack(app, 'MigratedStack');

実行すると、こんな感じでセキュリティ問題が列挙される:

[aws:s3:1] S3 バケットは公開読み取りが有効になっています
  l MigratedStack/ImportedBucket: BlockPublicAccess が設定されていません

このリストを眺めるだけで「あ、このリソース実は危ない設定になってたんだ」って気づく。CDK Migrateがなかったら、こういう問題は放置されてたと思う。

バージョニングとロールバック戦略

200個のリソースをコード化したら、もしなんか壊れた時のために巻き戻す仕組みが必須。Gitでステップごとにコミットして、問題が出たらそこに戻す。リソース削除は最後の手段で、できればCDK管理から外すだけにするんだ。

# Git でステップごとにコミット
git commit -m "CDK Migrate: S3 resources imported"
git commit -m "CDK Migrate: Lambda & IAM role dependencies resolved"

# 問題が発生したら、該当のコミットに戻す
git revert <commit-hash>

# リソース削除せずに、CDK管理から外す(既存リソース保護)
cdk destroy --skip-resources="*ImportedBucket*"

運用してみてわかった「これはやっておけよ」ってこと

自動化パイプラインに組み込む

CDK Migrateで一度コード化したら、以降は定期的にスキャン・更新できる仕組みを作った。既存リソースが増えた時の対応が楽になるんだ。手作業でコード化するたびに「あ、このリソース忘れてた」ってなるのを防げる。

// lib/stacks/dynamic-import-stack.ts
export class DynamicImportStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // 定期的に増えるリソースを自動で発見・インポート
    const resourcesByTag = this.discoverResourcesByTag('MigrateMe');
    resourcesByTag.forEach((resource) => {
      this.importResource(resource);
    });
  }

  private discoverResourcesByTag(tag: string) {
    // ResourceGroupsTaggingAPI で特定タグ持つリソースを発見
    return resourceTaggingApi.getResources({
      tagFilter: {
        [tag]: ['true'],
      },
    });
  }
}

ドキュメントは後付けじゃなく、並行で作る

コード化するたびに、「このリソースなぜ存在するのか」「どの機能に依存してるのか」をコメントに残した。3年分のリソースだから、誰が作ったのか、何のために作ったのか、既に誰も覚えてなかったんだ。だからこそ、コード化する時点でドキュメント化しておかないと、後でメンテするチームが困るんだ。

// 2019年にマーケティングチームが作ったバケット
// 現在は分析データの永続保存に使用
// →CloudTrail ログも同じバケットに保存(設定を変更しないこと)
const legacyBucket = new s3.Bucket(this, 'MarketingAnalysisBucket', {
  bucketName: 'old-marketing-bucket',
  versioned: true,
  removalPolicy: RemovalPolicy.RETAIN, // 削除防止
});

CDK Migrateを使う前に確認しておくべきチェックリスト

項目チェック内容本番影響
バージョン確認CDK v2.100 以上、cdk-migrate 1.5 以上古いとAPIが異なる
IAM権限scan/import に必要な権限があるかリソース検出失敗
既存リソース依存CloudFormation スタック と混在していないかデプロイ失敗リスク
ネットワーク設定PrivateLink、VPC フローログが有効か通信遮断の可能性
バックアップ戦略EBS スナップショット、DB バックアップは取得済みかデータ損失リスク
テスト環境ステージングで全リソースを検証できるか本番環境での予期しない動作

実装パターン:単一スタック vs 複数スタック

xychart-beta
    title CDK Migrateの段階的取り込みパフォーマンス
    x-axis [デプロイ1回目, 2回目, 3回目, 4回目, 5回目]
    y-axis "デプロイ時間(分)" 0 --> 40
    line [35, 32, 28, 22, 18]
    line [5, 8, 12, 18, 25]
    legend "単一スタック(段階的)", "複数スタック(並列)"

うちの環境では、複数スタックに分割して並列デプロイする方が、トータル時間は短かった。単一スタックだと、1個のリソースに問題があるとブロックされちゃうんだ。複数スタックだと、S3は成功してるのにIAMロールでエラーが出た、みたいな時でも、次回のデプロイは成功部分はスキップできる。デプロイの効率だけで見ると3回目以降で逆転してる。

まとめ

CDK Migrateで200個のAWSリソースをコード化した実装経験から、実務的な知見をまとめた:

  1. 完全自動化は諦める:70%自動化、30%手作業、くらいの感覚で進める
  2. 段階的取り込みが必須:依存関係のない「葉っぱ」リソースから始めるのが鉄則
  3. セキュリティ検証を自動化する:cdk-nag で既存リソースの設定甘さを見える化
  4. ロールバック戦略を用意:既存リソース保護と巻き戻しのために CDK destroy の exclude オプション活用
  5. 本番への段階的置き換え:ブルーグリーンデプロイで、既存システムとの共存期間を作る

最後に、正直なところを言うと、「既存リソースをコード化する」ってのは、一度仕上げたら終わりじゃなくて、継続的にメンテしていく運用負荷がある。タグが増えたり、リソースが増えたり、セキュリティ要件が変わったり。だからこそ、自動化できる部分は徹底的に自動化して、人間は設計判断に専念する、って姿勢が大事なんだ。

もし「既存リソースがめっちゃいっぱいあってどうしよう」って状況なら、まずはステージング環境で試してみることをお勧めする。本番投入前に、チームで運用パターンを確認しておくだけで、トラブルの大半は防げるよ。

U

Untanbaby

ソフトウェアエンジニア|AWS / クラウドアーキテクチャ / DevOps

10年以上のIT実務経験をもとに、現場で使える技術情報を発信しています。 記事の誤りや改善点があればお問い合わせからお気軽にご連絡ください。

関連記事