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. 03通过 Stack Props 传递配置
  4. 04将部署结果写入 SSM Parameter Store
  5. 05合成阶段验证配置

适用场景

  1. 01管理包含域名和证书 ARN 的 CDK 部署
  2. 02配置 dev、staging、prod 多环境部署
  3. 03避免 CDK 上下文缓存及循环依赖

安装

复制下面的命令,在终端中运行。

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

安装后在 Claude Code 中输入/cdk-bootstrap-configuration确认已加载。

详细介绍

CDK 引导配置

这是一种参考模式。 借鉴其方法,并根据你的场景进行调整——不要照搬。

适用对象:CDK 基础设施开发者。如果你正在构建读取配置的服务,请参阅 SSM 运行时配置。

问题:使用 CDK 的 valueFromLookup() 获取引导配置会导致上下文缓存问题,需要手动干预,并在部署脚本与基础设施代码之间产生循环依赖。

解决方案:将引导配置(部署输入)与运行时配置(基础设施输出)分开。引导配置通过环境文件在合成时传入 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)加载引导配置
  • 堆栈属性:通过属性传递引导值,使堆栈保持解耦
  • SSM 输出:将基础设施结果写入 Parameter Store,供服务使用

核心原则:SSM 应存储部署的结果,而不是部署的输入。


为什么采用此模式?

优点:

  • 无 CDK 上下文缓存:无需 cdk.context.json,也无需手动干预
  • 清晰分离:引导配置(输入)与输出(结果)在架构上彼此独立
  • 适用于 CI/CD:在本地和 CI 环境中都能顺畅运行
  • 类型安全:在合成时进行验证,及早发现缺失的配置
  • 无循环依赖:部署脚本不会预先填充 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 值
  • 验证:在合成时快速失败,并提供清晰的错误消息
  • 合并策略:文件值与 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
});

为什么使用属性:使堆栈与 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. 堆栈属性接口
// 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
用途创建基础设施配置服务
读取方合成时的 CDK运行时的服务
示例域名、证书 ARN、账户 IDCloudFront 域名、存储桶名称

为何这很重要:

  • 使用 valueFromLookup() 获取引导配置会产生循环依赖
  • CDK 会将查找结果缓存到 cdk.context.json 中(特定于环境,不适用于 Git)
  • 上下文丢失后需要手动干预,并花费时间进行调试

解决方法:

  • 引导配置:直接从环境中读取(不查询 SSM)
  • 运行时配置:部署后写入 SSM(供服务使用)

何时不应使用

  • 简单的单环境项目:如果只有一个环境,且没有生产域名,这种分离方式可能过于复杂
  • 非 CDK 项目:此模式专门用于解决 AWS CDK 上下文缓存问题
  • 没有外部依赖的项目:如果所有配置都由 CDK 自行生成,更简单的方法可能就足够了

替代方案:

  • CDK 上下文值:适用于真正静态、永不变更的值(不建议用于特定环境的配置)
  • CloudFormation 参数:适用于随每次部署而变化的值(会增加部署复杂度)

相关模式

  • SSM 运行时配置 - 服务如何从 SSM 获取运行时配置(此模式将配置写入 SSM,该模式从中读取)
  • 环境验证 - validateBootstrap() 方法中使用的快速失败验证模式
  • 环境部署策略 - 多环境部署方法,定义“dev”“prod”等代表什么

渐进式改进

如果开发者纠正了此技能本应防止的行为,请提出对此技能的具体修订建议,以避免今后再次出现同样的纠正。

评论

支持 Markdown