AWS CDKによるInfrastructure as Code

AWS CDKのApp・Stack・Constructの階層、CloudFormationとの関係、リソース参照とGrantによる権限設定、環境別コンテキスト、BootstrapからデプロイまでのCLI開発サイクルを解説します。

学習順Step 48 / 101サービス自動化 / IaC試験ドメインデプロイメント

AWS CDKとCloudFormationの関係

AWS CDK(AWS Cloud Development Kit)は、TypeScript、Python、Java、C#、Goなどのプログラミング言語でAWSインフラを定義するオープンソースのInfrastructure as Code(IaC)フレームワークです。CloudFormationテンプレートを直接JSONまたはYAMLで記述する方法に対し、CDKではクラス、オブジェクト、関数など、開発者が使い慣れた言語の仕組みを使ってリソースを組み立てられます。Construct Libraryが提供する抽象化や既定値を活用できるため、設定の共通化や構成部品の再利用もしやすくなります。

CDKはCloudFormationの代替となる別のリソースプロビジョニングサービスではありません。CDKアプリケーションを実行すると、まずリソース定義がCloudFormationテンプレートへ合成(synthesize)されます。そのテンプレートをCloudFormationに渡すことで、AWSリソースの作成・更新・削除が行われます。したがって、実際のデプロイとスタック管理を担うのはCloudFormationであり、変更セットなどCloudFormationの機能も活用できます。

CDKコードからCloudFormationを通じてAWSリソースをデプロイする流れ

App・Stack・Constructの階層

CDKアプリケーションは、App → Stack → Constructを基本とするConstructツリーで構成されます。ツリーの親子関係はリソースの所有関係やコード上の構成を表し、Constructを移動すると合成されるリソース識別子に影響することがあります。

要素 役割 覚えておきたいこと
App CDKアプリケーション全体のルート 1つのAppに複数のStackを含められる。App自体は通常、AWSリソースを直接作るためのConstructではなく、アプリ全体をまとめて合成する
Stack デプロイとCloudFormationスタック管理の基本単位 App内に複数作成できる。アカウント、リージョン、環境、サービスなどの境界で分割し、独立した単位でデプロイできる
Construct AWSリソースや関連設定を表す構成部品 Stackの中に複数置ける。Constructの中に別のConstructを含める入れ子構造も作れ、再利用可能な独自部品も実装できる

CDKのConstructは、AWSリソースを1つだけ表すとは限りません。抽象化の程度によって、CloudFormationリソースを直接表す低レベルのConstructから、複数リソースをまとめた高レベルのConstructまであります。Constructツリーの識別子は各スコープ内で一意である必要があり、CDKは親から子までのパスなどを使ってCloudFormationテンプレート内で必要となる識別子を生成します。CloudFormationテンプレート上の論理IDと関連しますが、Constructのidをそのまま論理IDとして固定的に使うという意味ではありません。

たとえば、社内向けのイベント処理基盤を作るAppに、検証環境用と本番環境用のStackを置き、それぞれにイベント受け付け用のキュー、処理用Lambda関数、監視設定のConstructを配置できます。サービスや環境をStackとして分割すれば、変更範囲やデプロイ時の境界を管理しやすくなります。一方、Stack間でリソースを参照する場合は依存関係が生じるため、結合度やデプロイ順序も考慮して分割します。

Constructの定義と引数

AWSリソースをConstructとして作るとき、一般的に次の3つの値をコンストラクタへ渡します。

引数 意味 設計上の役割
scope Constructの親となるスコープ 通常は現在のConstructを表すthis(Pythonではself)を渡し、Constructツリー上の所属先を示す
id その親スコープ内での識別子 同じ親の直下で一意にする。ConstructツリーのパスやCloudFormation側の識別子生成にも関係する
props リソースの設定値をまとめたオブジェクト リソースの種類に応じて、実行設定、暗号化、保持期間、ネットワークなどを指定する。省略可能なプロパティや既定値が用意されることもある

次のTypeScriptは、別の用途例として、注文処理用のキューを暗号化して作成する定義です。

import * as cdk from 'aws-cdk-lib';
import * as sqs from 'aws-cdk-lib/aws-sqs';

const app = new cdk.App();
const stack = new cdk.Stack(app, 'OrderProcessingStack');

const orderQueue = new sqs.Queue(stack, 'OrderQueue', {
  encryption: sqs.QueueEncryption.SQS_MANAGED,
  visibilityTimeout: cdk.Duration.seconds(45),
});

この例では、stackがスコープ、OrderQueueがスコープ内のID、暗号化方式と可視性タイムアウトを含むオブジェクトがpropsです。QueueはConstruct LibraryのConstructであり、CDKは定義内容をCloudFormationリソースへ変換します。サービスごとにpropsの型や意味は異なるため、設定可能な項目や既定値をAPIリファレンスで確認して使います。

リソース間の参照

CloudFormationテンプレートでは、リソース間で値を受け渡すときにRef、Fn::GetAtt、Fn::Subなどの組み込み関数を使います。CDKでは、Constructが公開するプロパティを別のConstructのpropsに渡す形で関係を表現できます。CDKはその値を合成時にCloudFormationの参照表現へ変換し、CloudFormationに依存関係を伝えます。

たとえば、処理用Lambda関数を作成するときに、先に定義したDynamoDBテーブルの名前を環境変数として渡すことができます。コード内にテーブル名を文字列で複製する必要はなく、table.tableNameのようなプロパティを使うことで、デプロイ時に決まる値も参照として扱えます。ConstructのAPIに応じて、ARN、名前、URL、IDなど必要な属性を選びます。

import * as cdk from 'aws-cdk-lib';

import * as lambda from 'aws-cdk-lib/aws-lambda';

const app = new cdk.App();
const stack = new cdk.Stack(app, 'JobProcessingStack');
const jobsTable = new dynamodb.Table(stack, 'JobsTable', {
  partitionKey: { name: 'jobId', type: dynamodb.AttributeType.STRING },
});

const worker = new lambda.Function(stack, 'JobWorker', {
  runtime: lambda.Runtime.NODEJS_20_X,
  handler: 'worker.handler',
  code: lambda.Code.fromInline('exports.handler = async () => ({ statusCode: 200 });'),
  environment: {
    JOBS_TABLE_NAME: jobsTable.tableName,
  },
});

jobsTable.grantReadData(worker);

jobsTable.tableNameは通常、ソースコードの評価時点で実際の物理テーブル名が確定している値ではありません。CDKがトークンとして扱い、CloudFormationテンプレート内に実行時解決される参照を出力します。リソースの参照は便利ですが、別Stackのリソースを参照すればStack間依存が生じます。また、異なるアカウントやリージョンのリソースは単純な同一Stack内参照と同じ扱いにはならず、明示的な連携方法が必要です。

GrantメソッドとIAM権限

CDKのConstruct Libraryには、リソースへのアクセス権を意図ベースで表現するGrantメソッドが用意されています。たとえば、Lambda関数がDynamoDBテーブルを読み取る必要がある場合、テーブル側のgrantReadDataにLambdaの実行ロールを渡すことで、必要な読み取り操作の許可を追加できます。

jobsTable.grantReadData(worker);

Grantを使うと、IAMポリシーのアクション名やリソースARNをすべて手作業で組み立てる代わりに、アクセスの意図をConstruct間で表現できます。Construct LibraryはGrantの内容をもとに、対象のプリンシパルへアイデンティティベースのポリシーを加えたり、サービスによっては対象リソースのリソースベースポリシーも変更したりします。どちらが必要になるか、またどの操作が追加されるかはサービスやConstruct、対象ロールの状況によって異なります。

Grantメソッドは権限設計そのものを不要にするものではありません。以下を必ず確認します。

  • 付与対象が想定どおりのIAMロールまたはプリンシパルであること。Lambdaの場合は通常、その関数の実行ロールが権限付与先となる。
  • grantRead、grantReadDataなどのメソッド名が示す範囲と、生成されたIAMアクションがアプリケーションに必要な最小限の権限であること。
  • 暗号化されたデータを扱う場合、データ読み取り権限とは別にKMSキーの使用許可も必要になる場合があること。
  • リソースポリシー、VPCエンドポイントポリシー、SCP、Permissions Boundary、明示的なDenyなど、ほかの認可条件によりアクセスが制限される可能性があること。
  • 外部から参照したリソースやConstructによっては、CDKがポリシーを追加できない、または何も変更しない場合があること。

最終的な権限は、合成後のCloudFormationテンプレートやcdk diffでレビューします。書きやすさだけを理由に広範囲なGrantを選ばず、利用するAPIとデータ範囲に応じた許可を選択することが重要です。

環境ごとの設定とcontext

検証・本番などの環境でインスタンスタイプや設定値を変えたいとき、コードに環境名ごとの条件を大量に埋め込むと、変更時の見通しが悪くなります。設定をコードから分離する方法の一つが、プロジェクトのcdk.jsonにあるcontextです。Context値は文字列、数値、配列、オブジェクトなどのJSON値を扱え、アプリケーションコードから読み出せます。

{
  "app": "npx ts-node bin/app.ts",
  "context": {
    "stageConfig": {
      "test": { "instanceType": "t3.small" },
      "production": { "instanceType": "m7i.large" }
    }
  }
}
const stage = app.node.tryGetContext('stage') ?? 'test';
const stageConfig = app.node.tryGetContext('stageConfig');
const instanceType = stageConfig[stage].instanceType;

実行時にcdk --context stage=production synthのように値を渡す方法もあります。Contextにより、環境ごとの構成値を外部化し、同じStackコードを保ちながら値を切り替えやすくできます。ただしContext値はデプロイ先環境の指定と同じものではありません。アカウントとリージョンを固定するにはStackのenvなどで環境を指定し、認証情報やデプロイ対象も別途正しく設定します。

またCDK CLIは、利用可能なAZやAMIなど、AWSアカウントから取得した情報をContextとしてキャッシュすることがあります。キャッシュにより、毎回変化しうるルックアップ結果を固定し、同じCDKコードから予測しやすいテンプレートを合成できます。自動取得される値はcdk.context.jsonで管理されることがあり、通常のプロジェクト設定として保持する値はcdk.jsonのcontextに分けておくと、キャッシュのクリア時に意図せず消えることを防げます。キャッシュされたContextを手で書き換えるのではなく、CDK CLIのContext管理機能で更新します。

CDK CLIを使った開発とデプロイ

CDKを使う開発は、プロジェクトを用意し、対象環境をBootstrapし、テンプレートを合成・確認してからCloudFormationでデプロイする反復作業です。代表的なコマンドは次のとおりです。

コマンド 目的 主な注意点
cdk init 選択した言語のCDKプロジェクトひな形を作成する 新規アプリケーション開始時に使う。作成後はコード、依存パッケージ、設定を管理する
cdk bootstrap デプロイ用のBootstrapリソースをCloudFormationスタックとして作成・更新する デプロイ対象のアカウントとリージョンごとに準備する。後から必要に応じて更新することもある
cdk synth CDKコードを実行し、CloudFormationテンプレートとCloud Assemblyを生成する 通常はローカルのcdk.outに出力する。AWSリソースをデプロイする操作ではない
cdk diff ローカルで合成される定義と、デプロイ済みスタックとの差を確認する 主に既存スタックの更新前レビューに使う。初回は比較対象のスタックがない
cdk deploy 選択したStackのテンプレートとアセットをAWSへ配備し、CloudFormationスタックを作成・更新する CloudFormationの変更セットや承認オプションを利用できる。変更内容と権限を確認してから実行する
cdk destroy Stackを削除し、削除対象となる管理リソースをCloudFormation経由で削除する データ保持ポリシーなどにより残るリソースがあり、永続データを失う危険もあるため事前確認する

基本的な初期化と反復の流れは次のようになります。

  1. cdk initでプロジェクトの骨組みを作り、リソースやConstructを実装する。
  2. デプロイ先として使うアカウント・リージョンを決め、その環境でcdk bootstrapを実行する。
  3. cdk synthでコードからCloudFormationテンプレートを作り、定義と出力を確認する。
  4. 既存Stackの変更ではcdk diffで追加・変更・削除の候補をレビューする。
  5. cdk deployで変更をCloudFormationへ渡し、Stackの作成または更新を実行する。
  6. コード変更後はSynth、Diff、Deployを繰り返す。不要な環境を片付ける場合にのみ、影響を確認してcdk destroyを使う。

Bootstrapとアセット

cdk bootstrapは、CDKアプリケーションをAWS環境へデプロイするための基盤を準備します。Bootstrap Stackは通常CDKToolkitという名前で作成され、アセットの保管・受け渡しに使うS3バケット、コンテナイメージ用ECRリポジトリ、デプロイを許可するIAMロールなどを含みます。実際の構成はBootstrapテンプレートのバージョンやオプションによって異なります。

CDKでは、Lambdaコード、ファイル群、コンテナイメージなど、デプロイに必要なファイルをアセットとして扱います。CLIはアセットを準備し、内容に応じてBootstrap環境のS3またはECRへアップロードします。その後、CloudFormationがテンプレートを処理する際に必要なアセットを参照します。従って、CDKコードがローカルにあるだけでは配備は完了せず、アセットの転送先を含むBootstrap環境と、その操作権限が必要です。

Bootstrapは「AWSアカウント全体で一生に一度だけ」行う操作ではありません。利用する各アカウント・リージョンは独立した環境なので、それぞれに準備が必要です。初回の利用時に実行するほか、より新しいCDK機能やデプロイ方式に対応するためBootstrap Stackを更新することもあります。

Synth・Diff・DeployとCloudFormationの変更セット

cdk synthはCDKコードをCloudFormationテンプレートに変換する工程です。構文や合成結果を確認するのに役立ちますが、生成しただけでAWS上のリソースは作成されません。cdk diffは合成結果を既存Stackの状態と比較し、リソースの追加、設定変更、削除などの差をレビューするためのコマンドです。比較対象となるStackがまだない初回デプロイでは通常、更新差分の確認には使えません。

cdk deployではCloudFormationがスタックの作成・更新を進めます。CloudFormationが内部で変更セットを使う構成では、リソースに適用される変更を実行前に調べられます。cdk diffとCloudFormation変更セットは関連するレビュー手段ですが、同一の機能ではありません。DiffはCDK CLIがローカルの合成結果とデプロイ済み状態を比較する操作で、変更セットはCloudFormationが変更を実行する前に評価する仕組みです。削除や置換、IAM権限の拡大など、影響の大きい差分を確認してから適用します。

Destroyとリソースのライフサイクル

cdk destroyは指定したStackをCloudFormation経由で削除するコマンドです。Stackが管理するリソースは削除対象になりますが、すべてのリソースが必ず消えるとは限りません。S3バケットやデータベースなどの保持設定(Removal Policy)がRETAINならStack削除後もリソースは残り、逆に破棄設定ではデータ消失につながる場合があります。たとえば、オブジェクトが残ったS3バケットは、そのままではCloudFormationから削除できない場合があります。したがってdestroyは一時環境の片付けに便利な一方、本番データを含むStackでは慎重な確認が必要です。

DVA試験で区別するポイント

  • CDKとCloudFormation:CDKは使い慣れた言語による定義とテンプレート生成を担い、リソースのデプロイ・スタック管理はCloudFormationが担う。
  • App・Stack・Construct:Appはアプリ全体のルート、StackはCloudFormationのデプロイ単位、Constructはリソースまたは再利用可能な構成を表す。
  • scope・id・props:親スコープ、スコープ内識別子、リソース設定値を表す。Construct IDはスコープ内で一意にする。
  • リソース参照:Constructプロパティの参照は、合成時にCloudFormationのRef/GetAtt等へ変換される。デプロイ時に決まる値はトークンとして扱われることがある。
  • Grant:アクセス許可を簡潔に表現する仕組みであり、権限が自動で常に最小化される保証ではない。生成されたポリシーと他の認可条件を確認する。
  • 環境別Context:cdk.jsonやCLIオプションからアプリ設定を与える仕組み。ContextとStackのデプロイ先アカウント・リージョン指定は別の設定である。
  • Bootstrap:デプロイ用のS3/ECRやIAMロールなどを対象環境に準備する。各アカウント・リージョン単位で必要となる。
  • コマンドの役割:synthはテンプレート生成、diffは変更比較、deployはCloudFormation経由の適用、destroyはStack削除である。

公式ドキュメント

重要ポイント

  • ▸AWS CDKはTypeScriptやPythonなどでAWSインフラを定義するオープンソースのIaCフレームワークであり、CloudFormationを置き換えるのではなくテンプレート生成とデプロイを支援する
  • ▸Appはアプリケーションのルート、StackはCloudFormationのデプロイ単位、Constructはリソースや再利用可能な構成を表す基本部品である
  • ▸Constructのscope・id・propsはそれぞれ親、スコープ内識別子、設定値を表し、Constructの階層からCloudFormationのリソース識別子が合成される
  • ▸Constructのプロパティによるリソース連携は、合成時にRefやFn::GetAttなどのCloudFormation参照へ変換される
  • ▸Grantメソッドは権限付与を簡潔に表現するが、生成されるIAMポリシーを確認し、最小権限とリソースポリシーの要否を検討する
  • ▸cdk.jsonのcontextは環境別設定やルックアップ値の管理に使えるが、デプロイ先アカウント・リージョンを自動で決めるものではない
  • ▸cdk bootstrapは利用するアカウント・リージョンごとに必要なデプロイ基盤を用意し、init・synth・diff・deploy・destroyで開発とライフサイクルを管理する

このトピックの学習を完了しますか?

完了状態はいつでも切り替えられます