CFnからBash実行まで行うAWSUtility::CloudFormation::CommandRunnerを試してみた。
AWSUtility::CloudFormation::CommandRunner
2020年11月9日に存在していた記事となります。
パブリックレジストリの発表が2021年6月24日との事なので、
CloudFormationレジストリのリソースタイプとしては更に古株という事になりそうです。
早速試してみます。
事前準備
ターミナルから以下を実行します。
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
↓
ExecutionRoleが作成されました。
作成されたポリシーの内容を転記します。
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"cloudformation:CreateStack",
"cloudformation:DeleteStack",
"cloudformation:DescribeStacks",
"ec2:AuthorizeSecurityGroupEgress",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:CreateSecurityGroup",
"ec2:CreateTags",
"ec2:DeleteSecurityGroup",
"ec2:DescribeInstances",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSubnets",
"ec2:DescribeVpcs",
"ec2:RevokeSecurityGroupEgress",
"ec2:RevokeSecurityGroupIngress",
"ec2:RunInstances",
"ec2:TerminateInstances",
"iam:GetInstanceProfile",
"iam:PassRole",
"iam:SimulatePrincipalPolicy",
"kms:Decrypt",
"kms:Encrypt",
"logs:CreateLogStream",
"logs:DescribeLogGroups",
"log:PutLogEvents",
"cloudwatch:PutMetricData",
"ssm:DeleteParameter",
"ssm:GetParameter",
"ssm:PutParameter",
"sts:GetCallerIdentity"
],
"Resource": "*",
"Effect": "Allow"
}
]
}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "resources.cloudformation.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
↓
最初の公式記事内で「AWSUtility::CloudFormation::CommandRunner」はAWS CloudFormation レジストリ登録から利用が可能になる旨記載がありますが、CloudFlare,NewRelic,DartaDog等のパブリッシャーとは異なり、「非公開登録」のリソースタイプである事がわかります。
利用してみる
同記事内に付属していたサンプルを実行してみます。
AWSTemplateFormatVersion: 2010-09-09
Parameters:
EBSVolumeSize:
Type: Number
Default: 10
MinValue: 10
MaxValue: 50
Resources:
IopsCalculator:
Type: AWSUtility::CloudFormation::CommandRunner
Properties:
Command:
Fn::Sub: 'expr ${EBSVolumeSize} \* 20 > /command-output.txt'
Outputs:
Iops:
Description: EBS IOPS
Value:
Fn::GetAtt: IopsCalculator.Output
↓
ネストスタックである表示はありませんが、私が作成したsampleスタックが先に作成開始され、
その後「AWSUtility-CloudFormation-CommandRunner-xxxxxxxxxx」というスタックが作成開始されました。
【sampleスタック】
【AWSUtility-CloudFormation-CommandRunner-xxxxxxxxxxスタック】
コマンド実行用のEC2インスタンス、SecutiryGroup、そしてWaitCondition、WaitConditionHandleが作成されている事がわかります。
↓
コマンドを実行し終わったのか、AWSUtility-CloudFormation-CommandRunner-xxxxxxxxxxスタックは削除が開始されました。
以下イベントタブです。
EC2についてはTerminateが完了したものを見てみました。
私は実行した時点ではt2.midiumのAmazon Linux 2が選択されていたようです。
テンプレートタブに表示されたjsonは整形されていませんでしたが、変換した結果は以下となります。
Parameters:
SubnetId:
Type: 'AWS::EC2::Subnet::Id'
Timeout:
Type: String
Default: '600'
AMIId:
Type: String
Default: ami-062f7200baf2fa504
InstanceType:
Type: String
Default: t2.medium
IamInstanceProfile:
Type: String
Default: empty
SecurityGroupId:
Type: String
Default: empty
VpcId:
Type: String
Default: empty
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
Conditions:
CreateSecurityGroup:
'Fn::Equals':
-
Ref: SecurityGroupId
- empty
UseInstanceProfile:
'Fn::Not':
-
'Fn::Equals':
-
Ref: IamInstanceProfile
- empty
Resources:
SecurityGroup:
Condition: CreateSecurityGroup
Type: 'AWS::EC2::SecurityGroup'
Properties:
GroupName:
'Fn::Sub': 'aws-cloudformation-commandrunner-temp-sg-${AWS::StackName}}'
GroupDescription: 'A temporary security group for AWS::CloudFormation::Command'
SecurityGroupEgress:
-
CidrIp: 0.0.0.0/0
FromPort: -1
IpProtocol: -1
ToPort: -1
VpcId:
Ref: VpcId
EC2Instance:
Type: 'AWS::EC2::Instance'
Metadata:
'AWS::CloudFormation::Init':
config:
packages:
yum:
awslogs: { }
files:
/etc/awslogs/awslogs.conf:
content:
'Fn::Sub': "[general]\nstate_file= /var/awslogs/state/agent-state\n[/var/log/cloud-init.log]\nfile = /var/log/cloud-init.log\\n\nlog_group_name = ${LogGroup}\nlog_stream_name = {instance_id}/cloud-init.log\n[/var/log/cloud-init-output.log]\nfile = /var/log/cloud-init-output.log\nlog_group_name = ${LogGroup}\nlog_stream_name = {instance_id}/cloud-init-output.log\n[/var/log/cfn-init.log]\nfile = /var/log/cfn-init.log\nlog_group_name = ${LogGroup}\nlog_stream_name = {instance_id}/cfn-init.log\n[/var/log/cfn-hup.log]\nfile = /var/log/cfn-hup.log\nlog_group_name = ${LogGroup}\nlog_stream_name = {instance_id}/cfn-hup.log\n[/var/log/cfn-wire.log]\nfile = /var/log/cfn-wire.log\nlog_group_name = ${LogGroup}\nlog_stream_name = {instance_id}/cfn-wire.log\n"
mode: '000444'
owner: root
group: root
/etc/awslogs/awscli.conf:
content:
'Fn::Sub': "[plugins]\ncwlogs = cwlogs\n[default]\nregion = ${AWS::Region}\n"
mode: '000444'
owner: root
group: root
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\nyum install -y aws-cfn-bootstrap\n/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource EC2Instance --region ${AWS::Region}\n${Command}\n/opt/aws/bin/cfn-signal -r 'Command ran successfully.' -e 0 --id 'Command Output' --data \"$(cat /command-output.txt)\" '${WaitConditionHandle}'\necho Contents of /command-output.txt = $(cat /command-output.txt)"
InstanceType:
Ref: InstanceType
SecurityGroupIds:
-
'Fn::If':
- CreateSecurityGroup
-
Ref: SecurityGroup
-
Ref: SecurityGroupId
ImageId:
Ref: AMIId
SubnetId:
Ref: SubnetId
IamInstanceProfile:
'Fn::If':
- UseInstanceProfile
-
Ref: IamInstanceProfile
-
Ref: 'AWS::NoValue'
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
以下githubに構文や使用例など詳細が丁寧に記載されています。
中でも、「よくある質問」は読んでいてなるほどという部分があったので翻訳して転載しました。
元となったスタックの内容にはリソースの記載はあるものの、
実際の実行に必要だったリソースを纏めたスタックは削除されてしまう所など、
個人的には若干の説明しにくい気持ち悪さがあるなと思いましたが、使う方によってはユースケースもありそうな気がしました。
以上でした
お読みいただき有難うございました。
当該リソースタイプの存在に気付かせてくださったブログ
Discussion