CloudFormationでEC2、ALBを作成する

# はじめに

以下記事の続きです。
[CloudFormationでVPC、サブネット、インターネットゲートウェイを作成する](https://qiita.com/gogo-muscle/items/3ef45a6fca4bf334bbaa)

* VPCID(`VpcId:`)とサブネットID(`SubnetId:`)は、上記記事でエクスポートした値を使っています。
* 本記事ではすでに作成済のVPC、サブネットを使いEC2とALBをCloudForrmationで作成します。
* EC2は一つだけ作成しています。
* EC2のAMIは、最新のAmazon Linuxイメージを指定しています。 [最新AMI取得方法](https://aws.amazon.com/jp/blogs/news/query-for-the-latest-amazon-linux-ami-ids-using-aws-systems-manager-parameter-store/)
* どのリージョンでも動くはず

# EC2、ALB作成コード
~~~yaml:Create-ec2.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: Create EC2 Instance
Parameters:
  InstanceType:
    Description: WebServer EC2 instance type
    Type: String
    Default: t2.micro
    AllowedValues:
    - t1.micro
    - t2.nano
    - t2.micro
    - t2.small
    - t2.medium
    - t2.large
  DiskSize:
    Description : EC2 VolumeSize (Gigabyte)
    Default: 8
    Type: String
  Ec2ImageId:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
  KeyName:
    Description : Name of an existing EC2 KeyPair
    Type: AWS::EC2::KeyPair::KeyName
    ConstraintDescription : Can contain only ASCII characters.
  SSHLocation:
    Description: 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.
  HealthCheckPath:
    Description : Webserver HealthCheckPath
    Default: "/"
    Type: String


# ------------------------------------------------------------#
# EC2 Create
# ------------------------------------------------------------#
Description: Create EC2 Instance
Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: !Ref InstanceType
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: 0
          SubnetId: !ImportValue TESTSTACK-PublicSubnet0
          GroupSet:
            - !Ref InstanceSecurityGroup
      BlockDeviceMappings:
        -
          DeviceName: /dev/xvda
          Ebs:
            VolumeType: gp2
            VolumeSize: !Ref DiskSize
      Tags:
      - Key: Name
        Value: myInstance
      KeyName: !Ref KeyName
      ImageId: !Ref Ec2ImageId

# ------------------------------------------------------------#
# Application LoadBalancer
# ------------------------------------------------------------#
  ApplicationLoadBalancer:
    Type: 'AWS::ElasticLoadBalancingV2::LoadBalancer'
    Properties:
      Subnets:
        - !ImportValue TESTSTACK-PublicSubnet0
        - !ImportValue TESTSTACK-PublicSubnet1
      SecurityGroups:
        - !Ref ALBSecurityGroup
  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
      HealthCheckPath: !Ref HealthCheckPath
      Port: 80
      Protocol: HTTP
      UnhealthyThresholdCount: 5
      VpcId: !ImportValue TESTSTACK-VPCID
      Targets:
        - Id: !Ref MyEC2Instance
          Port: 80

# ------------------------------------------------------------#
# Instance Srcurity Groups
# ------------------------------------------------------------#
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: connect with ssh and webservice
      VpcId: !ImportValue TESTSTACK-VPCID
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref SSHLocation
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0

# ------------------------------------------------------------#
# ALB Srcurity Groups
# ------------------------------------------------------------#
  ALBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: ALB access
      VpcId: !ImportValue TESTSTACK-VPCID
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0

# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#
Outputs:
  ALBDNSName:
    Value: !GetAtt ApplicationLoadBalancer.DNSName
    Export:
      Name: "alb-dnsname"
  InstanceSecurityGroupID:
    Value: !GetAtt InstanceSecurityGroup.GroupId
    Export:
      Name: "web-sg"
  ALBSecurityGroupID:
    Value: !GetAtt ALBSecurityGroup.GroupId
    Export:
      Name: "alb-sg"

~~~

なお、最新のAmazon LinuxAMIではなく決め打ちしたい場合は`Mappings:`でAMI IDを指定します。同じ名前のAMIでもリージョンによってAMI IDが違うのでマルチリージョン対応するには、リージョン毎にAMI IDを指定します。


~~~yaml
# ------------------------------------------------------------#
# Mappings
# ------------------------------------------------------------#
Mappings:
  RegionMap:
    ap-northeast-1:
      AMI: ami-0f310fced6141e627
    us-east-1:
      AMI: ami-0323c3dd2da7fb37d

# ------------------------------------------------------------#
# EC2 Create
# ------------------------------------------------------------#
Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
#~~~省略~~~~
      ImageId: !FindInMap
        - RegionMap
        - !Ref 'AWS::Region'
        - AMI
~~~

# AWS CLIでスタックの作成

AWS CLIで登録します。
デフォルト値(`Default:`)をスタック内で指定してないパラメータは`--parameters`オプションで指定しときます。

~~~shell
% aws cloudformation create-stack \
--tags Key="name",Value="test" \
--stack-name TESTEC2 \
--template-body file://Create-ec2.yml \
--parameters \
ParameterKey=KeyName,ParameterValue="test-ec2-sshkey"
~~~

成功したか確認します。

~~~shell
% aws cloudformation list-stacks \
--stack-status-filter CREATE_COMPLETE \
| jq -r ".StackSummaries[].StackName" | head -n1

TESTEC2
~~~

OutputでALBのDNSNameをエクスポートしているので参照できます。

~~~shell
% aws cloudformation describe-stacks --stack-name TESTEC2 \
| jq -r '.Stacks[].Outputs[]|select(.OutputKey == "ALBDNSName").OutputValue'

TESTE-Appli-1K4WKCRZ4AW5W-1349760917.ap-northeast-1.elb.amazonaws.com
~~~

要らなくなったら削除します。

~~~shell
% aws cloudformation delete-stack --stack-name TESTEC2
~~~