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.RETAIN と deletionProtection: true を設定すること——これは自分が6時間の復旧作業を経て得た教訓だ。
CDKが最も輝くのは「同じインフラパターンを複数環境・複数サービスで再利用する」場面だ。型安全なTypeScriptで書かれたカスタムConstructは、チーム全体のインフラ品質を底上げする最速の方法だと思っている。
関連記事
- Terraform vs CloudFormation徹底比較:AWSインフラで両方使った結論
- AWS Lambda パフォーマンス最適化完全ガイド:コールドスタートとコストを削る
- Pythonでインフラ運用を劇的に効率化:boto3自動化の実践術
- 副業から年収2000万フリーランスへ:AWS案件で単価を上げる戦略

コメント