CDK コードに外部パラメーターを与える方法
AWS CDK による CloudFormation スタックの構築時に、外部からキー&バリューの形でパラメーターを設定したいときは、主に次の 3 つの方法があります(クラウド上に値を保存するパラメーターストアなどは対象外とします)。
- Context values (コンテキスト・バリュー)
- Environment variables (環境変数)
- CloudFormation parameters (CloudFormation パラメーター)
S3 バケットの名前をパラメーター化したり、デプロイターゲットを staging
と production
の間で切り替えたりするときに使えます。
Context values(コンテキスト・バリュー)
コンテキスト・バリューは、CDK 特有の仕組みで、cdk deploy
実行時のコマンドライン引数や、cdk.json
ファイルの中で、キー&バリューのペアを設定することができます。
キーの型は string
で、バリューの型は JSON がサポートするデータ型のいずれかです(string
、number
、オブジェクト、およびそれらの配列)。
コンテキスト・バリューは CDK の仕組みなので、CDK コードの中からしか参照できません。
Lambda 関数の中から値を参照したい場合は、Lambda 関数のコンストラクトを生成するときに、environment
props などで間接的に渡す必要があります。
コマンドライン引数で指定する方法
cdk deploy
コマンド(あるいは diff
、synth
)を実行するときに、--context (-c)
オプションで、コンテキスト・バリューを設定できます。
$ cdk deploy --context key=value
複数のキー&バリューペアを設定したいときは、単純にオプション指定を繰り返します。
$ cdk deploy -c key1=value1 -c key2=value2
コンテキスト・バリューは CDK アプリ内の全スタックに渡されますが、特定のスタックにのみ反映させることもできます。
$ cdk deploy -c Stack1:key1=value1 -c Stack2:key2=value2
cdk.json で指定する方法
cdk init app
コマンドで CDK アプリを生成すると、cdk.json
というファイルがプロジェクトのルートに配置されます。
このファイルの中の、context
プロパティで、コンテキスト・バリュー用のキー&バリューを記述しておくことができます。
ホームディレクトリの ~/cdk.json
に記述しておくこともできます。
{
"app": "npx ts-node --prefer-ts-exts bin/myapp.ts",
"context": {
"@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
"@aws-cdk/core:enableStackNameDuplicates": "true",
// ★このあたりに追加
"target": "dev",
"bucket": {
"region": "ap-northeast-1",
"name": "my-bucket"
}
}
}
ちなみに、context
プロパティには最初からいくつか値が設定されていますが、これらは各モジュール用の機能を有効化するための設定です(@aws-cdk/aws-lambda:
のようなモジュール名のプレフィックスが付いています)。
これらは各機能を Off/On するためのものなので、Feature flags と呼ばれています(省略した場合のデフォルトは Off です)。
cdk.json
ファイルの context
プロパティの値よりも、cdk
コマンドの --context
引数で指定した値が優先されるので、cdk.json
ファイルの方にデフォルト設定を書いておき、cdk
コマンド実行時に上書きするという運用が可能です。
$ cdk deploy # 省略時は cdk.json に記述した target=dev が使われる
$ cdk deploy -c target=prod # 本番環境は明示的に target を上書き
cdk.App コンストラクト生成時に指定する方法
コンストラクト生成時の context
オプションで、明示的にコンテキスト・バリューを設定することもできます。
この値は、そのコンストラクタ以下のノード全体に反映されます。
import * as cdk from '@aws-cdk/core'
const app = new cdk.App({
context: {
bucketName: 'bucket-123456789012',
},
})
このように、トップレベルの cdk.App
コンストラクトに設定すれば、すべての Stack
コンストラクトから、this.node.tryGetContext()
でそのコンテキスト・バリューを参照できるようになります。
コンテキスト・バリューの参照方法
CDK のコード内から、construct.node.tryGetContext 関数 を呼び出すことで、コンテキスト・バリューを参照できます。
次の例では、キー名 bucketName
のコンテキスト・バリューを参照しています。
import * as cdk from '@aws-cdk/core'
const app = new cdk.App()
const bucketName = app.node.tryGetContext('bucketName') as string
if (bucketName == undefined) {
throw new Error('Context value [bucketName] is not set')
}
console.log(`bucketName = ${bucketName}`)
// ... 各スタックの定義 ...
tryGetContext
関数の戻り値は any
型で、指定したキーに対応する値がセットされていないときは、undefined
を返します(TypeScript の場合)。
デプロイのために必須の値であれば、戻り値が undefined
のときに Error
をスローすることで cdk
コマンドの実行を終了させることができます。
(おまけ)cdk.context.json ファイル
cdk
コマンド実行時にキャッシュファイル (cdk.context.json
) が生成されることがあります。
CDK のコードから Stack の availabilityZones
を参照したり、パラメーターストアを参照したりすると、cdk synth
のタイミングで生成されるようです。
これは、cdk
コマンドの実行を効率化するためのものであり、2 回目以降の実行では、このファイルに保存されたコンテキスト・バリューが参照されるようになります。
このキャッシュは自動的に削除されることはないので、例えば、パラメーターストア側の設定値を変更してすぐに反映したいときは、cdk context --clear
コマンドなどで cdk.context.json
の内容をクリアする必要があります。
(おまけ)cdk context コマンド
cdk context
コマンドを引数なしで実行すると、cdk.json
ファイルや cdk.context.json
ファイルで設定されているコンテキスト・バリューの一覧を確認することができます。
$ cdk context # テーブル形式で表示
$ cdk context -j # JSON形式で表示
cdk context --clear
コマンドを実行すると、cdk.context.json
ファイルに保存された情報(キャッシュ)をクリアできます。
cdk.json
ファイルの context
プロパティの値はクリアされません。
$ cdk context --clear # 全クリア
$ cdk context --reset <Name> # キー名を指定してクリア(インデックス指定も可)
Environment variables(環境変数)
CDK コードの中から、process.env.環境変数名
で環境変数の値を取得することができます(設定されていない場合は undefined
になります)。
new MyappStack(app, 'MyappStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION
},
})
環境変数は CDK の仕組みではないので、CDK のコードと Lambda 関数のコードで同じように参照することができますが、環境変数の値は実行時の環境によって変わってくることに注意してください。 あと、環境変数は文字列値しか保持できないので、オブジェクトを持てるコンテキスト・バリューの方が柔軟性があります。 コンストラクト・ツリーの子ノードのみに設定値を伝搬させるという仕組みも、コンテキスト・バリューにしかありません。 これらを考慮すると、基本的には、CDK によるデプロイ用のパラメーターとしては、コンテキスト・バリューの仕組みを使うのがよさそうです。 CDK Developers Guide の Best practices でも、コンストラクトの中での環境変数の参照はアンチパターン であると述べられています(トップレベル、つまり App コンストラクトでの参照はこの限りではないけれど、それ以下の階層へは props で伝搬させていくべき)。
一方、Lambda 関数からは CDK のコンテキスト・バリューを参照することはできないので、間接的にコンテキスト・バリューの値を使いたいときは、次のような感じで Lambda 関数コンストラクトの environment
オプションで値を渡してやる必要があります(Lambda 関数の実行環境の環境変数として設定されます)。
const myLambda = new lambdaNodejs.NodejsFunction(this, 'MyLambda', {
runtime: lambda.Runtime.NODEJS_14_X,
entry: 'lambda/index.ts',
environment: {
S3_BUCKET_NAME: this.tryGetContext('s3BucketName') as string,
S3_OBJECT_KEY: this.tryGetContext('s3ObjectKey') as string,
},
})
CloudFormation parameters(CloudFormation パラメーター)
この仕組みは CDK では非推奨とされています。
cdk deploy
時に、--parameters
オプションを指定することで、CloudFormation パラメーターに相当するキー&バリューを設定することができます。
$ cdk deploy --parameters uploadBucketName=UploadBucket
上記のように指定した CloudFormation パラメーターは、CDK コードの中から次のように参照することができます。
const uploadBucketName = new cdk.CfnParameter(this, 'uploadBucketName', {
type: 'string',
description: 'Name of the bucket to store image files'
})
const myBucket = new s3.Bucket(this, 'uploadBucket', {
bucketName: uploadBucketName.valueAsString
})
ただし、CloudFormation パラメーターはデプロイ時 (cdk deploy
) にしか参照できず、Synthesize (cdk synth
) のタイミングでは有効ではないという制約があります。
なぜなら、CloudFormation テンプレートは Synthesize 時に生成されるものであり、その時点では CloudFormation パラメーターはプレースホルダーとして残しておかなければいけないからです(コンテキスト・バリューのように Synthesize 時に値を展開できない)。
CDK アプリの他の部分とうまく連携がとれないため、コンテキスト・バリューの方を使うことが推奨されています。
応用: コンテキスト・バリューを使って実行環境 (staging/production) を切り替える
アプリの実行環境は、次のような感じで、用途ごとに作成することが多いと思います。
- 本番環境 (production, prod)
- ステージング環境 (staging, stg)
- 開発環境 (develop, dev)
例えば、本番環境用の CloudFormation スタックと、ステージング環境用の CloudFormation スタックは分けて作成することになります。
CDK のコンテキスト・バリューの仕組みを使って、次のように cdk deploy
時にターゲットとする環境を切り替えることができます(targetEnv
というキー名は勝手に決めた名前です)。
$ cdk deploy --context targetEnv=production # 本番環境用スタックの生成
$ cdk deploy --context targetEnv=staging # ステージング用スタックの生成
$ cdk deploy --context targetEnv=development # 開発環境用スタックの生成
あとは、App コンストラクトの CDK コードなどで、targetEnv
の値を使って、各種コンフィグ値を切り替えることができます。
次の例では、config.ts
というファイルに各環境用のコンフィグ情報を記述し、targetEnv
の値で切り替えるようにしています。
export type Config = {
stackName: string
stackDesc: string
imageBucketName: string
tags: {
team: string
targetEnv: string
}
}
export function getConfig(targetEnv: string): Config {
const stackName = `myapp-stack-${targetEnv}`
const stackDesc = `MyApp stack for ${targetEnv}`
switch (targetEnv) {
case 'production':
return {
stackName,
stackDesc,
imageBucketName: 'image-bucket-prod',
tags: {
team: 'ProdTeam',
targetEnv: 'production',
},
}
case 'development':
return {
stackName,
stackDesc,
imageBucketName: 'image-bucket-dev',
tags: {
team: 'DevTeam',
targetEnv: 'development',
},
}
default:
throw new Error(
'Context value [targetEnv] is invalid (use production or development).'
)
}
}
import * as cdk from '@aws-cdk/core'
import { getConfig } from '../lib/config'
import { MyappStack } from '../lib/myapp-stack'
const app = new cdk.App()
// ここでコンテキスト・バリューに基づいて Config 値を決定する
const targetEnv = app.node.tryGetContext('targetEnv') as string
const config = getConfig(targetEnv)
// デフォルトを development とする場合
// const config = getConfig(targetEnv ?? 'development')
// スタックの props として渡したりする
new MyappStack(app, config.stackName, {
env: { account: '123456789012', region: 'ap-northeast-1' },
description: config.stackDesc,
tags: config.tags,
config,
})
import * as cdk from '@aws-cdk/core'
import { Config } from './config'
interface MyappStackProps extends cdk.StackProps {
config: Config
}
export class MyappStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props: MyappProps) {
super(scope, id, props)
// あとは props.config の値を使って煮るなり焼くなり
}