AWS再入門2018 上級者でもたまに忘れる!? AWS CloudFormation 小ネタ編

こんにちは。池田です。札幌では八重桜がちらほら咲き始めてきました。道産子にもようやく屋外焼肉シーズンがやってまいりました。
※ ハマりポイントというよりは「これ忘れることあるよねー」という意図でしたので、タイトルと本文の一部を追記・変更しました。

はじめに

早速ですが問題です。マネジメントコンソールにてテスト用の新規VPNとサブネット2つを作成し、下記の(某所からコピーしてちょっとカスタマイズした)AWS CloudFormationテンプレート(225行)を実行したところ、図のようなエラーにより失敗してしまいました。その原因と、最も簡単に解決するためのテンプレート修正方法を見つけられるでしょうか。制限時間は5分です。ではスクロールし過ぎのネタバレに気をつけてご覧ください。

AWSTemplateFormatVersion: 2010-09-09
Description: >-
  Test Template.
Parameters:
  VpcId:
    Type: 'AWS::EC2::VPC::Id'
    Description: VpcId of your existing Virtual Private Cloud (VPC)
  Subnets:
    Type: 'List<AWS::EC2::Subnet::Id>'
    Description: The list of SubnetIds in your Virtual Private Cloud (VPC)
  InstanceType:
    Description: WebServer EC2 instance type
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.nano
      - t2.micro
      - t2.small
    ConstraintDescription: must be a valid EC2 instance type.
  KeyName:
    Description: Name of an existing EC2 KeyPair to enable SSH access to the instances
    Type: 'AWS::EC2::KeyPair::KeyName'
    ConstraintDescription: must be the name of an existing EC2 KeyPair.
  SSHLocation:
    Description: The IP address range that can be used to SSH to the EC2 instances
    Type: String
    MinLength: '9'
    MaxLength: '18'
    Default: 0.0.0.0/0
    AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})'
    ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.
Mappings:
  AWSInstanceType2Arch:
    t2.nano:
      Arch: HVM64
    t2.micro:
      Arch: HVM64
    t2.small:
      Arch: HVM64

  AWSInstanceType2NATArch:
    t2.nano:
      Arch: NATHVM64
    t2.micro:
      Arch: NATHVM64
    t2.small:
      Arch: NATHVM64

  AWSRegionArch2AMI:
    ap-northeast-1:
      PV64: ami-3e42b65f
      HVM64: ami-ceafcba8
      HVMG2: ami-edfd658b

Resources:
  WebServerGroup:
    Type: 'AWS::AutoScaling::AutoScalingGroup'
    Properties:
      VPCZoneIdentifier: !Ref Subnets
      LaunchConfigurationName: !Ref LaunchConfig
      MinSize: '2'
      MaxSize: '2'
      TargetGroupARNs:
        - !Ref ALBTargetGroup
    CreationPolicy:
      ResourceSignal:
        Timeout: PT15M
    UpdatePolicy:
      AutoScalingRollingUpdate:
        MinInstancesInService: '1'
        MaxBatchSize: '1'
        PauseTime: PT15M
        WaitOnResourceSignals: 'true'
  LaunchConfig:
    Type: 'AWS::AutoScaling::LaunchConfiguration'
    Metadata:
      Comment: Install a simple application
      'AWS::CloudFormation::Init':
        config:
          packages:
            yum:
              httpd: []
          files:
            /var/www/html/index.html:
              content: !Join
                - |+

                - - >-
                    <h3>You have successfully launched the AWS
                    CloudFormation test.</h3>
              mode: '000644'
              owner: root
              group: root
            /etc/cfn/cfn-hup.conf:
              content: !Join
                - ''
                - - |
                    [main]
                  - stack=
                  - !Ref 'AWS::StackId'
                  - |+

                  - region=
                  - !Ref 'AWS::Region'
                  - |+

              mode: '000400'
              owner: root
              group: root
            /etc/cfn/hooks.d/cfn-auto-reloader.conf:
              content: !Join
                - ''
                - - |
                    [cfn-auto-reloader-hook]
                  - |
                    triggers=post.update
                  - >
                    path=Resources.LaunchConfig.Metadata.AWS::CloudFormation::Init
                  - 'action=/opt/aws/bin/cfn-init -v '
                  - '         --stack '
                  - !Ref 'AWS::StackName'
                  - '         --resource LaunchConfig '
                  - '         --region '
                  - !Ref 'AWS::Region'
                  - |+

                  - |
                    runas=root
              mode: '000400'
              owner: root
              group: root
          services:
            sysvinit:
              httpd:
                enabled: 'true'
                ensureRunning: 'true'
              cfn-hup:
                enabled: 'true'
                ensureRunning: 'true'
                files:
                  - /etc/cfn/cfn-hup.conf
                  - /etc/cfn/hooks.d/cfn-auto-reloader.conf
    Properties:
      ImageId: !FindInMap
        - AWSRegionArch2AMI
        - !Ref 'AWS::Region'
        - !FindInMap
          - AWSInstanceType2Arch
          - !Ref InstanceType
          - Arch
      SecurityGroups:
        - !Ref InstanceSecurityGroup
      InstanceType: !Ref InstanceType
      KeyName: !Ref KeyName
      UserData: !Base64
        'Fn::Join':
          - ''
          - - |
              #!/bin/bash -xe
            - |
              yum update -y aws-cfn-bootstrap
            - '/opt/aws/bin/cfn-init -v '
            - '         --stack '
            - !Ref 'AWS::StackName'
            - '         --resource LaunchConfig '
            - '         --region '
            - !Ref 'AWS::Region'
            - |+

            - '/opt/aws/bin/cfn-signal -e $? '
            - '         --stack '
            - !Ref 'AWS::StackName'
            - '         --resource WebServerGroup '
            - '         --region '
            - !Ref 'AWS::Region'
            - |+

  ApplicationLoadBalancer:
    Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
    Properties:
      Subnets: !Ref Subnets
      SecurityGroups:
        - !Ref InstanceSecurityGroup
  ALBListener:
    Type: 'AWS::ElasticLoadBalancingV2::Listener'
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref ALBTargetGroup
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: '80'
      Protocol: HTTP
  ALBTargetGroup:
    Type: 'AWS::ElasticLoadBalancingV2::TargetGroup'
    Properties:
      HealthCheckIntervalSeconds: 30
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 3
      Port: 80
      Protocol: HTTP
      UnhealthyThresholdCount: 5
      VpcId: !Ref VpcId
  InstanceSecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      GroupDescription: Enable SSH access and HTTP access on the inbound port
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '80'
          ToPort: '80'
          CidrIp: !Ref SSHLocation
        - IpProtocol: tcp
          FromPort: '22'
          ToPort: '22'
          CidrIp: !Ref SSHLocation
      VpcId: !Ref VpcId
Outputs:
  URL:
    Description: URL of the website
    Value: !Join
      - ''
      - - 'http://'
        - !GetAtt
          - ApplicationLoadBalancer
          - DNSName

正解

原因はわかりましたでしょうか。実は初心者に限らずとも、つい見落としがちなミスが原因でした。

    Properties:
      AssociatePublicIpAddress: true
      ImageId: !FindInMap
        - AWSRegionArch2AMI
        - !Ref 'AWS::Region'
        - !FindInMap
          - AWSInstanceType2Arch
          - !Ref InstanceType
          - Arch

AssociatePublicIpAddress
VPC の Amazon EC2 インスタンスで、Auto Scaling グループのインスタンスがパブリック IP アドレスを受け取るかどうかを示します。true の場合には、Auto Scaling の各インスタンスが一意のパブリック IP を受け取ります。
AWS::AutoScaling::LaunchConfiguration

そうです。CloudFormationにより作成されたEC2インスタンスにパブリックIPアドレスが割り当てられていないことで、yumコマンドが完了せず、インスタンス作成のsuccessシグナルが送られないままタイムアウトが発生し、ロールバックされていたのでした。

まとめ

慣れたつもりでヒョイヒョイっとコピペして使うと、こういううっかりミスに遭遇して(マネジメントコンソールに表示される簡易エラー表示からは連想できずに)ハマってしまうことがあるよね。新規作成したVPC側の設定忘れはよくやらかすわー。などという話題がオフィスで出たのでご紹介でした。
※ 他にもサブネットを作成するときにPublic IPの自動付与をしておく、NAT経由で外部との通信が可能となるようにするなど、複数の解消方法はありますが、もっとも簡単な(テンプレートに1行追記するだけで済む)方法を今回の正解とさせていただきました。