実践!AWS CDK #4 Context

題字・息子たち
2021.05.24

はじめに

今回は AWS CDK の Context について学んでいきましょう。
簡単に言うとこれは CFn の パラメータ のように使える機能です。

前回の記事はこちら。

Context とは

ざっくり解説。

  • スタックや Construct に関連付けできるキーと値のペア
  • キーと値の型は string
    • 他の型にしたい場合は変換処理が必要
  • 6 つ の異なる方法で CDK アプリに提供される
    1. 現在の AWS アカウントから自動的に
    2. CDK コマンドの --context オプション
    3. プロジェクトの cdk.context.json ファイル
    4. プロジェクトの cdk.json ファイル
    5. ~/.cdk.json ファイルの context キー
    6. construct.node.setContext メソッド

この機能を利用してシステム名や環境を動的に変更してみましょう。

パラメータじゃダメなの?

ダメです。Context を使いましょう。

CfnParameter というクラスも存在するのですが、AWS 公式から 非推奨 というお言葉をいただいております。

In general, we recommend against using AWS CloudFormation parameters with the AWS CDK. Unlike context values or environment variables, the usual way to pass values into your AWS CDK apps without hard-coding them, parameter values are not available at synthesis time, and thus cannot be easily used in other parts of your AWS CDK app, particularly for control flow.

not available at synthesis time、つまり cdk synth コマンド実行時に値が取得できません。(cdk deploy コマンド実行時には取得できるのですが)

それでもオレはパラメータを使いたいんだ!という方は以下のリンクから利用方法を習得してください。
私は Context を使います。

使い方

ここから Context の利用方法を見ていきましょう。

値の設定

本シリーズでは次の方法で値を設定していきます。

  • プロジェクトの cdk.json ファイル
    • デフォルト値の設定に利用
    • このファイルはプロジェクト作成時に自動生成される(既にある)
  • CDK コマンドの --context オプション
    • デフォルト値以外の設定に利用
    • cdk synthcdk deploy 実行時に使用する
    • -c でも可

値の取得

値の取得には以下のメソッドを利用します。

  • construct.node.tryGetContext

先頭の construct はインスタンスです。上記 6 つの方法はいずれも暗黙的に App Construct に設定されるため、アプリ内のすべての Construct インスタンスで Context の値を取得することができます。

また、値を取得できなかった場合の結果は undefined となります。

実装

では実際に値の設定と取得を試してみましょう。

cdk.json を以下のように編集します。
既にいくつか値が設定されているところに追記します。(14 行目の行末カンマもお忘れなく)

cdk.json

{
  "app": "npx ts-node --prefer-ts-exts bin/devio.ts",
  "context": {
    "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
    "@aws-cdk/core:enableStackNameDuplicates": "true",
    "aws-cdk:enableDiffNoFail": "true",
    "@aws-cdk/core:stackRelativeExports": "true",
    "@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true,
    "@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true,
    "@aws-cdk/aws-kms:defaultKeyPolicies": true,
    "@aws-cdk/aws-s3:grantWriteWithoutAcl": true,
    "@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": true,
    "@aws-cdk/aws-rds:lowercaseDbIdentifier": true,
    "@aws-cdk/aws-efs:defaultEncryptionAtRest": true,

    "systemName": "devio",
    "envType": "stg"
  }
}

これがデフォルト値となります。

次にコードを修正します。

devio-stack.ts

import * as cdk from '@aws-cdk/core';
import { CfnVPC } from '@aws-cdk/aws-ec2';

export class DevioStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const systemName = this.node.tryGetContext('systemName');
    const envType = this.node.tryGetContext('envType');

    new CfnVPC(this, 'Vpc', {
      cidrBlock: '10.0.0.0/16',
      tags: [{ key: 'Name', value: `${systemName}-${envType}-vpc` }]
    });
  }
}

これでリソース名に設定していた システム名環境 が動的に変更可能となりました。

確認

まずは cdk synth コマンドを実行してみましょう。

$ cdk synth

Resources:
  Vpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      Tags:
        - Key: Name
          Value: devio-stg-vpc
~ 省略 ~

デフォルト値が取得され、リソース名が正しく設定されています。

次に --context オプションありで cdk synth コマンドを実行してみます。

$ cdk synth -c systemName=starwars -c envType=prd

Resources:
  Vpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      Tags:
        - Key: Name
          Value: starwars-prd-vpc
~ 省略 ~

すばらしい!
きちんと指定したとおりにプログラムが動いていますね。

デプロイ時にも適用する場合は cdk deploy コマンドに同じオプションを指定してください。

このように CFn でいうパラメータのように利用できる機能が、今回ご紹介した Context となります。

注意

この Context ですが、テスト実行時には値が取得できません。(cdk.json が読み込まれないようです)
また、テストの実行は npm コマンドであり cdk のコマンドではないため --context オプションも当然ながらサポートされていません。よってテスト時の Context の値は undefined が来ることを想定したものにしましょう。

VPC のテストの場合はこのような形になります。

test/devio.test.ts

test('Vpc', () => {
  const app = new cdk.App();
  const stack = new Devio.DevioStack(app, 'DevioStack');

  expect(stack).to(countResources('AWS::EC2::VPC', 1));
  expect(stack).to(haveResource('AWS::EC2::VPC', {
    CidrBlock: '10.0.0.0/16',
    Tags: [{ 'Key': 'Name', 'Value': 'undefined-undefined-vpc' }]
  }));
});

少し気持ち悪いですが、ここは我慢するしかなさそうです。

どうしてもテスト時に Context を設定したい場合は cdk.App() のイニシャライザーに Context を指定することができます。これを利用した Context 用のテストコードを 1 つ追加し、今回はおしまいにしたいと思います。

test/devio.test.ts

test('Context', () => {
  const app = new cdk.App({
    context: {
      'systemName': 'starwars',
      'envType': 'prd'
    }
  });
  const stack = new Devio.DevioStack(app, 'DevioStack');

  expect(stack).to(haveResource('AWS::EC2::VPC', {
    Tags: [{ 'Key': 'Name', 'Value': 'starwars-prd-vpc' }]
  }));
});

GitHub

今回のソースコードは コチラ です。

おわりに

今回は Context というこちらも AWS CDK ならではの機能紹介でした。CFn と違う部分はありますが、実際に使っていきながら徐々に慣れていきたいと思います。

さて、ベースが固まってきたので環境構築を再開していきましょう。

次回のお題は「サブネット」です。

リンク