如何在CDK部署时为UserData配置仅部署时可知的资源参数?
解决方案:CDK v2 + TypeScript 实现部署时资源变量替换
核心思路
利用CDK的Token机制自动解析部署时生成的资源属性(比如LogGroup名称),结合Fn.sub或直接字符串替换,将UserData脚本中的占位变量替换为实际AWS资源值,同时通过流水线确保资源部署顺序依赖。
步骤1:创建带自定义保留期的CloudWatch LogGroup
首先定义一个独立的Stack(或在堡垒机Stack内)创建LogGroup,支持自定义保留期,并暴露其logGroupName属性供后续使用:
import { Stack, StackProps, RemovalPolicy } from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs'; export class BastionLogStack extends Stack { public readonly bastionLogGroup: LogGroup; constructor(scope: Construct, id: string, props?: StackProps & { logRetention?: RetentionDays // 允许外部传入自定义保留期 }) { super(scope, id, props); this.bastionLogGroup = new LogGroup(this, 'BastionHostLogGroup', { logGroupName: undefined, // 让CDK自动生成带栈后缀的唯一名称(部署时确定) retention: props?.logRetention ?? RetentionDays.THIRTY_DAYS, // 默认30天,可自定义 removalPolicy: RemovalPolicy.DESTROY, // 根据业务需求调整(保留/销毁) }); } }
步骤2:替换堡垒机UserData中的变量
在堡垒机EC2实例的Stack中,通过Fn.sub批量替换脚本模板中的占位变量,CDK会自动将logGroupName Token解析为部署时的实际值:
import { Stack, StackProps, Fn } from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { Instance, InstanceType, AmazonLinuxImage, UserData } from 'aws-cdk-lib/aws-ec2'; import { Role, ServicePrincipal, PolicyStatement } from 'aws-cdk-lib/aws-iam'; import { BastionLogStack } from './bastion-log-stack'; export class BastionHostStack extends Stack { constructor(scope: Construct, id: string, props: StackProps & { logStack: BastionLogStack // 依赖LogGroup Stack }) { super(scope, id, props); const { bastionLogGroup } = props.logStack; // 1. 给堡垒机实例添加CloudWatch Logs写入权限 const bastionRole = new Role(this, 'BastionIamRole', { assumedBy: new ServicePrincipal('ec2.amazonaws.com'), }); bastionRole.addToPolicy(new PolicyStatement({ actions: ['logs:CreateLogStream', 'logs:PutLogEvents', 'logs:DescribeLogStreams'], resources: [bastionLogGroup.logGroupArn], })); // 2. 定义带占位变量的UserData模板 const userDataTemplate = `#!/bin/bash # 配置CloudWatch Logs Agent cat > /etc/awslogs/awslogs.conf << EOF [general] state_file = /var/lib/awslogs/agent-state [/var/log/bastion] file = /var/log/bastion/*.log log_group_name = ${TF_APP_LOG_GROUP} log_stream_name = {instance_id}/bastion.log datetime_format = %Y-%m-%d %H:%M:%S EOF # 安装并启动日志服务 yum install -y awslogs systemctl enable --now awslogs `; // 3. 替换模板中的变量,CDK会自动解析Token为实际LogGroup名称 const userData = UserData.forLinux(); userData.addCommands(Fn.sub(userDataTemplate, { TF_APP_LOG_GROUP: bastionLogGroup.logGroupName, })); // 4. 创建堡垒机EC2实例 new Instance(this, 'BastionHost', { instanceType: InstanceType.of('t2', 'micro'), machineImage: new AmazonLinuxImage(), userData: userData, role: bastionRole, // 补充VPC、安全组等其他配置 }); } }
步骤3:CDK流水线中配置部署顺序
在流水线Stack中,先部署LogGroup Stack,再部署堡垒机Stack,确保资源依赖关系正确:
import { Stack, StackProps } from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { CodePipeline, CodePipelineSource, ShellStep } from 'aws-cdk-lib/pipelines'; import { BastionLogStack } from './bastion-log-stack'; import { BastionHostStack } from './bastion-host-stack'; export class BastionPipelineStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); const pipeline = new CodePipeline(this, 'BastionDeploymentPipeline', { pipelineName: 'Bastion-Pipeline', synth: new ShellStep('Synth', { input: CodePipelineSource.gitHub('your-username/your-repo', 'main'), commands: ['npm install', 'npm run build', 'npx cdk synth'], }), }); // 先部署LogGroup Stack const logStage = pipeline.addStage('LogGroup-Deployment'); const logStack = new BastionLogStack(logStage, 'BastionLogStack', { logRetention: RetentionDays.SIX_MONTHS // 自定义保留期为6个月 }); // 再部署堡垒机Stack,传入LogGroup依赖 const bastionStage = pipeline.addStage('BastionHost-Deployment'); new BastionHostStack(bastionStage, 'BastionHostStack', { logStack: logStack, }); } }
关键说明
- Token机制:
bastionLogGroup.logGroupName是CDK生成的Token,部署时会被替换为实际的LogGroup名称(即使是CDK自动生成的带随机后缀的名称)。 - 权限最小化:避免给堡垒机实例赋予
CloudWatchLogsFullAccess,而是仅授予目标LogGroup的写入权限,符合安全最佳实践。 - 自定义保留期:通过Stack参数
logRetention可以灵活设置日志保留时长,支持RetentionDays枚举的所有值(如ONE_DAY、ONE_YEAR等)。
内容的提问来源于stack exchange,提问作者Shorn
相关产品推荐
相关产品推荐

