AWS JAPAN APN ブログ

AWS CDK による環境別パラメータ管理とリソース命名戦略 – NRI 実案件での設計手法と工夫

本稿は、以下 3 名による共著です。

  • 株式会社野村総合研究所 – プラットフォームサービス開発一部 シニアテクニカルエンジニア 八十田 周作
  • 株式会社野村総合研究所 – プラットフォームサービス開発一部 エキスパートテクニカルエンジニア 平野 裕昭
  • アマゾン ウェブ サービス ジャパン合同会社 – シニアパートナーソリューションアーキテクト 江成 篤

はじめに

Infrastructure as Code(以下、IaC)は、クラウドインフラの構築・管理における重要なプラクティスとして広く認知されています。特に AWS Cloud Development Kit(以下、CDK)は、TypeScript や Python などの一般的なプログラミング言語でインフラを定義できる特徴を持ち、開発者にとって親しみやすいツールとして注目を集めています。

本稿では、AWS の「DevOps のコンサルティングコンピテンシープログラム」認定パートナーである野村総合研究所(以下、NRI)の実プロジェクトにおける CDK の活用事例を紹介します。具体的には、開発と本番などの複数環境を効率的に構築・管理するための設計手法と、既存の AWS 環境を IaC へ移行する際の実践的なアプローチについて解説します。特に、環境設定の柔軟な切り替え機能、AWS リソースの命名について技術的な工夫に焦点を当てています。これらの知見は、CDK を活用した大規模プロジェクトの設計・運用に直接役立つものと考えています。本事例を通じて、CDK によるインフラ管理の効率化に向けた一つの取り組み例として、参考にしていただければ幸いです。

AWS CDK とは

IaC とは、インフラ構成をコードとして管理する手法です。採用する主な利点として、環境間の一貫性確保、バージョン管理による変更追跡、デプロイの自動化、迅速な環境構築、チーム間のコラボレーション促進、コンプライアンス対応の容易化が挙げられます。

AWS の主要 IaC サービスには、JSON/YAML 形式のテンプレートを使用する AWS CloudFormation (以下、CloudFormation)、プログラミング言語でインフラを定義する CDK、サーバーレスアプリケーション向けの AWS Serverless Application Model (以下、SAM) があります。特に、CDK は TypeScript、Python、Java などの言語でインフラを定義でき、再利用可能なコンポーネント(コンストラクト)を作成可能です。レイヤー 1(CloudFormation リソースの直接マッピング)、レイヤー 2(使いやすい高レベル抽象化)、レイヤー 3(複数リソースを組み合わせたパターン)の 3 つのレイヤーのコンストラクトを提供し、型チェックやコード補完などの IDE 機能を活用できる点が特徴です。CDK は複雑なインフラ管理や DevOps 環境で特に効果的です。CDK は従来の JSON/YAML による定義と比べて、より柔軟で保守性の高いインフラ定義を可能とし、開発者にとって親しみやすい IaC の実装方法を提供します。

AWS 構成

今回は例として、インターネットからのアクセスを Application Load Balancer(以下、ALB)で受け付け、複数のアベイラビリティゾーンに配置されたプライベートサブネット内の Amazon EC2(以下、EC2)インスタンスで処理を行い、Amazon Aurora データベースと連携するアプリケーションを想定しています。システム全体の監視には Amazon CloudWatch を利用し、ログの収集やメトリクスの監視、アラート通知などを行います。また、アプリケーションからのメール送信には Amazon Simple Email Service(以下、SES)を使用しています。

CDK 活用箇所の紹介

今回の事例では、全リソースを CDK 化することを目指しました。ただし、SES のみ CDK 化することを見送っています。理由は SES はドメイン検証やメール送信制限の引き上げ申請といった手動作業が必要になることに加え、一度設定すれば頻繁に変更されるものではなく、CDK でコード化するメリットが相対的に低いと判断したためです。

複数環境を作成するための工夫

本プロジェクトでは、開発・検証・性能テスト・本番といった複数の環境を利用する必要がありました。特にテスト工程が重なるタイミングでは新しい環境を迅速に用意する必要があり、性能テスト環境ではテスト実施時のみ本番スペックで環境を構築する必要があったためです。この背景から、「1つのコードから複数環境を構築できること」を重要な要件と定め、その実現のために 3 点の工夫を凝らしました。

  1. 環境設定ファイルを TypeScript ファイルとして準備
  2. CDK Context を利用した環境設定ファイルの切り替え
  3. 環境設定ファイルへのアクセスの簡素化

実装方針と品質担保の考え方

本プロジェクトでは、コンストラクトの再利用性よりも、プロジェクト固有の要件を迅速に実装し、メンバーが理解・運用しやすいことを優先しました。筆者がこれまで担当したプロジェクトでは、基本的に AWS が提供するレイヤー 2 コンストラクトをそのまま利用してきました。レイヤー 2 を組み合わせて自作したコンストラクトも作成しましたが、これらはプロジェクト固有の要件に特化しており、他プロジェクトで再利用することは想定していません。また、CDK コードに対する単体テストは、開発初期に大きな工数を必要とすることから実装していません。メンバーのスキルや開発期間に制約があることを踏まえると、単体テストを書くよりも、運用上の工夫によって品質を担保する方が効率的であると判断しました。具体的には、以下の方法で品質を確保しています。

  • 初回デプロイ
    • 実際に AWS 環境へデプロイし、結果をパラメータシートと突合することでコードの妥当性を検証
  • 2 回目以降のデプロイ
    • cdk diff を利用し、変更点をレビューすることで目視確認の工数を削減

このように、テストや運用の工夫によって、再利用性や単体テストを犠牲にしても十分に品質を担保できると判断しました。

CDK に与える環境ごとのパラメータを TypeScript ファイルで管理

環境ごとに変更したいパラメータ(例:EC2 インスタンスタイプやネットワークアドレスなど)は TypeScript ファイルとして管理しています。本稿では、このファイルを環境設定ファイルと呼びます。CDK のデプロイ時には、コマンドラインで CDK Context(例:-c ENV=DEV)を指定することで、どの環境設定ファイルを参照するかを切り替えています。

CDK では cdk.json に環境ごとのパラメータを記載する方法もありますが、TypeScript で環境設定を管理することで、IDE の型補完やリファクタリングなどの支援機能を活用でき、より生産性高く実装できるため、この方式を採用しています。さらに、例えば EC2 のインスタンスタイプ指定に ec2.InstanceType 型を利用することで、不正なインスタンスタイプを誤って指定してしまうミスを防ぐことができます。具体的な実装例は次の章をご覧ください。

CDK Context を利用した環境設定ファイルの切り替え

CDK Context を利用して環境設定ファイルを切り替える方法についてコード例を示して解説します。実装のポイントは以下のとおりです。

  • 環境設定の統一インターフェース: IMyEnvironment インターフェースにより、全環境で共通の設定項目を定義し、型安全性を確保
  • Context 値による設定切り替え: cdk deploy -c ENV=DEV のようにコマンド実行時に環境を指定し、対応する設定ファイルを自動選択

// lib/utils/env-utils.ts

import { Node } from ‘constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
// 環境ごとの設定ファイルをインポート
import { devEnv } from '../env/dev';
import { prodEnv } from '../env/prod';
/**
 * 環境設定を表すインターフェース。
 * CDK 内で環境別に変更したいパラメータをこのインタフェースで指定します。
 *
 * @property {string} environment - 環境タイプ(例: 'dev', 'prod')
 * @property {string} region - リージョン
 * @property {ec2.InstanceType} ec2InstanceType - EC2 インスタンスタイプ
 * @property {string} amiId - EC2 の AMI ID
 */
export interface IMyEnvironment {
  environment: string; // 例: 'dev', 'prod'
  region: string;
  ec2InstanceType: ec2.InstanceType;
  amiId: string;
}

/**
 * ENV_CONFIGS は IMyEnvironment インターフェースを実装したオブジェクト
 *
 * 環境ごとの設定をマップとしてまとめる
 * 後述している環境設定ファイル(env/dev.ts と env/prod.ts)から設定
 */
const ENV_CONFIGS: { [key: string]: IMyEnvironment } = {
  DEV: devEnv,
  PROD: prodEnv,
};

export class EnvUtils {
  /**
   * CDK Context から環境設定情報を取得します。
   * CDK 実行時に `-c ENV=DEV` のように指定された環境名を基に設定をロードします。
   *
   * @param node - CDK のノードオブジェクト
   * @returns 解決された環境設定情報
   * @throws {Error} 環境 Context が指定されていない、または無効な場合
   */
  static getEnvironment(node: Node): IMyEnvironment {
    const envContext = node.tryGetContext('ENV');
    if (!envContext) {
      throw new Error("CDK context 'ENV' is required. E.g., `cdk deploy -c ENV=DEV`");

    }
    if (!(envContext in ENV_CONFIGS)) {
      throw new Error(`Invalid environment specified: ${envContext}. Must be one of ${Object.keys(ENV_CONFIGS).join(', ')}.`);
    }
    return ENV_CONFIGS[envContext];
  }
}

各環境のファイル例

// lib/env/dev.ts
/**
 * 開発環境(dev)のパラメータ
 * @param environment 環境識別子 - 開発環境
 * @param region AWS リージョン - 東京
 * @param ec2InstanceType EC2 インスタンスタイプ - 開発用の小型インスタンス
 * @param amiId AMI ID - 開発環境用
 */
export const devEnv: IMyEnvironment = {
  side: 'dev',
  region: 'ap-northeast-1',
  ec2InstanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
  amiId: 'ami-xxxxxxxxxxxxxxx',
};

// lib/env/prod.ts
/**
 * 本番環境(prod)のパラメータ
 * @param side 環境識別子 - 本番環境
 * @param region AWS リージョン - 東京
 * @param ec2InstanceType EC2 インスタンスタイプ - 本番用の大型インスタンス
 * @param amiId AMI ID - 本番環境用
 */
export const prodEnv: IMyEnvironment = {
  side: 'prod',
  region: 'ap-northeast-1',
  ec2InstanceType: ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE),
  amiId: 'ami-yyyyyyyyyyyyyyy',
};

これにより、環境ごとの設定ファイルの準備は完了です。

CDK コード内で環境別パラメータにアクセスするコード例は以下のとおりです。

const env = EnvUtils.getEnvironment(this.node);
env.environment  // -c ENV=DEV と指定した場合は「dev」が格納されている。

環境設定ファイルへのアクセスを容易にする工夫

実際に環境別設定にアクセスするのはスタックやコンストラクト作成時です。

前の章で紹介した環境別パラメータにアクセスするコードをスタックやコンストラクトの基底クラス側に実装することで、実装クラス側でのアクセスを容易にする工夫をしています。

// lib/util/base-constructs.ts
import { Construct } from 'constructs';
import * as cdk from 'aws-cdk-lib';
import { EnvUtils, IMyEnvironment } from '../lib/utils/env-utils'; // ユーティリティクラスをインポート


export class BaseConstruct extends Construct {


  /**
   * 環境別定義を保持する変数
   * 実装クラス側でこの変数にアクセスすることにより、自動的に環境別設定にアクセスすることができる。
   */
  protected readonly env: IMyEnvironment;


  constructor(scope: Construct, id: string) {
    super(scope, id);
    this.env = EnvUtils.getEnvironment(this.node);
  }
}

コンストラクト実装クラス内では以下のようにしてアクセスします。

// src/construct/ec2-construct.ts
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import { BaseConstruct } from '../lib/utils/base-construct';
import { EnvUtils, IMyEnvironment } from '../lib/utils/env-utils'; // ユーティリティクラスをインポート
import { ResourceType } from '../lib/utils/base-construct'; // ResourceType をインポート


interface MyEC2InstanceProps {
  instancePurpose: string; // インスタンスの用途 (例: 'web', 'app', 'batch')
  vpc: ec2.IVpc; // インスタンスをデプロイする VPC
}


export class MyEC2Instance extends BaseConstruct {
  public readonly instance: ec2.Instance;


  constructor(scope: Construct, id: string, props: MyEC2InstanceProps) {
    super(scope, id);
    // 環境別パラメータから取得した EC2 インスタンスタイプと AMI ID を使用
    this.instance = new ec2.Instance(this, 'Instance', {
      vpc: props.vpc,
      instanceType: this.env.ec2InstanceType, // 環境別設定から取得したインスタンスタイプを使用
      machineImage: ec2.MachineImage.genericLinux({
        [this.env.region]: this.env.amiId // 環境別設定から取得した AMI ID を使用
      }),
    });
  }
}

サンプルコードの量から実装が大変に思えるかもしれませんが、ユーティリティクラスや基底クラスは一度作成すれば複数のプロジェクトで再利用できるものです。

CDK アプリケーションごとに新たに作成する必要があるのは、環境別パラメータを定義するインタフェースと各環境用のパラメータファイルのみです。

レイヤー 2 コンストラクト が要求するパラメータ変数など 、TypeScript による型定義がされているため、IDE のコード補完も活用でき生産性高く実装できます。

本稿で紹介する環境設定のアプローチには、以下のような利点と課題があります。実際のプロジェクトで採用する際は、これらの特徴を理解した上で判断することをお勧めします。

本事例の CDK 環境設定方式の Pros/Cons (メリット/デメリット)

本事例における CDK 環境設定方式の Pros と Cons を以下に整理します。これは CDK そのものの制約ではなく、プロジェクトの特性に合わせて意図的に選択した設計上のトレードオフを示しています。

Pros

  • TypeScript 初学者にとっての扱いやすさ:環境パラメータがすべて this.env にまとまっているため、各コンストラクトに個別にパラメータを渡す処理を自分で書く必要がありません。また、IDE(統合開発環境)の型補完機能を活用できるので、設定項目入力時のミスを減らせます。TypeScript に詳しくない人でも、既存の環境設定ファイルをコピーして値を変更するだけで新しい環境を作成できる点も初学者に優しいポイントです。
  • 設定の一元管理:各環境の設定ファイル(dev.ts, prod.ts)を並べて比較することで、環境間の違いを一目で確認できます。
  • デプロイ作業の標準化:cdk deploy -c ENV=DEV のようにシンプルなコマンドで環境を切り替えられます。どの環境にデプロイする場合も同じ手順で実行でき、オペレーションミスを防げます。

Cons

  • テスト容易性の低下:環境ごとのパラメータを一元的に管理できる反面、テストでは IMyEnvironment を満たすオブジェクトを必ず用意しなければなりません。例えば、EC2 インスタンスのみを扱う コンストラクト をテストする場合でも、Amazon RDS(以下、RDS)、AWS Lambda、Amazon VPC など、そのテストには無関係な全ての環境パラメータを含む IMyEnvironment オブジェクトを作成しなければなりません。また、特定のコンストラクトのテストでも、そのテストに無関係な環境パラメータまで含めた設定を定義しなければなりません。
  • コンストラクトの再利用性の制約:各コンストラクトが特定プロジェクトの IMyEnvironment に強く依存するため、他のプロジェクトでの再利用が困難になります。汎用的な コンストラクトライブラリとして公開することが難しくなります。

リソース名を生成する工夫

前提となる考え方

AWS CDK のベストプラクティスでは、通常リソース名の明示的な指定は推奨されていません。これは、CDK が自動生成する一意な名前により、リソースの重複や競合を避けられるためです。しかし、本プロジェクトでは本番運用も想定し、以下の理由から標準化されたリソース名の付与を採用しました。

  • 運用担当者が AWS コンソール上でリソースを迅速に識別・特定可能
  • インシデント発生時に、ログやアラートからリソースを素早く特定可能

一方、名前を明示的に指定することで、一部のリソースでは名前変更時にリソースの置換(削除・再作成)が発生します。本プロジェクトではそのようなケースにおいて、CDK に任せた自動置換は行わず、新規リソースを作成し、関連するリソースの接続を段階的に切り替える方法で対処しました。また、RDS のようなステートフルなリソースについては、事前にバックアップを取得し、アプリケーションを一時的にユーザーから利用できない状態(サービス停止)にしたうえで段階的にデプロイします。これにより、影響範囲を限定しつつ安全にリリースすることを前提としています。そのため、明示的なリソース名の指定によって一部リソースで置換が発生する場合でも、本プロジェクトでは事前の検証と計画的なデプロイにより、運用上の問題を回避できると判断しました。

リソース名作成の実装

物理名を使用してリソースを実装する場合は、複数環境への配備を達成するため、環境毎にリソース名が重複しないよう配慮が必要です。標準化した名前を付与することを目的として、基底クラス側でユーティリティ関数を準備しました。

// lib/utils/base-construct.ts
import { Construct } from 'constructs';
import * as cdk from 'aws-cdk-lib';
import { EnvUtils, IMyEnvironment } from '../lib/utils/env-utils'; // ユーティリティクラスをインポート


export class BaseConstruct extends Construct {


  /**
   * 環境別定義を保持する変数
   * 実装クラス側でこの変数にアクセスすることにより、自動的に環境別設定にアクセスすることができる。
   */
  protected readonly env: IMyEnvironment;


  constructor(scope: Construct, id: string) {
    super(scope, id);
    this.env = EnvUtils.getEnvironment(this.node);
  }


  /**
   * 標準化されたリソース名を生成。
   * 生成する名前に環境名を付与し、環境毎でのリソース名衝突を回避する。
  * プロジェクト名は環境パラメータ(this.env.projectName)から取得。
   * resourceType は enum ResourceType を利用することで、タイプミスや表記ゆれを防止できる。
   */
  protected getName(purpose: string, resourceType: ResourceType): string {
    // プロジェクト名を環境パラメータから取得
    const projectName = this.env.projectName ?? 'sample-project';
    return `${projectName}-${purpose}-${resourceType}-${this.env.side}`;
  }
}
/**
 * リソースタイプを表す enum。
 */
export enum ResourceType {
  S3Bucket = 's3-bucket',
  DynamoTable = 'dynamo-table',
  AuroraCluster = 'aurora-cluster',
  LambdaFunction = 'lambda-function',
  Vpc = 'vpc',
  Ec2Instance = 'ec2-instance',
  // 必要に応じて追加
}

環境別パラメータ切り替えの際に作成した基底クラスに getName() というメソッドを追加しました。利用方法は以下のとおりです。

// src/construct/ec2-construct.ts
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import { BaseConstruct } from '../lib/utils/base-construct';
import { EnvUtils, IMyEnvironment } from '../lib/utils/env-utils'; // ユーティリティクラスをインポート
import { ResourceType } from '../lib/utils/base-construct'; // ResourceType をインポート


interface MyEC2InstanceProps {
  instancePurpose: string; // インスタンスの用途 (例: 'web', 'app', 'batch')
  vpc: ec2.IVpc; // インスタンスをデプロイする VPC
}


export class MyEC2Instance extends BaseConstruct {
  public readonly instance: ec2.Instance;


  constructor(scope: Construct, id: string, props: MyEC2InstanceProps) {
    super(scope, id);
    // 環境別パラメータから取得した EC2 インスタンスタイプと AMI ID を使用
    this.instance = new ec2.Instance(this, 'Instance', {
      vpc: props.vpc,
      // 環境面の名前が付与された形で EC2 インスタンス名が付与される
      // 例えば、環境が「dev」、props.instancePurpose が「web-server」の場合、
      // インスタンス名は「sample-project-web-server-ec2-instance-dev」となります
      instanceName: this.getName(props.instancePurpose, ResourceType.Ec2Instance),
    });
  }
}

まとめ

NRI では CDK や CloudFormation を利用した IaC によるクラウド環境の構築を数多く手掛けています。本稿では実プロジェクトでの成果をもとに以下を達成するプロセスを紹介しました。

  • 複数環境を構成するために IaC コードの構造を工夫することにより、テスト環境構築のリードタイム短縮と構築品質の向上に貢献しました。
  • リソース名の標準化により、運用時の識別性と環境間での一貫性を向上させました。

ここまで、 CDK を利用した設計手法と IaC に移行するアプローチに焦点を当てて紹介しましたが、このサービスにはさらに多くの機能が備わっています。

是非、 AWS の公式ドキュメントを参照いただき、CDK の優れた機能と特徴をご自身の手でお試しください。


野村総合研究所 – AWS Partner Spotlight

野村村総合研究所(NRI)は、2013 年から 12 年連続で AWS プレミアティアサービスパートナーに認定されており、金融サービス、DevOps、移行、セキュリティ、Oracle、SAP、生成 AI の 7 つのコンピテンシーを取得しています。コンサルティングからシステム開発・運用まで幅広い分野で AWS を活用し、お客様の DX 実現を支援しています。

野村総合研究所にコンタクト | パートナー概要

Atsushi Enari

Atsushi Enari

シニアパートナーソリューションアーキテクト アマゾン ウェブ サービス ジャパン合同会社