CDK部署ECS ApplicationLoadBalancedFargateService加载Secrets时卡住
向ApplicationLoadBalancedFargateService的taskImageOptions下Secrets对象注入密钥时,无论通过sm.Secret.fromSecretCompleteArn(传入包含6位自动生成连字符后缀的完整ARN)还是sm.Secret.fromSecretNameV2方式导入密钥,CDK部署流程都会卡住无法完成,注释掉批量注入的自定义密钥配置后部署可正常完成。
预期行为
ECS服务可正常加载自定义密钥,完成部署流程。
当前行为
CDK部署流程在ECS apiService部署阶段冻结,无进展。
- 批量导入Secrets Manager密钥:
const importedSecrets = SECRET_NAMES.reduce((acc, key) => { const secret = sm.Secret.fromSecretCompleteArn( scope, `${config.ENVIRONMENT}/${key}`, secretFullArns[key], // 与AWS控制台完全一致的密钥完整ARN ); return {...acc, [key]: secret}; }, {} as {[key: string]: sm.ISecret});
其中SECRET_NAMES为存储密钥名称的数组。
2. 生成ECS密钥映射配置:
const secretsJson = SECRET_NAMES.reduce( (acc, key) => ({ ...acc, [key]: ecs.Secret.fromSecretsManager(importedSecrets[key]), }), {}, );
- 创建Fargate服务时传入上述配置:
// Fargate服务定义 this.backendService = new ecsPatterns.ApplicationLoadBalancedFargateService( scope, 'apiService', { serviceName: 'apiService', cluster: this.ecsCluster, taskSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT, }, memoryLimitMiB: 2048, cpu: 1024, desiredCount: 1, taskImageOptions: { containerName: 'apiContainer', image: ecs.ContainerImage.fromAsset('../api-backend/', { followSymlinks: SymlinkFollowMode.ALWAYS, }), containerPort: config.PORT, environment: { NODE_ENV: 'development', AWS_DEFAULT_REGION: config.AWS_DEFAULT_REGION, }, logDriver: ecs.LogDrivers.awsLogs({ streamPrefix: `${config.PROJECT_NAME}-logStream`, logGroup: fargateLog, }), secrets: { DB_BUSINESS_PASSWORD: ecs.Secret.fromSecretsManager( dbBusinessPasswordSecret, 'password', ), DB_BUSINESS_HOST: ecs.Secret.fromSecretsManager( dbBusinessPasswordSecret, 'host', ), ...secretsJson // ===========>> 注释掉该行时部署可成功,但无法加载所需自定义密钥 }, }, }, );
查看合成的CDK CloudFormation模板,生成的密钥名称正确,但ARN缺失了Secrets Manager创建密钥时自动生成的6位连字符后缀,示例配置如下:
.... { "Name": "X_API_KEY", "ValueFrom": { "Fn::Join": [ "", [ "arn:", { "Ref": "AWS::Partition" }, "secret-arn-as-it-is-in-aws-console/co****/staging/X_API_KEY" // 生成的ARN与AWS控制台中密钥ARN的唯一差异为缺失6位自动生成的连字符后缀 ] ] } }, ...
密钥参数名为/co****/staging/X_API_KEY,ARN缺失后缀是导致部署卡住的根本原因——ECS任务启动时无法匹配到对应密钥,会持续重试卡住。已确认任务执行角色的IAM权限配置正确,包含对应密钥的访问权限(密钥完整格式为/co***/staging/CIPHER_SECRET-??????,?代表6位自动生成的随机后缀)。
可通过CDK合成输出的模板直接验证上述ARN后缀缺失问题。
后续按照AWS官方文档指引尝试使用fromSecretAttributes方法导入密钥,代码如下:
const importedSecrets = SECRET_NAMES.reduce((acc, key) => { const secret = sm.Secret.fromSecretAttributes( scope, `/cobuy/${config.ENVIRONMENT}/${key}`, {secretCompleteArn: secretFullArns[key]}, // 传入包含6位连字符后缀的完整ARN ); return {...acc, [key]: secret}; }, {} as {[key: string]: sm.ISecret}); const secretsJson = SECRET_NAMES.reduce( (acc, key) => ({ ...acc, [key]: ecs.Secret.fromSecretsManager(importedSecrets[key]), }), {} as {[key: string]: ecs.Secret}, );
该方式仍无法解决问题,CDK合成阶段直接抛出错误:must use only one of secretCompleteArn or secretPartialArn
- aws-cdk-lib版本:
^2.27.0 - Node.js版本:
14.19 - TypeScript版本:
^4.7.2
cdk.json配置如下:
{ "app": "npx ts-node --prefer-ts-exts bin/CdkStarter.ts", "context": { "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": false, "@aws-cdk/core:stackRelativeExports": false } }
这是aws-cdk-lib 2.27.0版本的内置bug:通过fromSecretCompleteArn/fromSecretNameV2导入的Secrets Manager密钥实例,其secretArn属性会被CDK内部逻辑错误截断,丢失密钥自动生成的6位随机后缀,导致ECS任务定义中引用的密钥ARN无效。使用fromSecretAttributes时报错同样是该版本的参数校验逻辑bug,传入完整ARN时会被内部逻辑误判为同时传入了完整ARN和部分ARN,触发校验失败。
两种方案选择其一即可:
- 升级aws-cdk-lib到2.38.0及以上稳定版本,该类密钥ARN截断问题已在官方迭代中修复,升级后原有代码无需修改即可正常生成带完整后缀的密钥引用,部署流程恢复正常。
- 若暂时无法升级CDK版本,绕过CDK的密钥ARN自动解析逻辑,手动指定密钥版本引用,示例代码如下:
const secretsJson = SECRET_NAMES.reduce( (acc, key) => { const secret = sm.Secret.fromSecretCompleteArn( scope, `${config.ENVIRONMENT}/${key}-ecs`, secretFullArns[key] ); return { ...acc, [key]: ecs.Secret.fromSecretsManagerVersion(secret, { versionId: 'AWSCURRENT' }) } }, {} as Record<string, ecs.Secret> );
该方式会直接使用传入的完整ARN生成任务定义,不会触发CDK的ARN截断逻辑。
内容的提问来源于stack exchange,提问作者Yo2e43

