关于 cdk-bootstrap-configuration
面向 AWS CDK 基础设施开发者的配置参考模式,将部署前的引导输入与部署后的运行时输出分开。它用环境文件或 CI 变量向 CDK 传入输入,并将基础设施结果写入 SSM Parameter Store,以避免上下文缓存和循环依赖问题。
主要功能
- 01区分引导配置与运行时配置
- 02从环境文件或 CI 变量读取输入
- 03通过 Stack Props 传递配置
- 04将部署结果写入 SSM Parameter Store
- 05合成阶段验证配置
适用场景
- 01管理包含域名和证书 ARN 的 CDK 部署
- 02配置 dev、staging、prod 多环境部署
- 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、账户 ID | CloudFront 域名、存储桶名称 |
为何这很重要:
- 使用
valueFromLookup()获取引导配置会产生循环依赖 - CDK 会将查找结果缓存到
cdk.context.json中(特定于环境,不适用于 Git) - 上下文丢失后需要手动干预,并花费时间进行调试
解决方法:
- 引导配置:直接从环境中读取(不查询 SSM)
- 运行时配置:部署后写入 SSM(供服务使用)
何时不应使用
- 简单的单环境项目:如果只有一个环境,且没有生产域名,这种分离方式可能过于复杂
- 非 CDK 项目:此模式专门用于解决 AWS CDK 上下文缓存问题
- 没有外部依赖的项目:如果所有配置都由 CDK 自行生成,更简单的方法可能就足够了
替代方案:
- CDK 上下文值:适用于真正静态、永不变更的值(不建议用于特定环境的配置)
- CloudFormation 参数:适用于随每次部署而变化的值(会增加部署复杂度)
相关模式
- SSM 运行时配置 - 服务如何从 SSM 获取运行时配置(此模式将配置写入 SSM,该模式从中读取)
- 环境验证 -
validateBootstrap()方法中使用的快速失败验证模式 - 环境部署策略 - 多环境部署方法,定义“dev”“prod”等代表什么
渐进式改进
如果开发者纠正了此技能本应防止的行为,请提出对此技能的具体修订建议,以避免今后再次出现同样的纠正。