インフラ

【翻訳記事】AWS CDKで複数の環境を設定する4つの方法

Contents

はじめに(訳者より)

本稿は以下の、ブログ記事の翻訳です。
この翻訳記事を書くことに快諾頂いた@der_rehanさんに感謝します。

4 Methods to configure multiple environments in the AWS CDK

多少の意訳を含んでおり、翻訳に際して私のミスがある可能性も十分にご了承ください。もし間違いがありましたら、ご指摘いただけると幸いです。

記事の概要

この記事では、AWS CDKに設定を渡すために使用できる4つの方法を説明します。

最初にContext(cdk.jsonファイル)を使った方法を紹介します。
2番目にYAMLファイルを使う方法、3番目にAWS SSMパラメータストアを使ってSDK(API)呼び出して値を読取る方法を紹介します。
4番目は、ビルドツールとしてGULP.jsを使用して2番目と3番目の方法を組み合わせた方法です。

このブログで使用しているコードは以下にあります。

https://github.com/rehanvdm/cdk-multi-environment

1. Context(CDKが推奨する方法)

最初の方法は、ビルド時に外部変数をCDKに読み込む方法です。(CDKが推奨する方法)

CDKコードとこの方法で使用するcdk.jsonは一緒にコミットされ、構築されているリソースを設定する設定値を持ちます。
この方法は副作用のない、再現性のある一貫したデプロイが保証されます。

Context値をCDKにわたす方法はいくつかあります。
最も簡単な方法は、cdkコマンドラインでオプションとして--context(または-c)使用する方法です。

construct.node.tryGetContext(…)を使用して値を取得できます。
必ず戻り値を検証してください。TypeScript(TS)の型安全性では、実行時に値を読み取ることができません。

詳細については、最後の検証セクションをご覧ください。

多くの変数を渡すことは理想的ではないため、ファイルで設定することもできます。

新しいCDKプロジェクトを開始すると、cdk.jsonには、CDK自体によって使用される値が入力されたプロパティがいくつかあります。
これは、この方法を使用する際の最初の問題点でした。

CDK CLIで使用されるパラメーターを、CDKアプリケーション設定と同じファイルに保存するのは正しくないと感じました。

.jsonファイルを他の場所に保存することも可能であることに注意してください。
詳細は、公式ドキュメント(上記のリンク)を確認してください。

開発環境と本番環境の両方の値を同じファイルに保存しています。
CDK CLIを実行するときに、configとしてcontext変数を渡します。

これはindex.ts内で読み取られ、cdk.jsonファイルで定義されている使用可能な環境の1つを選択します。
上記は、getConfig(...)関数内で実行されます。

各Context値を個別に読み取り、/stacks/lib/build-config.tsにある独自のBuildConfigインターフェイスに割り当てているることに注意しましょう。

BuildConfigインスタンスがすべてのスタックに渡されますが、この例ではスタックは1つです。
また、CDKアプリにタグを追加して、可能な場合はすべてのスタックとリソースにタグを設定します。

リージョンとアカウントをスタックに渡すことで、その特定のスタックを他のアカウントやリージョンにデプロイできます。
(渡された「--profile」引数に、そのアカウントに対する権限がある場合)

MainStackには、Lambdaが1つ含まれています。
環境変数とLambda Insights Layerは、設定ファイルから取得するようになっています。

2. YAMLファイルから設定を読み取る

この方法では、アプリケーションの設定値をcontextファイルから分割し、YAMLファイルに保存します。
ファイルの名前が環境を示します。

次に、getConfig関数のindex.tsを変更して、contextからJSONではなくYAMLファイルを読み取ります。

3. AWS SSMパラメータストアから設定を読み取る

この方法は、AWS SSMパラメータストアだけではなく、サードパーティのAPI/SDK呼び出しを使用して、設定を取得しCDKビルドプロセスで使用できます。

最初の「トリック」は、すべてのコードを非同期関数内にラップしてから実行することです。
これで、スタックが作成される前にasync/await関数を最大限に活用できます。

getConfig(...)関数内で、CLIコマンドを実行するときにプロファイルとリージョンのcontext変数を渡す必要があります。

これは、AWS SDKで使用されるように設定するためです。
これにより、AWSに対して認証済みのAPI呼び出しが行われます。YAMLファイルと全く同じコンテンツでSSMパラメーターストアレコード(以下)を作成しました。
取得後、YAMLファイルの方法と全く同じようにBuildConfigを解析してデータを設定できます。

この方法には、構成ファイルがどのプロジェクトからも独立しており、単一の場所に保存され、複数のプロジェクトで使用できるという利点があります。
例のようにに完全なプロジェクト構成を保存することは、少し異例であり頻繁に行うことではありません。
理想的には、プロジェクトレベルの構成と、すべてのプロジェクトで使用されるグローバル値を保存します。
これについては、次の方法で説明します。

4. 外部ビルドスクリプトを利用する

この例では、上記の方法2と3を利用します。

  • プロジェクト固有の設定(YAML file) AWSプロファイルとリージョンを含むプロジェクトの場合
  • すべてのプロジェクトで使用されるグローバル設定(AWS SSMパラメータストア)

Lambda Insight Layer ARNは、AWS SSMパラメータストアであるグローバル設定にのみ保存されます。
AWSが新しいバージョンのレイヤーをリリースするときに、グローバル設定で一度更新するだけですべてのプロジェクトが次にデプロイされたときに更新されます。

GULP.jsスクリプトを使用して、Nodeで実行しています。
以下を行います。

  1. 環境に応じて、ローカルのYAMLファイルを読み取ります。これはデフォルトでブランチ名になります。
  2. グローバル設定を保持するAWS SSMパラメータ名を(ローカル設定から)取得します。グローバル構成を取得し、ローカル構成に追加します。
  3. AJVパッケージを使用したJSONスキーマを使用して、構成を検証します。
  4. 完全な構成をファイルに書き込み、リポジトリでコミットされるようにします。
  5. npm buildを実行して、CDK TSをJSにトランパイルします。
  6. AWSプロファイルやContext変数などの引数を渡して、CDKコマンドをビルドして実行します。CDKがindex.tsでCloudFormationに合成されると、方法2と同様に、ステップ4で書き込んだ構成が読み取られます。

/config/gulpfile.jsのgenerateConfigメソッド

CDKを構築し、構成を取得して、diff CDK CLIコマンドを実行するgulpタスク

ここで、npm run cdk-diff-devを実行する代わりに以下を実行します。
node node_modules\gulp\bin\gulp.js --color --gulpfile config\gulpfile.js generate_diff

cdk-deploy用は以下です。

node node_modules\gulp\bin\gulp.js --color --gulpfile config\gulpfile.js deploy_SKIP_APPROVAL

上記コマンドでは環境を渡さず、デフォルトでブランチ名で設定していることに注意してください。
ただし、マスターブランチでは、prod configを使用します。GULP.jsファイル内のgetConfig(…)関数を使用すると、明示的に環境を渡すことができます。
このデプロイ方法は、CIツールでも機能します。

index.tsで使用されるgetConfig関数は、AJVとJSONスキーマを使用して検証を行うことを除くと方法2に似ています。

GULP.jsファイルを使用してNodeで実行することの最大の利点の一つは、デプロイメントプロセスがOSに依存しないことです。
私はWindowsを使用しており、ほとんどの人が常にMakeスクリプトとBashスクリプトを記述して、UbuntusWSL2を使用するように強制しているのでこれは私に取って重要です。

このデプロイプロセスは非常に用途が広いです。このGULP.jsメソッドは、IaCツールを使用する前からLambdaコードのみを更新したかったときに使用しました。
それ以来、CluodFormation、SAM、CDKをデプロイするために何らかの形式が使用されています。

補足

Validation

TypeScriptはコンパイル時のみチェックを行います。
つまり、デコードしているYAML/JSONが実際に文字列であるか、実行時に定義されているかはわかりません。
したがって、実行時にセーフガードを手動で確認し配置する必要があります。
方法1から3は、構成が読み取られる関数ensureString(...)を使用して、index.ts内でチェックを実行しました。

以下では、もう少し高度なアプローチを紹介します。
AJVpackageは、BuildConfigファイルのJSONスキーマに対してJSONオブジェクトを検証します。
特定のプロパティが設定されていることを確認し、正しいAWSARNで開始するなどのルールを定義するスキーマファイルを作成できます。

JSONスキーマを作成して最新の状態に保つのは面倒です。そのため、typescript-json-schemaパッケージを使用することにしました。
既存のTypeScript BuildConfigインテーフェースを(/stacks/lib/build-config.ts)をJSONスキーマに変換し、それを/config/schema.jsonのconfigディレクトリに保存します。

プロジェクト構造

Github上のコードを見ると、CDKプロジェクトの初期構造になっていないことに気づくでしょう。

これも意見が別れていますが、初期の構造は私には論理的ではなく、すべてのプロジェクトで常に機能するとは限りません。

すべてのスタックは/stacksに入り、メインのCDK Constructはindex.tsとしてルート上にあり、すべてのアプリケーション固有のコードは/srcに入ります。
/srcディレクトリには、/lambda,/docker, /frontendなどの論理的な単位でサブディレクトリを分けています。

ここに表示されていないのは、時々必要になる/buildディレクトリで、/srcのコードが本番用にビルドされて保存されます。
CDKは/srcの代わりに/buildから読み取ります。

結論(要約)

このブログで使用しているコードは以下にあります。

https://github.com/rehanvdm/cdk-multi-environment

CDKプロジェクトの構成を保存する方法はたくさんあります。
私のお気に入りは、プロジェクトレベルでYAMLファイルとして保存し、ビルドツールとしてGULP.jsスクリプトを使用する方法です。(方法4)

どの方法を選択する場合でも、常に入力を検証することを忘れないでください。