PR

AWS CDK実践ガイド:TypeScriptでインフラをコード化する現代的アプローチ【2026年版】

AWS CDK実践ガイド:TypeScriptでインフラをコード化する現代的アプローチ【2026年版】

CloudFormationのYAMLを500行書いて、それでも「ちょっとした変更」のたびにスタック全体の差分を追いかけていた時期がある。AWS CDKに乗り換えた最初の週、「なぜもっと早くやらなかったのか」と思った。同時に、移行直後に本番データベースを消してしまった。その両方の体験を正直に書く。


【Haruの実体験】CDK移行で70%速くなった話と、本番DBを消してしまった話

あるSaaS案件でECS Fargate + RDS + ALBの構成をCloudFormation(YAML)で書いた。3ファイル合計約800行、2週間かかった。typoによるデプロイ失敗が7回発生した。

同じ構成をCDKで書き直すと340行、4日で完成した。IDEの型チェックがデプロイ前のミスを拾ってくれ、デプロイ失敗ゼロだった。

ただし、移行後すぐに問題が起きた。cdk destroyを実行したところ、RemovalPolicyを設定していなかったRDSインスタンスが本番データごと削除された。スナップショットがあったため復旧できたが、6時間かかった。CDKはリソースの作成だけでなく削除も自動化する。このことを理解せずに使うと、自動化が「削除の自動化」として機能してしまう。


1. 2026年時点のAWS CDKの状況

CDK v2は2021年にGA、v1はEOL(2023年6月)。2026年現在はv2.170以降が最新系。

npm install aws-cdk-lib constructs
npm install -g aws-cdk
cdk --version  # 2.170.0

CDK Migrate(2024年GA): 既存CloudFormationテンプレートをCDKコードに変換するツール。完璧ではないが、YAML数百行をTypeScriptの叩き台に変換できる。

cdk migrate --stack-name my-existing-stack --language typescript

2. Constructsの3層を理解する

L1(CfnXxx): CloudFormationリソースに1:1対応。完全制御できるが冗長。
L2(通常のConstruct): AWSサービス1つを適切なデフォルトで定義。実務の中心。
L3(Pattern): 複数L2の組み合わせ。最速だが細かい設定が難しい。
// L3: 最速。ALB+ECS構成が1オブジェクトで完成
import { ApplicationLoadBalancedFargateService } from 'aws-cdk-lib/aws-ecs-patterns';
const service = new ApplicationLoadBalancedFargateService(this, 'ApiService', {
  cluster,
  taskImageOptions: { image: ecs.ContainerImage.fromRegistry('nginx'), containerPort: 80 },
  publicLoadBalancer: true,
});
// L2: 細かい設定が必要な場合(セキュリティグループ等)
const service = new ecs.FargateService(this, 'ApiService', {
  cluster,
  taskDefinition,
  desiredCount: 2,
  securityGroups: [appSg],
  vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
});

3. マルチ環境設定の実装

型で縛ることで設定漏れをコンパイル時に検出できる。

// config/environments.ts
export interface EnvironmentConfig {
  readonly envName: string;
  readonly region: string;
  readonly vpc: { cidr: string; maxAzs: number };
  readonly ecs: { cpu: number; memoryLimitMiB: number; desiredCount: number };
  readonly rds: { instanceClass: ec2.InstanceClass; multiAz: boolean };
}
export const environments: Record<string, EnvironmentConfig> = {
  dev: {
    envName: 'dev', region: 'ap-northeast-1',
    vpc: { cidr: '10.0.0.0/16', maxAzs: 2 },
    ecs: { cpu: 256, memoryLimitMiB: 512, desiredCount: 1 },
    rds: { instanceClass: ec2.InstanceClass.T3, multiAz: false },
  },
  prod: {
    envName: 'prod', region: 'ap-northeast-1',
    vpc: { cidr: '10.1.0.0/16', maxAzs: 3 },
    ecs: { cpu: 1024, memoryLimitMiB: 2048, desiredCount: 2 },
    rds: { instanceClass: ec2.InstanceClass.R6G, multiAz: true },
  },
};

RDSのRemovalPolicy(自分が踏んだ罠)

const db = new rds.DatabaseInstance(this, 'Database', {
  engine: rds.DatabaseInstanceEngine.postgres({
    version: rds.PostgresEngineVersion.VER_16,
  }),
  vpc,
  instanceType: ec2.InstanceType.of(config.rds.instanceClass, ec2.InstanceSize.MEDIUM),
  multiAz: config.rds.multiAz,
  // ⚠️ ここを設定しないと cdk destroy で本番DBが消える
  deletionProtection: config.envName === 'prod',
  removalPolicy: config.envName === 'prod'
    ? cdk.RemovalPolicy.RETAIN   // prod: スタック削除後もリソース保持
    : cdk.RemovalPolicy.DESTROY, // dev: スタック削除と一緒に削除
  backupRetention: Duration.days(config.envName === 'prod' ? 30 : 7),
  snapshotIdentifier: config.envName === 'prod' ? 'prod-final-snapshot' : undefined,
});

4. カスタムConstruct(再利用パターン)

同じ構成を複数のサービスで使い回す場合に真価を発揮する。

// constructs/SecureEcsService.ts
export class SecureEcsService extends Construct {
  public readonly service: ecs.FargateService;
  constructor(scope: Construct, id: string, props: {
    cluster: ecs.Cluster;
    image: ecs.ContainerImage;
    port: number;
    cpu?: number;
    memoryLimitMiB?: number;
  }) {
    super(scope, id);
    const sg = new ec2.SecurityGroup(this, 'Sg', {
      vpc: props.cluster.vpc,
      allowAllOutbound: true,
    });
    const taskDef = new ecs.FargateTaskDefinition(this, 'TaskDef', {
      cpu: props.cpu ?? 256,
      memoryLimitMiB: props.memoryLimitMiB ?? 512,
    });
    taskDef.addContainer('App', {
      image: props.image,
      portMappings: [{ containerPort: props.port }],
      logging: ecs.LogDrivers.awsLogs({
        streamPrefix: id,
        logRetention: logs.RetentionDays.ONE_MONTH,
      }),
    });
    this.service = new ecs.FargateService(this, 'Service', {
      cluster: props.cluster,
      taskDefinition: taskDef,
      securityGroups: [sg],
    });
  }
}

5. インフラコードのテスト

CDKの大きなアドバンテージ。Jestで単体テストが書ける。

import { Template } from 'aws-cdk-lib/assertions';
test('prod環境でdesiredCountが2であること', () => {
  const app = new cdk.App();
  const stack = new MyStack(app, 'TestStack', environments.prod);
  const template = Template.fromStack(stack);
  template.hasResourceProperties('AWS::ECS::Service', {
    DesiredCount: 2,
  });
});
test('prod環境でRDSのdeletionProtectionが有効であること', () => {
  const app = new cdk.App();
  const stack = new MyStack(app, 'TestStack', environments.prod);
  const template = Template.fromStack(stack);
  template.hasResourceProperties('AWS::RDS::DBInstance', {
    DeletionProtection: true,
  });
});

まとめ

AWS CDKはYAML地獄からの解放と同時に、「削除も自動化する」という両刃の剣だ。本番環境では必ず RemovalPolicy.RETAINdeletionProtection: true を設定すること——これは自分が6時間の復旧作業を経て得た教訓だ。

CDKが最も輝くのは「同じインフラパターンを複数環境・複数サービスで再利用する」場面だ。型安全なTypeScriptで書かれたカスタムConstructは、チーム全体のインフラ品質を底上げする最速の方法だと思っている。


関連記事

参考リンク

コメント

タイトルとURLをコピーしました