mcpField
C
cdk-bootstrap-configuration

cdk-bootstrap-configuration

github.com/loxosceles-devクラウド・DevOps130 回インストール
リポジトリ

cdk-bootstrap-configuration について

AWS CDKのインフラ開発者向けの設定パターンで、デプロイ前の入力と実行時の出力を分離します。環境ファイルやCI変数からCDKへ入力を渡し、インフラの結果をSSM Parameter Storeに保存して、コンテキストキャッシュや循環依存の問題を避けます。

主な機能

  1. 01ブートストラップ設定と実行時設定の分離
  2. 02環境ファイルまたはCI変数から入力を読み込み
  3. 03Stack Props経由で設定を受け渡し
  4. 04デプロイ結果をSSM Parameter Storeに保存
  5. 05合成時の設定検証

活用シーン

  1. 01ドメインや証明書ARNを含むCDKデプロイの管理
  2. 02dev、staging、prod環境の設定
  3. 03CDKのコンテキストキャッシュや循環依存の回避

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/loxosceles-dev/dev-skills --skill cdk-bootstrap-configuration

インストール後、Claude Code で/cdk-bootstrap-configurationを入力して読み込まれたか確認します。

詳細

CDK ブートストラップ設定

これは参考パターンです。 このアプローチから学び、自分の状況に合わせて調整してください。逐語的にコピーしないでください。

対象者:CDK インフラストラクチャの開発者。設定を読み取るサービスを構築する場合は、SSM ランタイム設定を参照してください。

課題:ブートストラップ設定に CDK の valueFromLookup() を使用すると、コンテキストのキャッシュに関する問題が発生し、手動での対応が必要になったり、デプロイスクリプトとインフラストラクチャコードの間に循環依存が生じたりします。

解決策:ブートストラップ設定(デプロイ時の入力値)とランタイム設定(インフラストラクチャの出力値)を分離します。ブートストラップ設定は、synth 時に環境ファイルから CDK に渡します。ランタイムの出力値は、インフラストラクチャからサービスが利用する SSM Parameter Store に書き込みます。


パターン

アーキテクチャのフロー:

┌─────────────────────────────────────────────────────────────┐
│ BOOTSTRAP LAYER (Pre-Infrastructure)                        │
│ - Lives in: .env.{stage} files (local) or CI variables      │
│ - Purpose: External constants needed to CREATE infra        │
│ - Examples: domain, certificate ARN, account ID             │
│ - Access: Read by CDK at synth time                         │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ CDK SYNTHESIS                                                │
│ - Reads bootstrap from environment directly                 │
│ - No SSM lookup at synth time                               │
│ - Generates CloudFormation template                         │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ CDK DEPLOYMENT                                               │
│ - Creates infrastructure resources                          │
│ - Writes outputs to SSM for runtime consumption             │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│ SSM PARAMETER STORE (Post-Infrastructure)                   │
│ - Contains: Stack outputs for runtime consumption           │
│ - Purpose: Runtime config for services/lambdas/frontend     │
│ - Access: Services read at runtime                          │
└─────────────────────────────────────────────────────────────┘

主要コンポーネント:

  • ブートストラップ設定:インフラストラクチャの作成に必要な外部定数(ドメイン、証明書 ARN、アカウント ID)
  • 環境マネージャー:ファイル(ローカル)または環境変数(CI/CD)からブートストラップ設定を読み込みます
  • スタックの Props:ブートストラップ値を Props 経由で渡し、スタック間の依存を解消します
  • SSM 出力:インフラストラクチャの結果を、サービスが利用する Parameter Store に書き込みます

基本原則:SSM に保存するのはデプロイの入力値ではなく、デプロイの結果です。


このパターンを使う理由

メリット:

  • CDK のコンテキストキャッシュが不要:cdk.context.json と手動対応を排除します
  • 明確な分離:ブートストラップ(入力値)と出力値(結果)を、アーキテクチャ上明確に区別できます
  • CI/CD に適しています:ローカル環境と CI 環境の両方でシームレスに動作します
  • 型安全:synth 時の検証により、設定の不足を早期に検出できます
  • 循環依存がありません:デプロイスクリプトが、CDK の読み取る値を事前に設定する必要はありません
  • Git との相性が良好:環境固有のコンテキストファイルを管理する必要がありません

ユースケース:

  • 本番環境のドメインと証明書を使う CDK プロジェクト
  • 複数環境へのデプロイ(dev、staging、prod)
  • CI/CD パイプラインを使うプロジェクト
  • CDK のコンテキストキャッシュの問題に直面しているチーム

実装

1. 環境マネージャー
// lib/core/environment-manager.ts
export class EnvironmentManager {
  private loadEnv(stage: string): Record<string, string> {
    const envPath = path.join(this.infraRootPath, `.env.${stage}`);
    
    // Try file first (local), fall back to process.env (CI)
    if (fs.existsSync(envPath)) {
      const fileEnv = dotenv.parse(fs.readFileSync(envPath));
      
      // Filter undefined from process.env
      const processEnv: Record<string, string> = {};
      Object.entries(process.env).forEach(([key, value]) => {
        if (value !== undefined) processEnv[key] = value;
      });
      
      // CI variables override file values
      return { ...fileEnv, ...processEnv };
    }
    
    // CI path: use process.env only
    const processEnv: Record<string, string> = {};
    Object.entries(process.env).forEach(([key, value]) => {
      if (value !== undefined) processEnv[key] = value;
    });
    
    if (Object.keys(processEnv).length === 0) {
      throw new Error(
        `No configuration found for stage '${stage}'.\n` +
        `Expected file: ${envPath}\n` +
        `Or environment variables in CI/CD context.`
      );
    }
    
    return processEnv;
  }
  
  public getStackEnv(stackType: string): IStackEnv {
    const baseEnv = this.loadEnv(this.stage);
    const isProd = this.stage === 'prod';
    
    // Validate required bootstrap values
    this.validateBootstrap(baseEnv, isProd);
    
    return {
      awsAccountId: baseEnv.CDK_DEFAULT_ACCOUNT,
      awsRegionDefault: baseEnv.CDK_DEFAULT_REGION,
      stage: this.stage,
      // Add stage-specific bootstrap values
      ...(isProd && {
        prodDomainName: baseEnv.PROD_DOMAIN_NAME,
        certificateArn: baseEnv.CERTIFICATE_ARN
      })
    };
  }
  
  private validateBootstrap(env: Record<string, string>, isProd: boolean): void {
    const required = ['CDK_DEFAULT_ACCOUNT', 'CDK_DEFAULT_REGION'];
    
    if (isProd) {
      required.push('PROD_DOMAIN_NAME', 'CERTIFICATE_ARN');
    }
    
    const missing = required.filter(key => !env[key]);
    
    if (missing.length > 0) {
      throw new Error(
        `Missing required bootstrap configuration: ${missing.join(', ')}\n` +
        `Check .env.${this.stage} or CI/CD environment variables`
      );
    }
  }
}

重要な詳細:

  • 型安全:使用前に process.env から undefined の値を除外します
  • 検証:synth 時に明確なエラーメッセージを表示して、即座に失敗させます
  • マージ戦略:ファイルの値と process.env をマージします(CI の値がファイルの値を上書きします)
2. CDK アプリのエントリーポイント
// bin/app.ts
const envManager = new EnvironmentManager(config);
const stackEnv = envManager.getStackEnv('web');

new WebStack(app, `WebStack-${envManager.getStage()}`, {
  env: { 
    account: stackEnv.awsAccountId, 
    region: stackEnv.awsRegionDefault 
  },
  stackEnv: stackEnv  // Pass bootstrap through props
});

Props を使う理由:スタックを EnvironmentManager から切り離し、テストしやすくします。

3. スタックの実装
// lib/stacks/web-stack.ts
export class WebStack extends Stack {
  constructor(scope: Construct, id: string, props: WebStackProps) {
    super(scope, id, props);
    
    const isProd = props.stackEnv.stage === 'prod';
    
    // Use bootstrap values from props (NOT from SSM lookup)
    let domainName: string | undefined;
    let certificateArn: string | undefined;
    
    if (isProd) {
      domainName = props.stackEnv.prodDomainName;
      certificateArn = props.stackEnv.certificateArn;
    }
    
    // Create infrastructure...
    const distribution = new cloudfront.Distribution(this, 'Distribution', {
      domainNames: domainName ? [domainName] : undefined,
      certificate: certificateArn 
        ? acm.Certificate.fromCertificateArn(this, 'Cert', certificateArn)
        : undefined,
      // ... other config
    });
    
    // Write outputs to SSM for runtime consumption
    new ssm.StringParameter(this, 'CloudFrontDomainOutput', {
      parameterName: `/${projectId}/${stage}/outputs/CLOUDFRONT_DOMAIN`,
      stringValue: distribution.distributionDomainName,
      description: 'CloudFront distribution domain name',
      tier: ssm.ParameterTier.STANDARD
    });
  }
}

重要:ブートストラップ値に対する ssm.StringParameter.valueFromLookup() の呼び出しをすべて削除してください。

4. スタックの Props インターフェース
// types/stack-env.ts
export interface IStackEnv {
  awsAccountId: string;
  awsRegionDefault: string;
  stage: string;
  // Stage-specific bootstrap values
  prodDomainName?: string;
  certificateArn?: string;
}

export interface WebStackProps extends StackProps {
  stackEnv: IStackEnv;
}
5. ブートストラップ設定

ローカル開発(.env.{stage} — gitignore 対象):

# Bootstrap values for CDK synthesis
CDK_DEFAULT_ACCOUNT=123456789012
CDK_DEFAULT_REGION=us-east-1
PROD_DOMAIN_NAME=example.com
CERTIFICATE_ARN=arn:aws:acm:us-east-1:123456789012:certificate/abc123

CI/CD:リポジトリ変数として設定します(GitHub Actions Variables、CodeBuild の環境設定など)。


重要なトレードオフ:ブートストラップとランタイム

基本的な違い:

項目ブートストラップ(入力値)ランタイム(出力値)
タイミングインフラストラクチャが存在する前インフラストラクチャが存在した後
取得元.env ファイルまたは CI 変数SSM Parameter Store
目的インフラストラクチャの作成サービスの設定
読み取り元synth 時の CDKランタイムのサービス
例ドメイン、証明書 ARN、アカウント IDCloudFront ドメイン、バケット名

重要である理由:

  • ブートストラップに valueFromLookup() を使うと、循環依存が生じます
  • CDK は検索結果を cdk.context.json にキャッシュします(環境固有で、Git との相性がよくありません)
  • コンテキストが失われると、手動対応とデバッグに時間が必要になります

解決方法:

  • ブートストラップ:環境から直接読み取ります(SSM の検索は不要です)
  • ランタイム:デプロイ後に SSM に書き込みます(サービス向け)

使用しない場合

  • 単一環境のシンプルなプロジェクト:環境が1つだけで、本番用ドメインもない場合、この分離は過剰な可能性があります
  • CDK を使用しないプロジェクト:このパターンは AWS CDK のコンテキストキャッシュに関する問題に特化しています
  • 外部依存関係のないプロジェクト:すべての設定が CDK 自体によって生成される場合は、よりシンプルな方法で十分な可能性があります

代替案:

  • CDK コンテキスト値:本当に静的で、変更されることのない値に使用します(環境固有の設定には推奨されません)
  • CloudFormation パラメーター:デプロイごとに変更される値に使用します(デプロイが複雑になります)

関連パターン

  • SSM ランタイム設定 - サービスが SSM からランタイム設定を読み込む方法(このパターンでは SSM に書き込み、こちらのパターンでは SSM から読み込みます)
  • 環境の検証 - validateBootstrap() メソッドで使用される、失敗時に即座に終了する検証パターン
  • 環境デプロイ戦略 - 「dev」、「prod」などの意味を定義する、複数環境へのデプロイ方法

段階的な改善

開発者がこのスキルで防止すべき動作を修正した場合、今後同じ修正が必要にならないよう、このスキルに追加する具体的な改訂を提案してください。

コメント