はじめに
AWS CDK (Cloud Development Kit)について、私が初めて学習した際に学んだ内容を備忘録としてまとめてみました。
この記事を読んでいただくことで、aws-cdkを使用してコードを書き始めるために必要な前提知識を身につけることができます。
これから AWS CDK を学ばれる方にとって、少しでも参考になれば幸いです。
今回、コードのプログラミング言語にはTypeScriptを使用しています。
なお、最初に必要なNode.jsのインストールなどは以下のサイトを参考に進めてください。
TypeScript の基礎から始める AWS CDK 開発入門
なぜNode.jsが必要?
AWS CDK のコア実装は TypeScript (Node.js) で書かれています。
開発者が Python や Java で CDK を使えるのは、jsii という「言語間ブリッジ」の仕組みのおかげです。
jsii Kernel (Node.js)がJSON RPC という「共通言語」を使用してPython や JavaからのリクエストをTypeScriptに渡しています。
詳しくは
AWS CDK 実装の制約と多言語対応ツール jsii の仕組み
CDKとは
AWS CDK は インフラをコードで管理するツールIaC(Infrastructure as Code)の一つです。
cdk synth コマンドを実行することで、書いたコードが CloudFormationのテンプレート (YAML/JSON) に変換(合成-Synthesize)されます。
また、cdk deploy コマンドを実行すると、CloudFormation がそのテンプレートを使って AWS リソースを実際に構築します。
(cdk deploy を実行すると、自動的に cdk synth が内部で実行されます)
AWS CDKの構成要素
Construct Programming Model(CPM)が採用されています。
→クラウドアプリケーションなどの望ましい状態を定義して抽象化するためのプログラミングモデル
以下のツールでも使用されています。
・CDKTF(TerraformをTypeScriptやPythonで書けるようにする)
・CDK8s(KubernetesをTypeScriptやPythonで書けるようにする)
・Projen(プロジェクトのメタ設定ファイルをTypeScriptで書けるようにする)
構成要素は小さいものから順に以下のようになります。
Construct = AWSリソースを組み立てる部品(最小単位)
1つ以上のawsリソースを表現。
Construct はAWSリソース(S3バケットやLambda関数など)の組み立て部品を表します。
また、Constructには3つのレベルが存在します。
- L1(低レベル)
• CloudFormation リソースをほぼそのまま表現
• 名前は CfnS3Bucket や CfnLambdaFunction など
• 細かく設定できるが、全部自分で書く必要がある(すべてのプロパティを明示的に宣言する必要がある) - L2(中レベル)
• AWS がよく使う「ベストプラクティス」を組み込んである
• 例: s3.Bucket, lambda.Function
• デフォルト値が便利に設定されていて、すぐ使える - L3(高レベル / Patterns)
• 複数のリソースを組み合わせて「よくある構成」をまとめたもの
• 例: API Gateway + Lambda + DynamoDB をまとめてくれるパターン
• 自分でもオリジナルの Construct を作れる
これらの大小様々な部品(Construct)を組み合わせてStackを構築します。
基本的にはL2 Constructを使用し、L2にないリソースやプロパティ、Override、Dependencyの指定が必要な場合はL1を使う、といったように使い分けます。
Stack = Construct をまとめた 1つのデプロイ単位
CloudFormation Stackに対応します。
→最大リソース数やサイズなどの制約を受けます。
また、さらにこれらのStackをまとめてStageを構築します。
Stage = Stack をまとめた環境の単位(dev, stg, prodなど)
(デプロイパイプライン=CDK Pipelinesを使用する場合は必須)
App = アプリケーション全体(複数のAWSアカウント、リージョンにまたがるころが可能)
役割:CloudAssembly(デプロイに必要な資材一式)を作成します。
→CloudFormationテンプレート、Asset(Lambda関数のコード、コンテナイメージ、ファイルなど)
デプロイまでの大まかな流れ
- 開発者がプログラミング言語でインフラ構成を定義
-
cdk deploy(AWS CDK CLI) →AWS CDK Toolkitに含まれる
→テンプレートをAWS CloudFormationにわたす
→AssetはCLIが直接デプロイ - AWS CloudFormationはテンプレートを元にAPIを呼び出しリソースを操作(デプロイ)
※CDK Appのコード実行が完了してからCloudFormationやAssetのデプロイが行われます。
そのためコード上でデプロイ時に決定される値を受け取ったり、デプロイ中に処理を実行したりすることはできません。
(未決定の値を扱うTokenや、Custom Resource、Triggerなどの機能でカバー可能)
CDK App と CDK Toolkit は直接統合しない
CDK Toolkit が直接アプリのコードを呼び出すわけではなく、CLI が App を「外部プログラム」として起動します。
そのときの設定(Context や環境ごとの値)は「環境変数」や「JSON ファイル」で渡されます。
例)CDK_ENV=dev cdk deploy
プロジェクトの作成方法
まずはcdk用のディレクトリを作成します。
例)mkdir aws-cdk-cognito
作成したディレクトリに移動し、AWS CDK (Cloud Development Kit) の新しいプロジェクトを初期化するためのコマンドを実行します。
cd aws-cdk-cognito
cdk init app --language typescript
以下のようなディレクトリ構成が自動で作成されます。
aws-cdk-cognito/
├── bin/ # アプリケーションエントリーポイント
│ └── aws-cdk-cognito.ts # CDKアプリケーションのメインファイル
├── lib/ # スタック定義
│ └── aws-cdk-cognito-stack.ts # メインスタッククラス
├── test/ # テストファイル
│ └── aws-cdk-cognito.test.ts # ユニットテスト
├── node_modules/ # Node.js依存関係
├── package.json # プロジェクト設定と依存関係
├── package-lock.json # 依存関係のロックファイル
├── cdk.json # CDK設定ファイル
├── tsconfig.json # TypeScript設定
├── jest.config.js # Jestテスト設定
└── README.md # プロジェクト説明書
Constructの宣言
Construct = AWSリソースを組み立てる部品(最小単位)
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as s3 from "aws-cdk-lib/aws-s3";
export class MyStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// scope = this, id = "MyBucket", props = BucketProps
new s3.Bucket(this, "MyBucket", {
versioned: true,
removalPolicy: cdk.RemovalPolicy.DESTROY, // スタック削除時にS3も削除
});
}
}
Constructの初期化時は共通して3つの引数を取ります。
| 位置 | 仮引数 | 型 | 説明 |
|---|---|---|---|
| 1 | scope | Construct | Constructツリーの親を指定。すべてのリソースはStackの子孫である必要がある。 通常は現在のスコープを表す this を渡す。別のConstructを親にしたい場合も渡せるが、ID重複に注意する必要がある。 |
| 2 | id | string |
Construct ID と呼ばれる。scope 内で一意となる識別子を指定。CloudFormation テンプレートにおけるリソース名や論理IDの一部になるため、重複や意味不明な名前は避けるべき。 |
| 3 | props | (Constructごとに固有の型) | Constructの初期状態を定義するプロパティ。 例: BucketProps や FunctionProps など、対象のConstruct専用の型が割り当てられる。多くは適切なデフォルト値が用意されており、省略可能(オプション) なことも多い。 |
自作したスタックで、独自の必須パラメータがある場合は interface FooProps extends StackProps のように定義します。
import { Construct } from 'constructs';
import type { StackProps } from 'aws-cdk-lib';
import * as cognito from 'aws-cdk-lib/aws-cognito';
interface CognitoStackProps extends StackProps {
/** 例: dev / stg / prod */
envKey: string;
/** 例: myProject */
projectName: string;
/** 任意: 共通プレフィックスを上書きしたい場合 */
namePrefixOverride?: string;
}
export class CognitoStack extends cdk.Stack {
public readonly userPool: cognito.UserPool;
public readonly userPoolClient: cognito.UserPoolClient;
constructor(
scope: Construct,
id: string,
{ envKey, projectName, namePrefixOverride, ...stackProps }: CognitoStackProps
) {
super(scope, id, stackProps);
...
Composition(コンポジション)とは
CDKでコンポーネントを構造化するもっとも一般的な方法です。
Constructを継承したクラスを定義して複数のConstructをまとめます。
(→これらのコンポーネントをさらにまとめたのがStack)
複数の低レベルなConstructを組み合わせて、より高レベルの、再利用可能な抽象化(高レベルコンストラクト)を定義するパターンです。このパターンを用いることで、AWS リソースを整理し、会社のベストプラクティスをコードとして共有したり、他のチームと再利用可能なコンポーネントを作成したりできるようになります。
例)バックアップやモニタリング機能を持つAmazon DynamoDBテーブルをデプロイしたい
- 低レベルコンストラクト:
まずDynamoDBテーブルを作成するコンストラクトを作成。
import { Construct } from 'constructs'
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'
export class DynamoTableConstruct extends Construct {
public readonly table: dynamodb.Table
constructor(scope: Construct, id: string) {
super(scope, id)
this.table = new dynamodb.Table(this, 'Table', {
partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
})
}
}
2. 高レベルコンストラクト:
上記のコンストラクトに、バックアップとモニタリング用のコンストラクトを組み合わせ、
これらすべてを内包するコンストラクトを作成。
import { Construct } from 'constructs'
import * as cw from 'aws-cdk-lib/aws-cloudwatch'
import * as sns from 'aws-cdk-lib/aws-sns'
import * as backup from 'aws-cdk-lib/aws-backup'
import * as iam from 'aws-cdk-lib/aws-iam'
import { Duration } from 'aws-cdk-lib'
import { DynamoTableConstruct } from './dynamo-table'
export class DynamoWithOpsConstruct extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id)
// DynamoDBテーブル
const tableConstruct = new DynamoTableConstruct(this, 'Data')
const table = tableConstruct.table
// CloudWatch アラーム(例: スロットル検知)
const topic = new sns.Topic(this, 'AlarmTopic')
new cw.Alarm(this, 'ThrottleAlarm', {
metric: table.metricThrottledRequests(),
threshold: 1,
evaluationPeriods: 1,
}).addAlarmAction({ bind: () => ({ alarmActionArn: topic.topicArn }) })
// Backup Plan
const vault = new backup.BackupVault(this, 'Vault')
const plan = new backup.BackupPlan(this, 'Plan', { backupVault: vault })
plan.addRule(new backup.BackupPlanRule({ deleteAfter: Duration.days(7) }))
const role = new iam.Role(this, 'BackupRole', {
assumedBy: new iam.ServicePrincipal('backup.amazonaws.com'),
})
plan.addSelection('Selection', {
role,
resources: [backup.BackupResource.fromArn(table.tableArn)],
})
}
}
3.使いたいときは、スタックに高レベルコンストラクトを1つ置くだけで、コンポーネントとして使える
import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
import { DynamoWithOpsConstruct } from './constructs/dynamo-with-ops'
export class ExampleStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props)
new DynamoWithOpsConstruct(this, 'AppData')
}
}
以下、もう一つの例
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as s3n from 'aws-cdk-lib/aws-s3-notifications';
// --- 再利用可能な部品(Composition)---
class S3WithLambda extends Construct {
public readonly bucket: s3.Bucket;
public readonly handler: lambda.Function;
constructor(scope: Construct, id: string) {
super(scope, id);
// ① S3バケット
this.bucket = new s3.Bucket(this, 'MyBucket', {
removalPolicy: cdk.RemovalPolicy.DESTROY, // サンプルなので削除OK
autoDeleteObjects: true,
});
// ② Lambda関数
this.handler = new lambda.Function(this, 'MyLambda', {
runtime: lambda.Runtime.NODEJS_20_X,
code: lambda.Code.fromInline(`
exports.handler = async (event) => {
console.log("S3 Event:", JSON.stringify(event, null, 2));
return { statusCode: 200 };
};
`),
handler: 'index.handler',
});
// ③ S3にファイルアップロード → Lambdaをトリガー
this.bucket.addEventNotification(
s3.EventType.OBJECT_CREATED,
new s3n.LambdaDestination(this.handler)
);
}
}
// --- Stackで利用 ---
export class MyStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
new S3WithLambda(this, 'S3LambdaExample');
}
}
Token とは
• 未確定の値を表す仕組み
• 合成時にはプレースホルダ、デプロイ時に解決
• CloudFormation の Ref/GetAtt に変換される
• 他のリソースに渡すのに便利(例:S3 バケット名を Lambda に渡す)
CDK でリソースを作成するとき、まだデプロイされていないから値が決まらないものがあります。
例えば S3 バケット名 や Lambda の ARN などです。
こういう「未確定の値」を CDK 内では Token と呼びます。
Token は プレースホルダ(仮の値) のような役割を果たし、後で CloudFormation に変換されます。
import * as cdk from "aws-cdk-lib";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as lambda from "aws-cdk-lib/aws-lambda";
import { Construct } from "constructs";
export class MyStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// S3 バケットを作成
const bucket = new s3.Bucket(this, "MyBucket");
// Lambda を作成
const fn = new lambda.Function(this, "MyFunc", {
runtime: lambda.Runtime.NODEJS_18_X,
handler: "index.handler",
code: lambda.Code.fromInline("exports.handler = async () => {};"),
environment: {
// ここで bucket.bucketName を参照したい
BUCKET_NAME: bucket.bucketName,
},
});
}
}
• bucket.bucketName はまだ決まっていない → Token が返る
• cdk synth すると、CloudFormation テンプレート内では !Ref MyBucket に変換される
• デプロイ時(AWS 側でスタック作成時)に 実際のバケット名 が決まる
Tokenを扱うときの注意点
// OK(文字列連結)
const value = `bucket-${bucket.bucketName}`;
// NG(数値演算など)
const value = bucket.bucketName + 123; // エラー
リソースとパラメータの参照
CDK のコードで“別リソースの値”を参照すると、その場で実体文字列になるのではなく、トークン(Token)が利用されます。また、参照が張られると 依存関係(DependsOn) も自動付与され、作成順序も自動調整されます。
const taskRole = new iam.Role(this, 'TaskRole', {
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
});
const execRole = new iam.Role(this, 'TaskExecRole', {
assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy')],
});
new ecs.FargateTaskDefinition(this, 'TaskDef', {
taskRole: taskRole, // ← 別リソースを参照(トークン)
executionRole: execRole,
});
// StackA
export const bucket = new s3.Bucket(this, 'SharedBucket');
// StackB(A を受け取って使用)
new iam.Role(this, 'Reader', {
assumedBy: new iam.AccountRootPrincipal(),
inlinePolicies: {
read: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
actions: ['s3:GetObject'],
resources: [StackA.bucket.arnForObjects('*')], // ← 参照
}),
],
}),
},
});
詳しくはこちらのスライドを参考にしてください。
実践 AWS CDK 〜 いろいろな参照のカタチと使い分け 〜
Aspectとは
Aspect は「横断的な共通処理」を、指定したスコープ配下のすべての Construct に一括適用する仕組みです。タグ付け・削除ポリシー設定・暗号化チェックなどを “後からまとめて” 付けられます。
• cdk.Aspects.of(スコープ).add(aspect) で登録すると、合成(synth)の準備段階で CDK がツリーを走査し、各ノードに対して visit(node) が呼ばれます。
• visit の中で対象かどうかを判定し、必要な操作(タグ付け、RemovalPolicy 付与、警告の追加など)を行います。
• Stage をまたいでは適用されません。(ある Stage に追加した Aspect は、その Stage 内の Stack/Construct にだけ効きます。別 Stage には影響しません。)
主な使われ方
• すべてのリソースへ タグ を一括付与(cdk.Tags は内部的に Aspect)
• RemovalPolicy(DESTROY / RETAIN など)を一括適用
• セキュリティ・運用ルールの強制(未暗号化をエラーにする 等)
• 命名・設定の lint(基準に合わないものへ警告/エラー)
class EnvAwareRemovalPolicyAspect implements cdk.IAspect {
constructor(private readonly env: 'prod' | 'stg' | 'dev') {}
visit(node: IConstruct): void {
if (node instanceof cdk.CfnResource) {
const policy =
this.env === 'prod'
? cdk.RemovalPolicy.RETAIN
: cdk.RemovalPolicy.DESTROY;
node.applyRemovalPolicy(policy);
}
}
}
const app = new cdk.App();
const env = (app.node.tryGetContext('env') as 'prod'|'stg'|'dev') ?? 'dev';
// Stack 単位で適用
const stack = new MyStack(app, 'AppStack', {});
cdk.Aspects.of(stack).add(new EnvAwareRemovalPolicyAspect(env));
デプロイ時の依存関係
CDK が自動で依存解決してくれるケース
CDK は リソース同士の参照関係 を解析して、自動で CloudFormation の DependsOn を生成してくれます。
const bucket = new s3.Bucket(this, 'MyBucket');
const topic = new sns.Topic(this, 'MyTopic');
// バケットイベント通知を SNS Topic に追加
bucket.addEventNotification(s3.EventType.OBJECT_CREATED, new s3n.SnsDestination(topic));
この場合、CDK は「BucketNotification → Topic」への参照を検出し、CloudFormation の内部で自動的に依存関係をつけます。
→ 通常は addDependency を書かなくてもOK。
自動で解決できないケース
•単なる文字列参照(リソース間の実際の参照がない)
const bucket = new s3.CfnBucket(this, 'MyBucket');
const policy = new s3.CfnBucketPolicy(this, 'BucketPolicy', {
bucket: 'my-bucket-name', // ← bucket.ref を使っていない
policyDocument: {...},
});
// CDK は依存関係を解決できないので、自分で追加
policy.addDependency(bucket);
•RemovalPolicy, Override など低レベル操作
→addOverride, overrideLogicalId などで CloudFormation の直接操作をしている場合は依存関係が壊れることがある。
・複数スタックをまたぐ依存
→CDK は「同じスタック内」なら自動解決できるが、「スタック間」だと基本的に addDependency が必要。
const networkStack = new NetworkStack(app, 'Network');
const appStack = new AppStack(app, 'App');
appStack.addDependency(networkStack); // ← 明示が必要
デプロイ時のコマンド
Bootstrapping
cdk bootstrap
プロジェクト内にインストールした 特定バージョンの CDK を共通して使用したい場合は
npm run cdk bootstrap
CDK Appをデプロイする前に、ファイルを保存するためのS3バケットやECRリポジトリ、CDK ToolkitやCloudFormationがAssumeするIAMロールなどを作成するための必須のプロセスです。
デプロイ対象のアカウント、リージョンにつき1回だけ実施します。
(アップデートによりbootstrapのバージョンが上がった場合は再実行が必要→cdk deploy時のメッセージで確認可能)
AWSアカウントとリージョンごとにBootstrapが必要です。
# 東京リージョンにアカウントID:123456789012で作成
cdk bootstrap aws://123456789012/ap-northeast-1
Diff
npm run cdk diff
CDK Appで新しく合成したテンプレートと、すでにデプロイ済みのテンプレートを比較します。
※前回のデプロイ後に手動でリソースに変更を加えた場合の差分は反映されません。また、手動での変更は次のデプロイ時に上書きされてしまいます。
Deploy
npm run cdk deploy
# スタックを指定してデプロイ
npm run cdk deploy SampleStack
# デプロイ時の確認を行わない
npm run cdk deploy --all --require-approval=never
# Lambda関数などの開発時に変更を高速に反映する
npm run cdk deploy --all --hotswap
# スタックの更新失敗時に自動ロールバックしない
npm run cdk deploy --all --no-rollback
# Context を指定
npm run cdk deploy --all -c key=value
※deploy --allではStage配下は対象にならないため注意が必要
→deploy '**'もしくはdeploy 'dev/*'を使用する。
エントリーポイント
デプロイ対象のファイル(エントリーポイント)は、
プロジェクト配下に作成されるcdk.jsonのappプロパティで指定されます。
(CDK CLIの --appオプションでも指定可能=上書き)
{
"app": "npx ts-node --prefer-ts-exts bin/aws-cdk-cognito.ts", //エントリーポイント
"watch": {
...
おわりに
今回の学習を終えて、aws cdkに関してかなり理解を深めることができました。
各AWSリソースを定義する際の詳細なプロパティ(デフォルト値)などは、AWS CDK Reference Documentation
から確認することが可能です。
また、初めてCDKを学ぶ際は、個人的に以下のstepがオススメです。
step1: AWS CDK 概要 (Basic #1)【AWS Black Belt】 (座学)
step2: AWS CDKの基本的なコンポーネントと機能 (Basic #2)【AWS Black Belt】 (座学)
step3: TypeScript の基礎から始める AWS CDK 開発入門 (ハンズオン)
step4: AWS CDK Immersion Day ワークショップ(日本語版) (ハンズオン)
step5: 実践(自分で考えたインフラ構成をデプロイする)