🏃

CFnからBash実行まで行うAWSUtility::CloudFormation::CommandRunnerを試してみた。

2023/10/03に公開

AWSUtility::CloudFormation::CommandRunner

2020年11月9日に存在していた記事となります。
パブリックレジストリの発表が2021年6月24日との事なので、
CloudFormationレジストリのリソースタイプとしては更に古株という事になりそうです。
https://aws.amazon.com/jp/blogs/mt/running-bash-commands-in-aws-cloudformation-templates/

https://repost.aws/ja/knowledge-center/cloudformation-commandrunner-stack

早速試してみます。


事前準備

ターミナルから以下を実行します。

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等のパブリッシャーとは異なり、「非公開登録」のリソースタイプである事がわかります。


利用してみる

同記事内に付属していたサンプルを実行してみます。

sample.yml
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は整形されていませんでしたが、変換した結果は以下となります。

AWSUtility-CloudFormation-CommandRunner.yml
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に構文や使用例など詳細が丁寧に記載されています。
https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-awsutilities-commandrunner

中でも、「よくある質問」は読んでいてなるほどという部分があったので翻訳して転載しました。

元となったスタックの内容にはリソースの記載はあるものの、
実際の実行に必要だったリソースを纏めたスタックは削除されてしまう所など、
個人的には若干の説明しにくい気持ち悪さがあるなと思いましたが、使う方によってはユースケースもありそうな気がしました。


以上でした

お読みいただき有難うございました。


当該リソースタイプの存在に気付かせてくださったブログ

https://zenn.dev/5hintaro/scraps/f5dadfc7178be0

Discussion