Use Cases (更新: 2026/6/7)

AWS CDK入門:CloudFormationとの違いと、Claude Codeで差分を読む手順

AWS CDK(TypeScript)とCloudFormation(YAML)の違いと使い分けを、synth→diff→deployの実例で解説。ドリフト検知とスタック分割、Claude Codeでの差分レビューまで。

AWS CDK入門:CloudFormationとの違いと、Claude Codeで差分を読む手順

「本番のS3、なんでバージョニング有効なんだっけ?」

去年、引き継いだAWSアカウントで僕がフリーズした瞬間です。コンソールをポチポチ直してきた環境は、設定の理由がどこにも残っていませんでした。検証環境と本番で値が違う。誰がいつ変えたかも分からない。設計書はExcelで、しかも半年前で更新が止まっていました。

このときに腹を括って入れたのが、IaC(Infrastructure as Code、インフラの設計書をそのまま実行できるコードにする考え方)です。AWSだと選択肢は実質ふたつ。CloudFormation(YAMLでテンプレートを書く)と、AWS CDK(TypeScriptなどのコードを書いてテンプレートに変換する)。

どっちを選ぶか、CDKでどう書いてどう安全にデプロイするか、そしてcdk deployで事故らないために何を見るか。実際に運用して分かったことを、僕の失敗込みで書きます。

この記事の要点

  • CloudFormation = YAMLの宣言CDK = TypeScriptのコード。CDKは内部でCloudFormationテンプレートを生成して動くので、土台は同じ。
  • 新規開発で型補完と再利用が欲しいならCDK、シンプルさと依存ゼロを優先するならCloudFormation。迷ったら新規はCDKでいい。
  • CDKの基本動線は cdk synth(テンプレ生成)→ cdk diff(差分確認)→ cdk deploy最初に押すのはdeployではなくsynthとdiff
  • 小さなコード変更がCloudFormation上では「作り直し(置換)」になることがある。diffの [~] と change set の Replacement を読まずにデプロイしない。
  • 手でコンソールを直すとドリフト(コードと実物のズレ)が出る。月1回でも detect-stack-drift を回すと事故の芽を早く摘める。

CloudFormationとCDKは何が違うのか

まず誤解を解いておきます。CloudFormationとCDKは「対立する別物」ではありません。CDKは、書いたTypeScriptコードを最終的にCloudFormationテンプレート(JSON/YAML)へ変換してから、CloudFormationに渡して実行します。つまり土台は同じで、書き方の入り口が違うだけです。

CloudFormationは、AWSが公式に持っているテンプレート実行サービスです。「このリソースをこういう設定で作って」という最終形をYAMLで宣言します。読めば全部がそこに書いてある、という素直さが強みです。

CDK(Cloud Development Kit)は、プログラミング言語でインフラを組み立てる開発キットです。if文で環境を分けたり、関数で共通設定をまとめたり、ループで似たリソースを量産したりできます。エディタの型補完が効くので、僕みたいに「S3のプロパティ名なんだっけ」と毎回ドキュメントを開く人間にはありがたい。

並べるとこうなります。

観点CloudFormation (YAML)AWS CDK (TypeScript)
書くもの最終形のテンプレートリソースを組み立てるコード
抽象化低い(全部明示)高い(既定値や共通化が効く)
学習コストYAMLが読めればすぐTypeScript+CDKの作法が必要
環境差分Parameters/Mappingsで分岐コードの条件分岐で自然に書ける
依存追加ツール不要Node.jsとnpmパッケージが必要
差分確認change setcdk diff(内部でchange set)
向いている場面小さく宣言的に管理したい規模が育つ、再利用したい新規開発

僕の使い分けはシンプルです。既存環境のごく一部をコード化するだけならCloudFormationのYAMLを1枚書く。これから育てる新規プロジェクトならCDK。理由は、規模が大きくなったときの再利用と分岐のしやすさが効いてくるからです。小さいうちはCDKの恩恵は薄いので、無理に使わなくていいと思っています。

Claude Codeはデプロイ係ではなくレビュー係にする

ここで先に立場を決めておきます。Claude CodeにIaCを触らせるとき、僕は「作って」より「レビューして」を強く意識します。

理由は単純で、生成タスクのときAIは「動くこと」を優先しがちだからです。Action: "*" のIAMポリシー、削除保護なしのDB、本番でうっかり有効なオブジェクト自動削除。便利な自動化の顔をして、これらは普通に事故ります。だから役割を切り分けます。

作業Claude Codeに任せる人間が確認する
要件整理リソース一覧、環境差分、依存関係の整理本当に必要なリソースか
テンプレ作成CloudFormation/CDKの初稿命名、削除保護、課金影響
IAM設計必要アクションの候補出しResourceConditionが狭いか
差分レビューcdk diffやchange setの要約削除・置換・権限拡大の承認
障害対応ログの読み解き、修正案ロールバック方針と本番反映

頼み方も「このCDKを本番に出していいか、置換・削除・IAM拡大・公開アクセス・月額コスト増だけを表で指摘して」のように観点を絞ると、ただの生成より実務に近い答えが返ってきます。最小権限の詰め方そのものはClaude Code × AWS IAMガイドに寄せたので、IAMで悩んでいる人はそちらも。

全体の流れはこうです。文章で要件を書く → CDK/YAMLの初稿を作る → リソース名とコストを人間が確認 → cdk diffかchange setを作る → 危険差分を説明させる → 承認後にdeploy → ドリフト検知とロールバック手順を記録する。

CloudFormationで既存設定をコード化する

いきなり大きなCDKアプリに全部移す必要はありません。まずは影響の小さいリソースを1枚のYAMLでコード化するのが堅実です。次の例は、外部に公開しないS3バケットと、そのバケットだけを読めるLambda用IAMロールを作ります。IAMロールを作るので、デプロイ時に CAPABILITY_NAMED_IAM を明示します。

AWSTemplateFormatVersion: "2010-09-09"
Description: "ClaudeCodeLab safe IaC review demo: private S3 bucket and least-privilege Lambda role"

Parameters:
  Environment:
    Type: String
    Default: dev
    AllowedValues:
      - dev
      - staging
      - prod
  ProjectName:
    Type: String
    Default: cclab-iac-demo
    AllowedPattern: "^[a-z0-9][a-z0-9-]{2,24}$"

Resources:
  DemoBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      BucketName: !Sub "${ProjectName}-${Environment}-${AWS::AccountId}-${AWS::Region}"
      VersioningConfiguration:
        Status: Enabled
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      Tags:
        - Key: Project
          Value: !Ref ProjectName
        - Key: Environment
          Value: !Ref Environment

  DemoReaderRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${ProjectName}-${Environment}-reader-${AWS::Region}"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: ReadOnlyDemoBucket
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Sid: ListOnlyThisBucket
                Effect: Allow
                Action:
                  - s3:ListBucket
                Resource: !GetAtt DemoBucket.Arn
              - Sid: ReadObjectsOnly
                Effect: Allow
                Action:
                  - s3:GetObject
                Resource: !Sub "${DemoBucket.Arn}/*"

Outputs:
  BucketName:
    Value: !Ref DemoBucket
  ReaderRoleArn:
    Value: !GetAtt DemoReaderRole.Arn

DeletionPolicy: RetainUpdateReplacePolicy: Retain を付けてあるのがポイントです。これがないと、スタック削除やリソース置き換えのときにバケットごとデータが消えます。実物のデータを持つリソースには、まずこの2行を付ける癖をつけるといいです。

デプロイの前に、いきなり本番へ流さず change set で中身を見ます。aws cloudformation deploy は内部でchange setを使いますが、本番では明示的に作って確認する方が安心です。

aws cloudformation create-change-set \
  --stack-name cclab-iac-demo-dev \
  --change-set-name review-20260607-001 \
  --template-body file://cfn-safe-iac-demo.yaml \
  --capabilities CAPABILITY_NAMED_IAM \
  --parameters ParameterKey=Environment,ParameterValue=dev ParameterKey=ProjectName,ParameterValue=cclab-iac-demo

aws cloudformation describe-change-set \
  --stack-name cclab-iac-demo-dev \
  --change-set-name review-20260607-001

ここで見る単語は AddModifyRemove、そして Replacement です。特に Replacement: True は「更新」ではなく「作り直す」サイン。S3やRDS、DynamoDBでこれを見落とすと、データ移行や停止時間の話になります。

CDKでスタックを書く:synthとdiffまで

ここからが本題のCDKです。新規ならこちらが扱いやすい。型補完が効くぶん、Claude Codeが出したコードを人間も読みやすく、レビューもしやすくなります。次の例は、低コストで試せるDynamoDB・S3・Lambdaの最小スタックです。Lambdaのコードはインラインなので、追加ファイルなしで cdk synth まで通ります。

まず足場を作ります。

mkdir cdk-iac-review-demo
cd cdk-iac-review-demo
npm init -y
npm install aws-cdk-lib constructs
npm install --save-dev aws-cdk typescript ts-node @types/node
mkdir bin lib

cdk.json にアプリの起動コマンドを書きます。

{
  "app": "npx ts-node --prefer-ts-exts bin/app.ts"
}

エントリポイント。ここでスタックを1つ生成しています。

// bin/app.ts
import * as cdk from "aws-cdk-lib";
import { IacReviewDemoStack } from "../lib/iac-review-demo-stack";

const app = new cdk.App();

new IacReviewDemoStack(app, "IacReviewDemoStack", {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: process.env.CDK_DEFAULT_REGION ?? "ap-northeast-1",
  },
});

スタック本体です。stage という値(コンテキスト)で本番か検証かを分け、削除保護や自動削除の挙動を切り替えています。同じコードで環境ごとに安全策の強さを変える、これがCDKのうれしさです。

// lib/iac-review-demo-stack.ts
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import * as iam from "aws-cdk-lib/aws-iam";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as logs from "aws-cdk-lib/aws-logs";
import * as s3 from "aws-cdk-lib/aws-s3";

export class IacReviewDemoStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // stage=prod のときだけ保護を強める
    const stage = this.node.tryGetContext("stage") ?? "dev";
    const isProd = stage === "prod";

    const table = new dynamodb.Table(this, "AppTable", {
      partitionKey: { name: "pk", type: dynamodb.AttributeType.STRING },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, // 使った分だけ課金
      pointInTimeRecovery: isProd,
      deletionProtection: isProd,
      removalPolicy: isProd ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
    });

    const bucket = new s3.Bucket(this, "PrivateAssetsBucket", {
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, // 公開を全面ブロック
      enforceSSL: true,
      encryption: s3.BucketEncryption.S3_MANAGED,
      versioned: true,
      removalPolicy: isProd ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: !isProd, // 検証だけ中身ごと削除を許す
    });

    const fn = new lambda.Function(this, "ApiHandler", {
      runtime: lambda.Runtime.NODEJS_22_X, // 2026年時点のLTS
      handler: "index.handler",
      code: lambda.Code.fromInline(`
exports.handler = async () => ({
  statusCode: 200,
  body: JSON.stringify({
    tableName: process.env.TABLE_NAME,
    bucketName: process.env.BUCKET_NAME
  })
});
`),
      environment: {
        TABLE_NAME: table.tableName,
        BUCKET_NAME: bucket.bucketName,
        STAGE: stage,
      },
      timeout: cdk.Duration.seconds(10),
      memorySize: 256,
      logRetention: logs.RetentionDays.ONE_WEEK,
    });

    // 必要な権限だけを付与(grant系は最小権限になりやすい)
    table.grantReadWriteData(fn);
    bucket.grantPut(fn);

    // SSL以外の通信は明示的に拒否する保険
    fn.addToRolePolicy(
      new iam.PolicyStatement({
        sid: "DenyInsecureS3Transport",
        effect: iam.Effect.DENY,
        actions: ["s3:*"],
        resources: [bucket.bucketArn, bucket.arnForObjects("*")],
        conditions: { Bool: { "aws:SecureTransport": "false" } },
      }),
    );

    cdk.Tags.of(this).add("Project", "ClaudeCodeLab");
    cdk.Tags.of(this).add("Stage", stage);

    new cdk.CfnOutput(this, "FunctionName", { value: fn.functionName });
    new cdk.CfnOutput(this, "BucketName", { value: bucket.bucketName });
    new cdk.CfnOutput(this, "TableName", { value: table.tableName });
  }
}

そして最初に走らせるのは deploy ではありません。synth でテンプレートを生成し、diff で差分を見ます。

# コードをCloudFormationテンプレートに変換して中身を確認
npx cdk synth -c stage=dev

# デプロイ済みスタックとの差分を見る
npx cdk diff -c stage=dev

cdk synth を一度通すと、「自分が書いたTypeScriptが、結局どんなCloudFormationになるのか」が目で見えます。ここで生成されたYAMLを眺める習慣は、CDKを理解するいちばんの近道でした。cdk diff は内部で読み取り専用のchange setを作り、置換が起きるかどうかまで含めて教えてくれます(--method=template を付けるとchange setを作らず速いが精度は落ちる)。

差分が出たら、そのままClaude Codeに渡してレビューさせます。

claude -p "
このcdk diffをレビューして。
削除・置換・IAM権限拡大・S3公開・DynamoDB課金・Lambdaの環境変数漏えいの観点だけで指摘して。
$(npx cdk diff -c stage=dev 2>&1)
"

期待するのはコードの称賛ではなく、赤信号の抽出です。たとえば autoDeleteObjects: !isProd は検証では便利でも本番で有効なら危険。bucket.grantPut(fn) は書き込み中心なので広すぎませんが、読み取りが必要になった瞬間に安易に grantReadWrite へ広げる前に、実際の用途を確かめるべきです。Lambda側の設計をもっと詰めたい人はAWS Lambda実装の勘所を、データ設計はDynamoDBの設計を合わせて読むと、スタック全体の観点がつながります。

本番の差分は「承認者の言葉」に翻訳させる

CDKの怖さは、TypeScriptの小さな変更がCloudFormation上では大きな置換になることです。開発者向けの差分をそのまま見せても、承認するプロダクト責任者には伝わりません。そこでClaude Codeに翻訳させます。

claude -p "
以下のcdk diffを、非AWS専門のプロダクト責任者にも分かる言葉で要約して。
出力は次の列にして:
- 変更種別
- 対象リソース
- ユーザー影響
- セキュリティ影響
- コスト影響
- 承認前に聞くべき質問

$(npx cdk diff -c stage=prod 2>&1)
"

本番レビューで僕が必ず見るのは、IAMポリシーの ActionResource、Security Groupの 0.0.0.0/0、RDSやDynamoDBの削除保護、S3の公開設定、そしてCloudFrontやNAT Gatewayの追加です。NAT Gatewayは検証環境でも固定費が出やすいので、「この構成で月額固定費が増えるリソースだけ挙げて」とClaude Codeに聞く価値があります。

スタック分割の考え方

リソースが増えてくると、ひとつの巨大スタックは扱いづらくなります。デプロイのたびに全部が対象になり、差分も読みにくい。変更の頻度とライフサイクルが近いものをまとめるのが分割の基本です。

僕の目安はこうです。

  • ネットワーク層(VPC、サブネット、NAT): めったに変えない。土台なので別スタック。
  • データ層(RDS、DynamoDB、S3): 消えると痛い。削除保護を効かせて独立させる。
  • アプリ層(Lambda、API Gateway、ECS): 毎日でも変わる。ここを身軽にしておく。

層をまたいで値を渡すときは、出力(CfnOutput)とインポートでつなぎます。ただし依存が増えすぎると「片方を消したいのに参照が残って消せない」状態になりがちなので、最初は2〜3スタックくらいから始めるのがちょうどいいです。APIの入口を別で設計するならAWS API Gatewayの実装ガイドも参考になります。

ドリフト検知とロールバックを定例化する

デプロイが失敗したとき、あわててコンソールで直すとドリフト(コードと実物のズレ)が生まれます。UPDATE_ROLLBACK_FAILEDROLLBACK_COMPLETE の扱いは、公式ドキュメントを見ながら慎重に進める領域です。状態の確認とドリフト検出はこのあたりのコマンドで回します。

aws cloudformation describe-stacks --stack-name cclab-iac-demo-dev
aws cloudformation detect-stack-drift --stack-name cclab-iac-demo-dev
aws cloudformation describe-stack-drift-detection-status \
  --stack-drift-detection-id YOUR_DETECTION_ID

障害時のClaude Codeへの依頼は、原因の推測と「触っていい/ダメ」の切り分けに限定します。

claude -p "
CloudFormationスタックがUPDATE_ROLLBACK_FAILEDになった。
以下のeventsを読み、原因候補・触ってよいリソース・触ってはいけないリソース・公式ドキュメントで確認すべき手順を分けて説明して。
$(aws cloudformation describe-stack-events --stack-name cclab-iac-demo-dev 2>&1)
"

大事なのは、Claude Codeが「コンソールで手動修正しましょう」と言ったとき、その修正を後で必ずIaCへ戻すことです。手動修正は緊急避難であって恒久対応ではありません。ここをサボると、せっかくコード化した意味が消えます。

僕がやらかした落とし穴4つ

正直に書きます。最初の数か月は事故とヒヤリの連続でした。

ひとつ目は、IAMを広げすぎたこと。Claude Codeが出した Action: "*" をそのまま通して、必要のない権限を本番ロールに付けていました。今はS3ならバケットARNとオブジェクトARNを分け、DynamoDBはテーブルARNに限定します。「必要な操作を、必要な対象にだけ」です。

ふたつ目は、差分を読まずに cdk deploy したこと。たった数文字のプロパティ変更が、CloudFormation上ではDynamoDBの作り直しになっていて、危うくデータを飛ばすところでした。名前付きリソース、暗号化設定、サブネット、DBエンジン、キー設定は特に慎重に見ます。

みっつ目は、検証環境のコストを軽視したこと。動作確認のために立てたNAT GatewayとRDSを消し忘れ、翌月の請求でうなりました。ALB、NAT Gateway、RDS、OpenSearch、常時稼働ECSは、使っていなくても費用が出ます。

よっつ目は、Secretsをテンプレートに直書きしたこと。APIキーをCDKコードに書いたらGit履歴に残り、消すのに苦労しました。今はSecrets ManagerやSSM Parameter Storeを使い、Claude Codeにも「秘密値は出力しない」と毎回明示しています。

よくある質問

Q. CloudFormationとCDK、結局どっちを使えばいい? A. これから育てる新規開発ならCDK、既存環境のごく一部をコード化するだけならCloudFormationのYAML1枚で十分です。CDKは規模が大きくなったときの再利用と分岐で効いてきます。小さいうちは無理に使わなくていいです。

Q. CDKはTypeScript以外でも書ける? A. 書けます。Python、Java、Go、C#にも対応しています。ただチームに型補完とエコシステムの厚みを求めるならTypeScriptが無難で、Claude Codeのレビューとも相性がいいです。

Q. cdk synthcdk diff は何が違う? A. synth はコードをCloudFormationテンプレートへ変換して中身を出します。diff はそのテンプレートと、すでにデプロイ済みのスタックを比べて差分を出します。デプロイ前は両方を見て、置換や削除が起きないか確認します。

Q. ドリフトはどのくらいの頻度で見ればいい? A. 最低でも月1回 detect-stack-drift を回すのをおすすめします。コンソールで手を入れたSecurity GroupやS3ポリシーは、次のデプロイで戻ったり差分に出なかったりするので、定例化しておくと事故の芽を早く摘めます。

Q. Claude Codeに直接 cdk deploy まで任せていい? A. 僕はやりません。生成・差分の説明まではAIに任せ、削除・置換・IAM拡大・本番反映は人間が承認する形にしています。AIはデプロイ担当ではなく、厳しい二人目のレビュアーとして使うのが一番実務的です。

実際に試した結果

引き継いだあのアカウントは、半年かけて主要リソースをCDKとCloudFormationに移しました。いちばん変わったのは、「なぜこの設定なのか」が全部コードとPull Requestに残るようになったことです。本番のS3がなぜバージョニング有効なのか、もう誰もフリーズしません。

そして体感で効果が大きかったのは、Claude Codeへの頼み方を「作って」から「最小権限・削除保護・コスト・ロールバック・ドリフトの観点でレビューして」に変えたことでした。小さなS3とIAMロールの例でも、change setで置換や削除を確認し、diffを説明させる流れを入れるだけで、レビューの会話が一気に具体的になります。

最初に押すのはdeployではなくsynthとdiff。危険な操作は人間が承認する。手動修正は必ずコードに戻す。この3つを守るだけで、IaCの導入はかなり安全になりました。チームでこの型を共有したい人は、実践教材にPull Requestテンプレート向けのチェックリストもまとめてあります。

公式の一次情報は、迷ったら必ずここに戻ります。

#claude-code #aws #cloudformation #cdk #iac #typescript
無料

無料PDF: Claude Code はじめてのチートシート

まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。

スパムは送りません。登録情報は厳重に管理します。

Claude Codeを仕事で使える形にしませんか?

まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。

Masa

この記事を書いた人

Masa

Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。