【AWS CDK】「CDKにおけるパラメータの外だし方法 ~node-config編~」

## はじめに
IaCを作成する時は、環境依存になるようなパラメータは外出ししてまとめて管理したいですよね？
パラメータを外出ししてまとめて管理しておくと、読み込む外出しパラメータ情報を切り替えるだけで、色々な環境をDeploy出来るので、構築コスト的にも好ましいですよね。

CDKには、パラメータを外出ししてまとめて管理する方法はいくつかあるんですが、その中から個人的に使っている以下2つをご紹介させて頂きます。
- node-configを利用する方法
- 自前TypeScriptで実装する方法

本記事では、「node-configを利用する方法」に関してご紹介させて頂きます。

「自前TypeScriptで実装する方法」に関しては、以下のリンクの記事をご確認ください。
[自前TypeScriptで実装する方法](https://qiita.com/akwayne/items/619e34b4d94e4ecd542d)

※ 本ブログに記載した内容は個人の見解であり、所属する会社、組織とは全く関係ありません。

## node-configとは
「node-config」は、Node.jsの拡張ライブラリです。
[node-config](https://github.com/node-config/node-config)

本ライブラリを使用すると、パラメータファイルを事前に準備しておくことにより、アプリケーション実行時に環境変数NODE_ENVの値に紐ついたパラメータファイルを読み込み引き渡してくれます。
つまり、パラメータ等を外出しのファイルで管理することが出来ます。
また、外だしのパラメータファイルは、環境変数NODE_ENVの値に紐ついているので、環境が増加しても環境変数NODE_ENVの値とセットのパラメータファイルを追加するだけで大丈夫なため、コード変更が伴わず、パラメータをコードから切り離すことが出来ます。
つまり、CDKコード内でIF等の分岐処理で環境毎のパラメータをかき分ける実装が不要となりシンプルなコードにすることが出来ます。

言葉だけだと伝わり辛いと思いますので、実際の使い方を紹介させて頂きます。
使い方を見て頂けますと具体的なイメージが湧くと思われます。

## node-config導入方法
「node-config」は下記コマンドでインストールできます。
~~~console
$ npm install @types/config config
~~~~

環境毎のパラメータファイルはプロジェクト直下にconfigディレクトリ格納するため、作成します。
~~~console
$ mkdir ./config
~~~~

これで事前準備は完了です。
実際にこれを用いて実装してみましょう。

## node-configを用いた実装方法
実装は以下3ステップに分かれます。
- パラメータファイルを作成する
- Stackコードに引き渡す
- Stackコードからパラメータを受領する

それぞれのセクションに分けてご紹介させて頂きます。

### パラメータファイルを作成する
パラメータファイルは環境毎に作成し、インストール時に作成したconfigディレクトリに配置します。
今回はサンプルとして「Dev.ts」という名前で作成します。
この名前を環境変数NODE_ENVに入れるため、重要な要素となります。
環境毎等の名前にしてしまうのが、使いやすいかと思われます。
~~~typescript:Dev.ts
//共通パラメータ
export const common = {
  project: "parama-sample",
  envPrefix: "dev",
  accountId: "xxxxx"
};
//NW系パラメータ
export const nw = {
  vpcCider: "10.0.0.0/24",
  vpcMaxAZs: 2,
  vpcPublicSubnetCidrMask: 27,
  vpcPrivateSubnetCidrMask: 27,
};

//Aurora1用パラメータ
export const aurora1 = {
  clusterParam: {
    timezone: "Asia/Tokyo",
  },
  maxAurora: 4,
  minAurora: 2, 
};
~~~

上記のイメージのように目的毎に箱を作っていく形になります。
箱の中はkey:value形式であれば自由に組むことができ、Auora1の箱のClusterParamのようにネストされた構造を組むこともできます。

### Stackコードに引き渡す
このサンプルでは、Stackコードにconfigモジュールインポートし、configのget Methodを用いて、後段のConstructコードに引き渡しています。
~~~typescript:sample-stack.ts
 // node-configモジュールのインポート
 import config = require("config");

export class SampleStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: cdk.StackProps) {
    super(scope, id, props);
    
    //NW系Contructクラス
    const nw = new Nw(this, "Nw", {
      envName: config.get("common.envPrefix"),
      vpcCider: config.get("nw.vpcCider"),
      vpcMaxAZs: config.get("nw.vpcMaxAZs"),
      vpcPublicSubnetCidrMask: config.get("nw.vpcPublicSubnetCidrMask"),
      vpcPrivateSubnetCidrMask: config.get("nw.vpcPrivateSubnetCidrMask"),
      });

    //Aurora1用Constructクラス
    const aurora1 = new aurora1(this, "aurora1", {
      vpc: nw.myVpc,
      envName: config.get("common.envPrefix"),
      clusterParam: config.get("aurora1.clusterParam"),
      maxAurora: config.get("aurora1.maxAurora"),
      minAurora: config.get("aurora1.minAurora"),
~~~

**import config = require("config");**
ここでは、node-configモジュールをインポートしています。
requireを使っているのは、このモジュールがESモジュールでなく、CommonJSモジュールのためです。

Node.jsでは、多くのパッケージやモジュールがCommonJS方式で書かれています。
そのため、require()関数を使用してモジュールをインポートします。
 
**envName: config.get("common.envPrefix"),**
node-configモジュールのgetメソッドを使用して、Keyを指定(common.envPrefix)し、Dev.tsからデータを取得しています。

## Stackコードからパラメータを受領する
ここからは、node-configモジュールを用いた、パラメータの引き渡しは関係ありません。
Stackから各Constructsのコードにパラメータは渡されているので、それを受領する部分となります。

~~~typescript:sample-constructs.ts
export interface NwProps {
  envName: string;
  vpcCider: string;
  vpcMaxAZs: number;
  vpcPublicSubnetCidrMask: number;
  vpcPrivateSubnetCidrMask: number;
}

export class Nw extends Construct {
  public myVpc: ec2.Vpc;

  constructor(scope: Construct, id: string, props: NwProps) {
    super(scope, id);

    // VPCの作成
    this.myVpc = new ec2.Vpc(this, "Vpc", {
      vpcName: `${props.envName}-Vpc`,
      ipAddresses: ec2.IpAddresses.cidr(props.vpcCider), 
      maxAzs: props.vpcMaxAZs, 
      subnetConfiguration: [
        {
          cidrMask: props.vpcPublicSubnetCidrMask, 
          name: "Public",
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: props.vpcPrivateSubnetCidrMask, 
          name: "Private",
          subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
        },
      ],
    });
~~~
**export interface NwProps {**
ここでは、このConstructsのインターフェースを定義しています。
Stackコードでは、このインターフェースを満たすようにpropsを渡してあげる必要があります。

**public myVpc: ec2.Vpc;**
StackコードのAurora1はNWからmyVpcが引き渡されています。
これを実現するためにここでpublicしています。
    
**ipAddresses: ec2.IpAddresses.cidr(props.vpcCider),**
Stackコードから渡されたvpcCiderを引き渡しています。

## node-configを用いた実行方法
外だししたパラメータファイルからパラメータマップを行うために、環境変数NODE_ENVに環境名を設定します。
環境名はconfig/配下に作成するパラメータファイル名(Dev.ts)と同一のものを設定します。
~~~console
$ export NODE_ENV=Dev
~~~~

これで、Dev.tsのパラメータをCDK実行時にマップしてくれます。

パラメータ外だしに関連する作業は以上になります。
ここから先は、普段のCDK deploy時と同じ作業をして頂ければ問題ありません。


## まとめ
以上が、node-configを用いたパラメータファイルの外だし方法になります。
環境変数に応じて、パラメータファイルを切り替えられるのが、非常に手軽で便利ですよね。
個人的には、CodeBuildでCDK deployをする際の親和性も非常に高いと感じています。
ご利用されたい場合は、ご紹介させて頂いた内容を参考にして頂けると幸いです。

この環境変数に応じて、パラメータファイルを切り替えが個人的にすごく魅力に感じており、それを取り込んだ自前TypeScript版は以下のリンクの記事でご紹介させて頂いてます。
[自前TypeScriptで実装する方法](https://qiita.com/akwayne/items/619e34b4d94e4ecd542d)
※ 主に使っているのはこっちです。余計なモジュール入れるのもな。。。ってことで

ご興味がありましたら、こちらも併せて参照して頂けますと幸いです。

※ 本ブログに記載した内容は個人の見解であり、所属する会社、組織とは全く関係ありません。