AWS CDKで複数の環境を設定する際の設定値(config)の渡し方

2023.01.10

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

「AWS CDK 環境ごとに変えたい設定はどう表現すればいいんだろう?」

本番環境と開発環境でインスタンスのスペックが違うなど、環境ごとに変えたい設定が発生することがあります。

その際に、AWS CDKではどういった方法で環境差異を表現するか悩むことがあると思います。

今回は個人的によく使う方法を2つ紹介します。

ブログ中のコードは以下になります。

msato0731/cdk-config-sample

1. Context

Contextとは

1つ目は、Contextを使う方法です。

ランタイムコンテキスト - AWS Cloud Development Kit (AWS CDK) v2

CDKのコマンド実行時に、引数で値を渡すことができます。

$ npm run cdk deploy -- -c vpcId=vpc-xxxxxxx-c instanceType="t3.micro"

受け取る側は、construct.node.tryGetContext(…)といった形で受け取れます。

index.ts

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { MainStack } from '../lib/main-stack';

const app = new cdk.App();

new MainStack(app, 'MainStack', {
    env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
    vpcId: app.node.tryGetContext("vpcId"),
    instanceType: app.node.tryGetContext("instanceType"),
});

cdk.jsonにconfigを書いてみる

最初にコマンドで引数を渡す例を出しましたが、ファイル(cdk.json)に記載することで引数を渡すこともできます。

最初の例では、デフォルトのAWSアカウントID・リージョンを使用してCDKを実行しますが、デプロイ環境間違い防止のためにはアカウントID・リージョンに関してもcdk.json に書いた方が安全そうです。

上記を踏まえて変更してみましょう。

以下は開発環境(dev)と本番環境(prd)でVPC IDやインスタンスIDを切り替えたい場合のサンプルになります。

cdk.json

{
  "app": "npx ts-node --prefer-ts-exts bin/index.ts",
  "watch": {
    "include": [
      "**"
    ],
    "exclude": [
      "README.md",
      "cdk*.json",
      "**/*.d.ts",
      "**/*.js",
      "tsconfig.json",
      "package*.json",
      "yarn.lock",
      "node_modules",
      "test"
    ]
  },
  "context": {
    "dev": {
      "vpcId": "vpc-XXXXXXX",
      "instanceType": "t3.micro",
      "env": {
        "account": "0000000000",
        "region": "ap-northeast-1"
      }
    },
    "prd": {
      "vpcId": "vpc-XXXXXXX",
      "instanceType": "t3.large",
      "env": {
        "account": "111111111",
        "region": "ap-northeast-1"
      }
    }
  }
}

環境情報を取得して、スタックに渡す様にしています。 前の例では、プロパティごとにtryGetContextを実施していましたが、envValsに入れて参照する形にしました。

Configを読み込む部分はBLEAを参考にしました。

aws-samples/baseline-environment-on-aws

index.ts

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { MainStack } from '../lib/main-stack';

const app = new cdk.App();

const argContext = 'environment';
const envKey = app.node.tryGetContext(argContext);
if (envKey == undefined)
  throw new Error(`Please specify environment with context option. ex) cdk deploy -c ${argContext}=dev`);
const envVals = app.node.tryGetContext(envKey);
if (envVals == undefined) throw new Error('Invalid environment.');

const env = { account: envVals['env']['account'], region: envVals['env']['region'] };

new MainStack(app, 'MainStack', {
    env,
    vpcId: envVals['vpcId'],
    instanceType: envVals['instanceType']
});

環境(devprd)の切り替えは、コマンドラインでContext渡しています。

AWS認証情報から環境を判断して、自動的に取得するようにするような処理も実装すれば環境名もContextで渡す必要もないかもしれません。

実装がシンプルでコマンド実行時に手動で環境名を打った方が、ミスが起きないと思い現在の実装にしています。

$ npm run cdk deploy -- -c environment=dev

ちなみにpacakge.jsonに書いてnpmスクリプトを作っておけば、短いコマンドで実行可能です。

{
  "name": "1-context",
  "version": "0.1.0",
  "bin": {
    "1-context": "bin/index.js"
  },
  "scripts": {
    "build": "tsc",
    "watch": "tsc -w",
    "test": "jest",
    "cdk": "cdk",
    "cdk:dev": "cdk -c environment=dev",
    "cdk:prd": "cdk -c environment=prd"
  },
  "devDependencies": {
    "@types/jest": "^29.2.4",
    "@types/node": "18.11.15",
    "jest": "^29.3.1",
    "ts-jest": "^29.0.3",
    "aws-cdk": "2.59.0",
    "ts-node": "^10.9.1",
    "typescript": "~4.9.4"
  },
  "dependencies": {
    "aws-cdk-lib": "2.59.0",
    "constructs": "^10.0.0",
    "source-map-support": "^0.5.21"
  }
}
$ npm run cdk:dev deploy

メリット・デメリット

  • メリット:
    • 実装がシンプル
  • デメリット:
    • パラメータの数が増えるとcdk.jsonが肥大化する恐れあり

cdk.jsonに全ての環境のパラメータを入れる必要があります。

環境ごとに変えたいパラメータの数が多いとcdk.jsonが肥大化して見づらくなってしまうかもしれません。(そもそも、大量のパラメータを定義するなという感じはありますが。。)

環境ごとにファイルを分けれたらよさそうですが、現時点ではcdk.jsonを環境ごとに分ける方法はなさそうです。(2023/1/6時点)

2. 外部ファイルの利用(TSファイル)

2つ目は、外部ファイルに設定値を書き出すパターンです。

このパターンでも、環境名を渡すためにContextを使用します。(cdkコマンドの実行方法は同様です。)

env配下に環境ごとに設定ファイルを作成します。

TypeScriptで書けるため、以下のInstanceTypeのように型を使用することができます。 直接t3.microの様に書くパターンと比べてタイプミスを減らすことができます。

env/dev.ts

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

export const config = {
    vpcId: "vpc-XXXXXX",
    instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO)
};

configを使う側は以下のようになりました。

Contextで渡される環境名をもとに、該当するConfigを取得するといった処理を追加しました。

bin/index.ts

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { MainStack } from '../lib/main-stack';
import { config as devProperties } from '../env/dev';
import { config as prdProperties } from '../env/prd';

const app = new cdk.App();

const argContext = 'environment';
const envKey = app.node.tryGetContext(argContext);
if (envKey == undefined)
  throw new Error(`Please specify environment with context option. ex) cdk deploy -c ${argContext}=dev`);
const envVals = app.node.tryGetContext(envKey);
if (envVals == undefined) throw new Error('Invalid environment.');

const env = { account: envVals['env']['account'], region: envVals['env']['region'] };

const properties = getProperties(envKey)

new MainStack(app, 'MainStack', {
    env,
    ...properties
});

function getProperties(envKey: String) {
  if (envKey === "dev") {
    return devProperties
  } else if (envKey === "prd") {
    return prdProperties
  }  else {
    throw new Error("No Support environment")
  }
}

メリット・デメリット

  • メリット:
    • 環境ごとにconfigファイルを分割できる
    • 設定値をもつファイル(config)で型を使用できる
  • デメリット:
    • cdk.jsonのみを使用するパターンに比べると若干記述量が増える

この方法では、環境ごとにconfigファイルを分割できます。 Contextパターンと違いJsonではなく、プログラミング言語で記述できるためより自由度の高い分割も可能です。

また、TypeScriptなど型を使用できるプログラミング言語を使用している場合は、Configファイル内で型を使用できることもメリットです。

あまりデメリットもないような気がしますが、強いてあげればcdk.jsonパターンよりはプログラミング言語の知識が必要になることがあげられます。

この方法について、もっと詳しく知りたい方は以下の記事をご覧ください。

おわりに

AWS CDKで複数の環境を設定する際の設定値(config)の渡し方の紹介でした。

個人的によく使う方法を2つあげました。 2つの方法はどちらもシンプルで、比較的取り入れやすいかと思います。

他にもSSM Parameter Storeを使う方法や、外部ファイルの利用(yaml)パターンなどいろいろな方法があります。

他の方法が気になる方は、参考リンクにあるブログ記事や「The CDK Book」の7章をご確認ください。

以上、AWS事業本部の佐藤(@chari7311)でした。

参考・関連リンク