如何在CloudFormation中正确传递LaunchUserData参数至EC2的UserData
我有一个用于创建EC2实例的CloudFormation模板,需要关联一个UserData脚本,该脚本通过LaunchUserData参数赋值。当前模板片段如下:
AWSTemplateFormatVersion: '2010-09-09' Description: Create an AMI from an EC2 instance. Parameters: LaunchUserData: Description: Base64-encoded user data to launch EC2 instances. Type: String GitSSHKey: Description: Git SSH key Type: String Resources: LaunchEc2: Type: AWS::EC2::Instance Properties: ImageId: !Ref ImageId InstanceType: !Ref InstanceType UserData: ...
LaunchUserData是一个bash脚本,里面会引用栈的参数和资源,示例脚本如下:
#!/bin/bash GIT_SSH_KEY=${GitSshKey}; echo "$GIT_SSH_KEY" | base64 -d > $HOME/.ssh/id_rsa; echo ${AWS:Region};
我希望通过以下CLI命令创建栈:
GIT_SSH_KEY="somesshkey" LAUNCH_USERDATA=$(jq -Rs . < $PWD/templates/launch) STACK_ID=$(aws cloudformation create-stack \ --stack-name test \ --template-body file://$CLOUD_FORMATION_FILE \ --parameters \ ParameterKey=GitSSHKey,ParameterValue="$GIT_SSH_KEY" \ ParameterKey=LaunchUserData,ParameterValue="$LAUNCH_USERDATA" \ --output text \ --query 'StackId');
要求脚本在EC2实例中执行时,其中的${}变量能被替换为当前栈的参数和资源值,最终执行的脚本应该是这样:
$ curl http://169.254.169.254/latest/user-data #!/bin/bash GIT_SSH_KEY=somesshkey; echo "$GIT_SSH_KEY" | base64 -d > $HOME/.ssh/id_rsa; echo us-east-1;
需要注意脚本还可能引用其他CloudFormation资源,请问如何在不将脚本硬编码到模板中的前提下,正确将LaunchUserData参数传递给UserData?
要实现脚本变量的动态替换,同时不硬编码脚本到模板,需要结合CloudFormation的Fn::Sub函数和参数传递来处理,具体步骤如下:
1. 修改CloudFormation模板的UserData配置
把UserData字段改成用Fn::Sub函数处理传入的脚本参数,同时加上CloudFormation要求的Base64编码:
Resources: LaunchEc2: Type: AWS::EC2::Instance Properties: ImageId: !Ref ImageId InstanceType: !Ref InstanceType UserData: Fn::Base64: !Sub - ${LaunchUserDataContent} - LaunchUserDataContent: !Ref LaunchUserData
Fn::Sub会自动把脚本里的变量(比如${GitSSHKey}、${AWS::Region})替换成栈对应的参数值或内置资源值,Fn::Base64是UserData的必填要求。
2. 修正脚本中的变量格式
CloudFormation的Fn::Sub识别的变量有固定格式:
- 栈参数直接写参数名,比如模板里的
GitSSHKey,脚本里对应写${GitSSHKey} - CloudFormation内置变量要写完整名称,比如区域是
${AWS::Region}(注意是双冒号,不是单冒号)
修改后的示例脚本:
#!/bin/bash GIT_SSH_KEY=${GitSSHKey}; echo "$GIT_SSH_KEY" | base64 -d > $HOME/.ssh/id_rsa; echo ${AWS::Region};
3. 调整CLI命令的参数传递
不需要用jq做JSON转义,直接读取脚本内容传递即可:
GIT_SSH_KEY="somesshkey" # 直接读取脚本文件内容 LAUNCH_USERDATA=$(cat $PWD/templates/launch) STACK_ID=$(aws cloudformation create-stack \ --stack-name test \ --template-body file://$CLOUD_FORMATION_FILE \ --parameters \ ParameterKey=GitSSHKey,ParameterValue="$GIT_SSH_KEY" \ ParameterKey=LaunchUserData,ParameterValue="$LAUNCH_USERDATA" \ --output text \ --query 'StackId')
如果脚本里有特殊字符(比如双引号、反斜杠),可以先做base64编码再传递,模板里用Fn::Base64+Fn::Sub+Fn::DecodeBase64组合处理,但上述方式对大多数bash脚本已经够用。
4. 验证效果
栈创建完成后,登录EC2实例执行curl http://169.254.169.254/latest/user-data,就能看到脚本中的变量已经被替换为实际值,和预期结果一致。
内容的提问来源于stack exchange,提问作者Constantin

