参考资料
-
Running bash commands in AWS CloudFormation templates
-
如何使用 AWSUtility::CloudFormation::CommandRunner 在 CloudFormation 堆栈中的资源之前或之后运行命令?
由于cloudformation语法和资源的限制,有些场景下我们可能会希望执行一些自定义逻辑。实际上cloudformation已经提供了自定义资源和宏实现这个目的。而CommandRunner
则从另一个角度解决此问题。
commandrunner本质上就是启动额外的ec2来执行指定的逻辑,和自定义资源的lambda类似的思路,只是资源类型不同
测试过程
安装commandrunner
git clone https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-awsutilities-commandrunner.git
cd aws-cloudformation-resource-providers-awsutilities-commandrunner
curl -LO https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-awsutilities-commandrunner/releases/latest/download/awsutility-cloudformation-commandrunner.zip
./scripts/register.sh --set-default
具体的执行过程如下,核心具体就是将awsutility-cloudformation-commandrunner.zip
注册到cfn中,需要临时s3桶存储该zip文件
Creating Execution Role...
Waiting for execution role stack to complete...
Waiting for execution role stack to complete...
Creating/Updating Execution Role complete.
Creating temporary S3 Bucket b34239efa0b947bebd574be7fa6d7e2d...
Creating temporary S3 Bucket b34239efa0b947bebd574be7fa6d7e2d complete.
Configuring S3 Bucket Policy for temporary S3 Bucket b34239efa0b947bebd574be7fa6d7e2d...
An error occurred (MalformedPolicy) when calling the PutBucketPolicy operation: Policy has invalid resource
Configuring S3 Bucket Policy for temporary S3 Bucket b34239efa0b947bebd574be7fa6d7e2d complete.
Copying Schema Handler Package to temporary S3 Bucket b34239efa0b947bebd574be7fa6d7e2d...
Copying Schema Handler Package to temporary S3 Bucket b34239efa0b947bebd574be7fa6d7e2d complete.
Creating CommandRunner Log Group called awsutility-cloudformation-commandrunner-logs2...
Creating CommandRunner Log Group complete.
Registering AWSUtility::CloudFormation::CommandRunner to AWS CloudFormation...
RegistrationToken: 5a22a5d4-723e-4d2a-b20d-eff95352830c
Waiting for registration to complete...
Waiting for registration to complete...
Waiting for registration to complete...
Waiting for registration to complete...
Registering AWSUtility::CloudFormation::CommandRunner to AWS CloudFormation complete.
Setting current version as default...
Setting current version as default complete. (Current Version is 00000001)
Cleaning up temporary S3 Bucket...
Deleting SchemaHandlerPackage from temporary S3 Bucket b34239efa0b947bebd574be7fa6d7e2d...
Deleting SchemaHandlerPackage from temporary S3 Bucket b34239efa0b947bebd574be7fa6d7e2d complete.
Cleaning up temporary S3 Bucket complete.
以上命令会使用默认的awscli凭证在账户中创建cfn资源
之后可以在cfn模板中直接使用,这里创建简单的s3资源
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
VersioningConfiguration:
Status: Suspended
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
CommandRunner1:
DependsOn: S3Bucket
Type: AWSUtility::CloudFormation::CommandRunner
Properties:
Command: echo justfortest > /command-output.txt
Role: MyEc2AdministratorAccess
SubnetId: subnet-0270xxxxxxxd
CommandRunner2:
Type: AWSUtility::CloudFormation::CommandRunner
Properties:
Role: MyEc2AdministratorAccess
Command: echo helloworld > /command-output.txt
SubnetId: subnet-02xxxxxxxd
Outputs:
Output1:
Description: The output of the CommandRunner.
Value: !GetAtt CommandRunner1.Output
Output2:
Description: The output of the CommandRunner.
Value: !GetAtt CommandRunner2.Output
由于创建了两个commandrunner资源,因此会创建额外的两个堆栈
启动两台ec2示例,默认为t2.medium
以下是AWSUtility-CloudFormation-CommandRunner
资源的模板截取,逻辑较为简单,只需要关注userdata部分,相关的概念的命令在之前的文章中都提到过,不赘述
- 执行init初始化
- 执行指定的command命令
- 执行signal,向cfn响应,响应内容是
command-output.txt
文件中的内容
Parameters:
...略
Command:
Type: String
Default: >-
yum install jq -y && aws ssm get-parameter --name RepositoryName --region
us-east-1 | jq -r .Parameter.Value > /commandrunner-output.txt
LogGroup:
Type: String
Default: cloudformation-commandrunner-log-group
Resources:
SecurityGroup:
Condition: CreateSecurityGroup
Type: AWS::EC2::SecurityGroup
EC2Instance:
Type: AWS::EC2::Instance
Metadata:
AWS::CloudFormation::Init:
config:
packages:
yum:
awslogs: []
files:
/etc/awslogs/awslogs.conf:
content:
Fn::Sub: |
[general]
state_file= /var/awslogs/state/agent-state
[/var/log/cloud-init.log]
file = /var/log/cloud-init.log\n
log_group_name = ${LogGroup}
log_stream_name = {instance_id}/cloud-init.log
... 略
/etc/awslogs/awscli.conf:
content:
Fn::Sub: |
[plugins]
cwlogs = cwlogs
[default]
region = ${AWS::Region}
commands:
01_create_state_directory:
command: mkdir -p /var/awslogs/state
services:
sysvinit:
awslogsd:
enabled: true
ensureRunning: true
files:
- /etc/awslogs/awslogs.conf
Properties:
UserData:
Fn::Base64:
Fn::Sub: >-
#!/bin/bash
yum install -y aws-cfn-bootstrap
/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource
EC2Instance --region ${AWS::Region}
${Command}
/opt/aws/bin/cfn-signal -r 'Command ran successfully.' -e 0 --id
'Command Output' --data "$(cat /command-output.txt)"
'${WaitConditionHandle}'
echo Contents of /command-output.txt = $(cat /command-output.txt)
WaitConditionHandle:
Type: AWS::CloudFormation::WaitConditionHandle
WaitCondition:
Type: AWS::CloudFormation::WaitCondition
Properties:
Count: 1
Handle:
Ref: WaitConditionHandle
Timeout:
Ref: Timeout
Outputs:
Result:
Description: The output of the commandrunner.
Value:
Fn::Select:
- 3
- Fn::Split:
- '"'
- Fn::GetAtt:
- WaitCondition
- Data
最终能够获取到output
总结commandrunner的特点如下
- 便宜,据说短时间的ec2启动费用要小于lambda
- 灵活,用户自行决定资源的阻塞和依赖
- 方便,不需要写代码,lambda不能直接执行bash脚本,降低了使用门槛
- 慢,启动ec2的相比调用lambda慢很多,而且貌似没法进行资源复用
- 不好排查,因为都是在实例上,需要查日志,而且这个资源类型具体做什么我们看不到
相关错误
(1)waitcondition超时
可能是由于ec2没有权限,ec2网络配置有误,在syslog中能看到相关信息
(2)输出结果无效,cli命令要确保写入到文件中
Resource handler returned message: "Either the command failed to execute, the value written to /command-output.txt was invalid or the Subnet specified did not have internet access. The value written to /command-output.txt must be a non-empty single word value without quotation marks. Check cloud-init.log in the LogGroup specified for more information." (RequestToken: c7ebb7d1-ec0a-43e0-ea18-f13a331fc00d, HandlerErrorCode: NotStabilized)