ハンズオン(簡易版): CFnテンプレート入門(EC2::VolumeAttachment)

2.1. CloudFormationテンプレートの作成 (handson-cli-cfn-ec2-VolumeAttachment)

手順の目的 [why]

CloudFormationスタック"handson-cli-cfn-ec2-VolumeAttachment"を作成します。

設定値の指定

設定値の指定

手順に必要な設定値を変数に格納をします。

1. リソースファイルディレクトリ

リソースファイルディレクトリを指定します。

変数の設定:

DIR_CLOUDFORMATION_RESOURCE="${HOME}/environment/conf-handson-cli-cfn-ec2-VolumeAttachment/resources"

2. CloudFormationテンプレート名

CloudFormationテンプレート名を指定します。

変数の設定:

CLOUDFORMATION_TEMPLATE_NAME='handson-cli-cfn-ec2-VolumeAttachment'

3. CloudFormationテンプレートの説明

CloudFormationテンプレートの説明を指定します。

変数の設定:

CLOUDFORMATION_TEMPLATE_DESCRIPTION='Template for handson-cli-cfn-ec2-VolumeAttachment.'

4. テンプレートファイルディレクトリ

テンプレートファイルディレクトリを指定します。

変数の設定:

DIR_CLOUDFORMATION_TEMPLATE="${HOME}/environment/conf-handson-cli-cfn-ec2-VolumeAttachment"

ディレクトリが存在することを確認します。

コマンド:

ls -d ${DIR_CLOUDFORMATION_TEMPLATE}

結果(例:存在する場合):

${HOME}/environment/conf-handson-cli-cfn-ec2-VolumeAttachment

存在しない場合は作成します。

コマンド:

mkdir -p ${DIR_CLOUDFORMATION_TEMPLATE}

5. テンプレートファイル名の指定

テンプレートファイル名の指定します。

変数の設定:

FILE_CLOUDFORMATION_TEMPLATE="${DIR_CLOUDFORMATION_TEMPLATE}/${CLOUDFORMATION_TEMPLATE_NAME}.template" \
  && echo ${FILE_CLOUDFORMATION_TEMPLATE}

結果(例):

${HOME}/environment/conf-handson-cli-cfn-ec2-VolumeAttachment/handson-cli-cfn-ec2-VolumeAttachment.template

設定値の確認

各変数に正しい設定値が格納されていることを確認しながら保存します。

変数の確認:

cat << END

  # 1. DIR_CLOUDFORMATION_RESOURCE:"${HOME}/environment/conf-handson-cli-cfn-ec2-VolumeAttachment/resources"
       DIR_CLOUDFORMATION_RESOURCE="${DIR_CLOUDFORMATION_RESOURCE}"
  # 2. CLOUDFORMATION_TEMPLATE_NAME:"handson-cli-cfn-ec2-VolumeAttachment"
       CLOUDFORMATION_TEMPLATE_NAME="${CLOUDFORMATION_TEMPLATE_NAME}"
  # 3. CLOUDFORMATION_TEMPLATE_DESCRIPTION:"Template for handson-cli-cfn-ec2-VolumeAttachment."
       CLOUDFORMATION_TEMPLATE_DESCRIPTION="${CLOUDFORMATION_TEMPLATE_DESCRIPTION}"
  # 4. DIR_CLOUDFORMATION_TEMPLATE:"${HOME}/environment/conf-handson-cli-cfn-ec2-VolumeAttachment"
       DIR_CLOUDFORMATION_TEMPLATE="${DIR_CLOUDFORMATION_TEMPLATE}"
  # 5. FILE_CLOUDFORMATION_TEMPLATE:"${HOME}/environment/conf-handson-cli-cfn-ec2-VolumeAttachment/handson-cli-cfn-ec2-VolumeAttachment.template"
       FILE_CLOUDFORMATION_TEMPLATE="${FILE_CLOUDFORMATION_TEMPLATE}"

END

下段の変数が入っていない、もしくは上段と同等の値が入っていない場合は、それぞれの手順番号に戻って変数の設定を行います。

処理の実行

CloudFormationテンプレート共通部分を作成します。

変数の確認:

cat << END

  # FILE_CLOUDFORMATION_TEMPLATE:"${HOME}/environment/conf-handson-cli-cfn-ec2-VolumeAttachment/handson-cli-cfn-ec2-VolumeAttachment.template"
    FILE_CLOUDFORMATION_TEMPLATE="${FILE_CLOUDFORMATION_TEMPLATE}"
  # CLOUDFORMATION_TEMPLATE_DESCRIPTION:"Template for handson-cli-cfn-ec2-VolumeAttachment."
    CLOUDFORMATION_TEMPLATE_DESCRIPTION="${CLOUDFORMATION_TEMPLATE_DESCRIPTION}"

END

コマンド:

cat << EOF > ${FILE_CLOUDFORMATION_TEMPLATE}
AWSTemplateFormatVersion: 2010-09-09
Description:
  ${CLOUDFORMATION_TEMPLATE_DESCRIPTION}

EOF

cat ${FILE_CLOUDFORMATION_TEMPLATE}

結果(例):

AWSTemplateFormatVersion: 2010-09-09
Description:
  Template for handson-cli-cfn-ec2-VolumeAttachment.

CloudFormationテンプレートにリソース部分を追加します。

コマンド:

echo 'Resources:' >> ${FILE_CLOUDFORMATION_TEMPLATE}

for i in $( ls ${DIR_CLOUDFORMATION_RESOURCE}/*.txt ); do \
  cat $i | sed '/^$/d' >> ${FILE_CLOUDFORMATION_TEMPLATE}; \
  echo '' >> ${FILE_CLOUDFORMATION_TEMPLATE} \
; done

cat ${FILE_CLOUDFORMATION_TEMPLATE}

結果(例):

AWSTemplateFormatVersion: 2010-09-09
Description:
  Template for handson-cli

Resources:
  Igw0:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Join
            - ''
            - - !Ref AWS::StackName
              - '-igw'

  IgwAttachment0:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref Igw0
      VpcId: !Ref Vpc0

  InstanceProfile0:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: !Join
        - ''
        - - !Ref AWS::StackName
          - '-instance-profile'
      Path: /handson-cli/
      Roles:
        - !Ref Role0

  LogsGroup0:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: /var/log/secure

  Role0:
    Type: AWS::IAM::Role
    Properties:
      Path: /handson-cli/
      RoleName: !Join
        - ''
        - - !Ref AWS::StackName
          - '-role'
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess

  RouteDefault:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref RouteTable0
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref Igw0

  RouteTable0:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref Vpc0
      Tags:
        - Key: Name
          Value: !Join
            - ''
            - - !Ref AWS::StackName
              - '-routetable'

  SecurityGroup0:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref Vpc0
      GroupDescription: for handson-cli-cfn-ec2-Instance.
      GroupName: !Join
        - ''
        - - !Ref AWS::StackName
          - '-sg'

  SGIngress0Cidr:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !GetAtt SecurityGroup0.GroupId
      Description: Rule for handson-cli-cfn-ec2-Instance.
      IpProtocol: tcp
      FromPort: 22022
      ToPort: 22022
      CidrIp: 0.0.0.0/0

  Subnet0:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref Vpc0
      CidrBlock: 10.0.3.0/24
      Tags:
        - Key: Name
          Value: !Join
            - ''
            - - !Ref AWS::StackName
              - '-subnet'
      AvailabilityZone: ap-northeast-1c

  SubnetRouteTableAttachment0:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTable0
      SubnetId: !Ref Subnet0

  Vpc0:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      Tags:
        - Key: Name
          Value: !Join
            - ''
            - - !Ref AWS::StackName
              - '-vpc'

  Instance0:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-09ebacdc178ae23b7
      InstanceType: t3.micro
      Tags:
        - Key: Name
          Value: !Ref AWS::StackName
      NetworkInterfaces:
        - DeviceIndex: "0"
          SubnetId: !Ref Subnet0
          GroupSet:
          - !Ref SecurityGroup0
          AssociatePublicIpAddress: "true"
      UserData: !Base64 |
        #!/bin/bash
        # configure sshd
        cat << EOF >> /etc/ssh/sshd_config
        Port 22022
        EOF
        systemctl restart sshd.service
        # yum
        yum update -y
        #
        # logs agent
        #
        readonly EC2_METADATA_SECOND='900'
        readonly EC2_METADATA_TOKEN=$( \
          curl -s \
            -X PUT "http://169.254.169.254/latest/api/token" \
            -H "X-aws-ec2-metadata-token-ttl-seconds: ${EC2_METADATA_SECOND}" \
        )
        readonly EC2_METADATA_HEADER="X-aws-ec2-metadata-token: ${EC2_METADATA_TOKEN}"
        readonly EC2_REGION_NAME=$( \
          curl -s -H "${EC2_METADATA_HEADER}" \
            http://169.254.169.254/latest/meta-data/placement/availability-zone \
          | sed -e 's/[a-z]*$//' \
        )
        # install cloudwatch agent
        readonly URL_DOWNLOAD_LINK="https://s3.${EC2_REGION_NAME}.amazonaws.com/amazoncloudwatch-agent-${EC2_REGION_NAME}/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm"
        cd /tmp/
        wget ${URL_DOWNLOAD_LINK}
        rpm -U ./amazon-cloudwatch-agent.rpm
        # setup cloudwatch agent
        readonly LOGS_CONF='/opt/aws/amazon-cloudwatch-agent/etc/logs.conf'
        # logs.conf
        cat << EOF > ${LOGS_CONF}
        {
          "agent": {
            "logfile": "/opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log"
          },
          "logs": {
            "logs_collected": {
              "files": {
                "collect_list": [
                  {
                    "file_path": "/var/log/secure",
                    "log_group_name": "/var/log/secure",
                    "timezone": "UTC"
                  }
                ]
              }
            },
            "log_stream_name": "{instance_id}",
            "force_flush_interval" : 15
          }
        }
        EOF
        # start cloudwatch agent
        /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
          -a fetch-config \
          -m ec2 \
          -c file:${LOGS_CONF} \
          -s
        #
        # disk format (sdh)
        #
        DEVICE_FILE_NAME="sdh"
        MOUNT_POINT_NAME="/mnt-handson-cli-ebs"
        DISK_TYPE='xfs'
        DISK_TARGET="/dev/${DEVICE_FILE_NAME}"
        # format disk
        mkfs \
           -t ${DISK_TYPE} \
           ${DISK_TARGET}
        # make mount point
        mkdir ${MOUNT_POINT_NAME}
        # update fstab
        cp /etc/fstab /etc/fstab.$(date +%s)
        DEVICE_LINK_NAME=$( \
          file -s ${DISK_TARGET} \
          | sed "s/^.*\`//" \
          | sed "s/'$//" \
        )
        DISK_UUID=$( \
          sudo blkid \
            | grep /dev/${DEVICE_LINK_NAME} \
            | sed "s/^.*UUID=\"//" \
            | sed "s/\".*$//" \
        )
        DISK_TYPE_TARGET=$( \
          sudo blkid \
            | grep /dev/${DEVICE_LINK_NAME} \
            | sed "s/^.*TYPE=\"//" \
            | sed "s/\".*$//" \
        )
        if [ ! $( grep ${DISK_UUID} /etc/fstab ) ]; then
          sh -c "echo \"UUID=${DISK_UUID}     ${MOUNT_POINT_NAME} ${DISK_TYPE_TARGET} defaults,nofail  0  2\" >> /etc/fstab"
        fi
        # mount disk
        mount ${MOUNT_POINT_NAME}
        chown ec2-user:ec2-user ${MOUNT_POINT_NAME}
      KeyName: handson-cli-cfn-ec2-VolumeAttachment-keypair
      IamInstanceProfile: !Ref InstanceProfile0

  Volume0:
    Type: AWS::EC2::Volume
    Properties:
      AvailabilityZone: ap-northeast-1c
      VolumeType: gp3
      Size: 8
      Tags:
        - Key: Name
          Value: !Ref AWS::StackName

  VolumeAttachment0:
    Type: AWS::EC2::VolumeAttachment
    Properties:
      VolumeId: !Ref Volume0
      InstanceId: !Ref Instance0
      Device: /dev/sdh

完了確認

本手順の主処理は、以下の完了条件を満たしたときに成功したものとします。

完了条件1: CloudFormationテンプレート"handson-cli-cfn-ec2-VolumeAttachment"が存在する。

「CloudFormationテンプレート"handson-cli-cfn-ec2-VolumeAttachment"が存在する。」ことを確認します。

コマンド:

ls ${FILE_CLOUDFORMATION_TEMPLATE}

結果(例):

${HOME}/environment/conf-handson-cli-cfn-ec2-VolumeAttachment/handson-cli-cfn-ec2-VolumeAttachment.template

完了条件2: CloudFormationテンプレート"handson-cli-cfn-ec2-VolumeAttachment"がYAMLフォーマットとして正常である。

「CloudFormationテンプレート"handson-cli-cfn-ec2-VolumeAttachment"がYAMLフォーマットとして正常である。」ことを確認します。

コマンド:

yamllint ${FILE_CLOUDFORMATION_TEMPLATE}

結果(例):

(出力なし)

手順の完了