如何在CloudFormation栈中引用EC2 User Data创建的AMI ID?
我正在编写CloudFormation模板,用来部署EC2实例。该实例会通过User Data从Git仓库拉取脚本,并用脚本创建AMI。我想在同一个CloudFormation栈的其他资源里,通过!Ref或其他方式引用这个新生成的AMI ID。
目前我在User Data里加了以下代码获取AMI ID:
export AMIID=$(aws ec2 describe-images --filters "Name=name,Values=ami-name" | jq -r ".Images[].ImageId")
还添加了把AMI ID存入SSM参数存储的代码:
aws ssm put-parameter --name aminame --type String --value "$AMIID"
在CloudFormation栈里我定义了如下参数:
AMI: Type : 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>' Default: aminame
然后在资源块里引用这个AMI:
EC2Instance: Type: "AWS::EC2::Instance" CreationPolicy: ResourceSignal: Timeout: PT120M Properties: ImageId: !Ref AMI UserData: Fn::Base64: | #!/bin/bash # 这里是拉取脚本、创建AMI并存入SSM的代码
运行时收到“参数未找到”的错误,这在意料之中,有没有其他可行的实现方式?
方法一:用CloudFormation自定义资源传递AMI ID
自定义资源能让EC2实例在生成AMI后主动通知CloudFormation栈,把AMI ID作为资源属性存储,这样栈内其他资源就能直接引用。
步骤&代码示例
给EC2实例添加必要的IAM权限
先定义一个IAM角色,允许实例创建AMI、调用CloudFormation的自定义资源回调API:EC2Role: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: ec2.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: AMICreationAndCFNCallback PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - ec2:CreateImage - ec2:DescribeImages - ssm:PutParameter Resource: "*" - Effect: Allow Action: - cloudformation:SignalResource Resource: !Sub "arn:aws:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*" EC2InstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: [!Ref EC2Role]定义自定义资源
在模板里加一个自定义资源,用来接收AMI ID:CustomAMIResource: Type: Custom::AMIResource Properties: ServiceToken: !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:AWSLambdaBasicExecutionRole" # 留空等待EC2实例回调填充数据注:实际使用时,
ServiceToken可以用AWS提供的Lambda函数,或自行编写简单Lambda处理回调,核心是接收AMI ID并返回给CloudFormation。修改EC2的User Data,完成AMI创建后回调自定义资源
在创建AMI并存入SSM的代码后,添加回调逻辑:# 创建AMI的代码... # 获取AMI ID后,发送信号给自定义资源 aws cloudformation signal-resource \ --stack-name ${AWS::StackName} \ --logical-resource-id CustomAMIResource \ --region ${AWS::Region} \ --status SUCCESS \ --data "$AMIID"在其他资源中引用
之后其他资源就可以通过!GetAtt CustomAMIResource.Data获取AMI ID:NewEC2Instance: Type: AWS::EC2::Instance Properties: ImageId: !GetAtt CustomAMIResource.Data # 其他属性...
方法二:用等待条件+SSM动态引用
先让EC2实例生成AMI并存入SSM,然后用WaitCondition等待参数创建完成,再通过动态引用获取AMI ID。
步骤&代码示例
添加WaitCondition和WaitConditionHandle
AMIWaitConditionHandle: Type: AWS::CloudFormation::WaitConditionHandle AMIWaitCondition: Type: AWS::CloudFormation::WaitCondition DependsOn: EC2Instance Properties: Handle: !Ref AMIWaitConditionHandle Timeout: PT120M修改EC2的User Data,存入SSM后发送等待信号
# 创建AMI、获取AMI ID并存入SSM的代码... # 发送信号通知等待条件完成 aws cloudformation signal-resource \ --stack-name ${AWS::StackName} \ --logical-resource-id AMIWaitCondition \ --region ${AWS::Region} \ --status SUCCESS引用AMI ID
在需要使用AMI的资源里,用动态引用{{resolve:ssm:aminame}},并确保依赖WaitCondition:NewEC2Instance: Type: AWS::EC2::Instance DependsOn: AMIWaitCondition Properties: ImageId: "{{resolve:ssm:aminame}}" # 其他属性...
方法三:拆分CloudFormation栈
把流程拆成两个独立的栈,第一个栈负责生成AMI,第二个栈使用生成的AMI。
步骤
第一个栈(AMI生成栈)
只部署用来生成AMI的EC2实例,在User Data里完成AMI创建并存入SSM参数,不需要其他资源。第二个栈(业务资源栈)
通过SSM参数引用AMI ID,部署需要使用该AMI的资源,比如新的EC2实例。可以设置第二个栈依赖第一个栈,或等第一个栈执行完成后手动创建第二个栈。
代码示例(第二个栈的参数)
Parameters: AMIId: Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id> Default: aminame Resources: BusinessEC2Instance: Type: AWS::EC2::Instance Properties: ImageId: !Ref AMIId # 其他属性...
内容的提问来源于stack exchange,提问作者Zekeg

