想ひ出のへっぽこBlogⅡ from35

~ 自身の備忘録および学習促進のためにブログります。~

想ひ出24: AWS/CloudFormationに入門する/2

f:id:moqrin3:20190114193742p:plain

おはようございます、moqrinです。

前回の概要に続き、今回はCloudFormationの基本的な対応のまとめです。

スタックの作成、更新という基本操作を行っていきます。

内容としては、新規VPCを作成して、パブリックサブネットとプライベートサブネットを各2つずつ、 ALBを作成してパブリックサブネットを紐付けて、RDSを作って、Auto Scaling グループ内に Amazon EC2 インスタンスを作成します。

AWS CloudFormation Designerから図示したものがこちらです。

f:id:moqrin3:20190126153038p:plain

中身としては、サンプルソリューションの「スケーラブルで耐久性の高い WordPress」に VPCとPublic,Privateのサブネットを新規追加したものですね。

目次

1. テンプレートでスタックを新規作成

2. スタックを変更セットを使って更新

3. 宿題


1. テンプレートでスタックを新規作成

下記のテンプレートからスタックを作成します。

# VPC_Multi_RDS_AutoScaling_EC2_instance.json
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "AWS CloudFormation VPC_RDS_ALB_AutoScaling_EC2Instances",

  "Parameters" : {

  "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."
  },
  "DBName": {
    "Default": "myDatabase",
    "Description": "MySQL database name",
    "Type": "String",
    "MinLength": "8",
    "MaxLength": "64",
    "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*",
    "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters."
  },
  "DBUser": {
    "NoEcho": "true",
    "Default": "root",
    "Description": "Username for MySQL database access",
    "Type": "String",
    "MinLength": "3",
    "MaxLength": "16",
    "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*",
    "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters."
  },
  "DBPassword": {
    "NoEcho": "true",
    "Default": "samplePasswd",
    "Description": "Password for MySQL database access",
    "Type": "String",
    "MinLength": "8",
    "MaxLength": "41",
    "AllowedPattern": "[a-zA-Z0-9]*",
    "ConstraintDescription": "must contain only alphanumeric characters."
  },
  "DBAllocatedStorage": {
    "Default": "5",
    "Description": "The size of the database (Gb)",
    "Type": "Number",
    "MinValue": "5",
    "MaxValue": "1024",
    "ConstraintDescription": "must be between 5 and 1024Gb."
  },
  "DBInstanceClass": {
    "Description": "The database instance type",
    "Type": "String",
    "Default": "db.t2.micro",
    "AllowedValues": [
      "db.t1.micro",
      "db.t2.micro"
    ],
    "ConstraintDescription": "must select a valid database instance type."
  },
  "MultiAZDatabase": {
    "Default": "false",
    "Description": "Create a Multi-AZ MySQL Amazon RDS database instance",
    "Type": "String",
    "AllowedValues": [
      "true",
      "false"
    ],
    "ConstraintDescription": "must be either true or false."
  },
  "WebServerCapacity": {
    "Default": "1",
    "Description": "The initial number of WebServer instances",
    "Type": "Number",
    "MinValue": "1",
    "MaxValue": "3",
    "ConstraintDescription": "must be between 1 and 3 EC2 instances."
  },
  "InstanceType": {
    "Description": "WebServer EC2 instance type",
    "Type": "String",
    "Default": "t2.micro",
    "AllowedValues": [
      "t1.micro",
      "t2.nano",
      "t2.micro"
    ],
    "ConstraintDescription": "must be a valid EC2 instance type."
  },
  "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" : {
      "t1.micro"    : { "Arch" : "HVM64"  },
      "t2.nano"     : { "Arch" : "HVM64"  },
      "t2.micro"    : { "Arch" : "HVM64"  }
    },

    "AWSRegionArch2AMI" : {
      "us-east-1"        : {"HVM64" : "ami-0ff8a91507f77f867", "HVMG2" : "ami-0a584ac55a7631c0c"},
      "us-west-2"        : {"HVM64" : "ami-a0cfeed8", "HVMG2" : "ami-0e09505bc235aa82d"},
      "us-west-1"        : {"HVM64" : "ami-0bdb828fd58c52235", "HVMG2" : "ami-066ee5fd4a9ef77f1"},
      "ap-northeast-1"   : {"HVM64" : "ami-00a5245b4816c38e6", "HVMG2" : "ami-053cdd503598e4a9d"}
    }

  },

  "Resources" : {
    "VPC" : {
      "Type" : "AWS::EC2::VPC",
      "Properties" : {
        "CidrBlock" : "10.0.0.0/16",
        "Tags" : [ {"Key" : "Name", "Value" : "Sample-VPC" },{"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
      }
    },

    "PublicSubnet1" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : "10.0.0.0/24",
        "AvailabilityZone" : { "Fn::Select" : [ "0", { "Fn::GetAZs" : { "Ref" : "AWS::Region" } }]},
        "Tags" : [ {"Key" : "Name", "Value" : "PublicSubnet1" },{"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
      }
    },

    "PublicSubnet2" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : "10.0.1.0/24",
        "AvailabilityZone" : { "Fn::Select" : [ "1", { "Fn::GetAZs" : { "Ref" : "AWS::Region" } }]},
        "Tags" : [ {"Key" : "Name", "Value" : "PublicSubnet2" },{"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
      }
    },

    "PrivateSubnet1" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : "10.0.2.0/24",
        "AvailabilityZone" : { "Fn::Select" : [ "0", { "Fn::GetAZs" : { "Ref" : "AWS::Region" } }]},
        "Tags" : [ {"Key" : "Name", "Value" : "PrivateSubnet1" },{"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
      }
    },

    "PrivateSubnet2" : {
      "Type" : "AWS::EC2::Subnet",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "CidrBlock" : "10.0.3.0/24",
        "AvailabilityZone" : { "Fn::Select" : [ "1", { "Fn::GetAZs" : { "Ref" : "AWS::Region" } }]},
        "Tags" : [ {"Key" : "Name", "Value" : "PrivateSubnet1" },{"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
      }
    },

    "InternetGateway" : {
      "Type" : "AWS::EC2::InternetGateway",
      "Properties" : {
        "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
      }
    },

    "AttachGateway" : {
      "Type" : "AWS::EC2::VPCGatewayAttachment",
      "Properties" : {
        "VpcId" : { "Ref" : "VPC" },
        "InternetGatewayId" : { "Ref" : "InternetGateway" }
      }
    },

    "PublicRouteTable" : {
      "Type" : "AWS::EC2::RouteTable",
      "Properties" : {
        "VpcId" : {"Ref" : "VPC"},
        "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
      }
    },

    "PublicRoute" : {
      "Type" : "AWS::EC2::Route",
      "DependsOn" : "AttachGateway",
      "Properties" : {
        "RouteTableId" : { "Ref" : "PublicRouteTable" },
        "DestinationCidrBlock" : "0.0.0.0/0",
        "GatewayId" : { "Ref" : "InternetGateway" }
      }
    },

    "Public1SubnetRouteTableAssociation" : {
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PublicSubnet1" },
        "RouteTableId" : { "Ref" : "PublicRouteTable" }
      }
    },

    "Public2SubnetRouteTableAssociation" : {
      "Type" : "AWS::EC2::SubnetRouteTableAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PublicSubnet2" },
        "RouteTableId" : { "Ref" : "PublicRouteTable" }
      }
    },

    "NetworkAcl" : {
      "Type" : "AWS::EC2::NetworkAcl",
      "Properties" : {
        "VpcId" : {"Ref" : "VPC"},
        "Tags" : [ {"Key" : "Application", "Value" : { "Ref" : "AWS::StackId"} } ]
      }
    },

    "InboundPublicNetworkAclEntry" : {
      "Type" : "AWS::EC2::NetworkAclEntry",
      "Properties" : {
        "CidrBlock" : "0.0.0.0/0",
        "Egress" : "false",
        "NetworkAclId" : { "Ref" : "NetworkAcl" },
        "Protocol" : "-1",
        "RuleAction" : "allow",
        "RuleNumber" : "100"
      }
    },

    "OutboundPublicNetworkAclEntry" : {
      "Type" : "AWS::EC2::NetworkAclEntry",
      "Properties" : {
        "CidrBlock" : "0.0.0.0/0",
        "Egress" : "true",
        "NetworkAclId" : { "Ref" : "NetworkAcl" },
        "Protocol" : "-1",
        "RuleAction" : "allow",
        "RuleNumber" : "100"
      }
    },

    "PublicSubnet1NetworkAclAssociation" : {
      "Type" : "AWS::EC2::SubnetNetworkAclAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PublicSubnet1" },
        "NetworkAclId" : { "Ref" : "NetworkAcl" }
      }
    },

    "PublicSubnet2NetworkAclAssociation" : {
      "Type" : "AWS::EC2::SubnetNetworkAclAssociation",
      "Properties" : {
        "SubnetId" : { "Ref" : "PublicSubnet2" },
        "NetworkAclId" : { "Ref" : "NetworkAcl" }
      }
    },

    "ApplicationLoadBalancer" : {
      "Type" : "AWS::ElasticLoadBalancingV2::LoadBalancer",
      "Properties" : {
        "SecurityGroups" : [{"Ref" : "ALBSecurityGroup"}],
        "Subnets" : [{"Ref": "PublicSubnet1"},{ "Ref" : "PublicSubnet2" }],
        "Tags" : [ {"Key" : "Name", "Value" : "Sample-ALB" } ]
      }
    },

    "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" : 10,
        "HealthCheckTimeoutSeconds" : 5,
        "HealthyThresholdCount" : 2,
        "Port" : 80,
        "Protocol" : "HTTP",
        "UnhealthyThresholdCount" : 5,
        "VpcId" : {"Ref" : "VPC"}
      }
    },

    "WebServerGroup" : {
      "Type" : "AWS::AutoScaling::AutoScalingGroup",
      "Properties" : {
        "VPCZoneIdentifier" : [{"Ref": "PublicSubnet1"},{ "Ref" : "PublicSubnet2" }],
        "LaunchConfigurationName" : { "Ref" : "LaunchConfig" },
        "MinSize" : "1",
        "MaxSize" : "3",
        "DesiredCapacity" : { "Ref" : "WebServerCapacity" },
        "TargetGroupARNs" : [ { "Ref" : "ALBTargetGroup" } ]
      },

      "UpdatePolicy": {
        "AutoScalingRollingUpdate": {
          "MinInstancesInService": "1",
          "MaxBatchSize": "1",
          "PauseTime" : "PT15M",
          "WaitOnResourceSignals": "true"
        }
      }
    },

    "DBSubnetGroup" : {
      "Type" : "AWS::RDS::DBSubnetGroup",
      "Properties" : {
        "DBSubnetGroupDescription" : "This is a privateSubnet",
        "SubnetIds": [{"Ref": "PrivateSubnet1"},{ "Ref" : "PrivateSubnet2" }]
      }
    },

    "LaunchConfig": {
      "Type" : "AWS::AutoScaling::LaunchConfiguration",
      "Properties": {
        "AssociatePublicIpAddress": true,
        "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },
          { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] },
        "InstanceType" : { "Ref" : "InstanceType" },
        "SecurityGroups" : [ {"Ref" : "WebServerSecurityGroup"} ],
        "KeyName" : { "Ref" : "KeyName" }
      }
    },

    "WebServerSecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "GroupDescription" : "Enable HTTP access via port 80 locked down to the ELB and SSH access",
        "SecurityGroupIngress" : [
          { "IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0"},
          {"IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : { "Ref" : "SSHLocation"}
          } ],
        "VpcId" : { "Ref" : "VPC" }
      }
    },

    "ALBSecurityGroup" : {
      "Type" : "AWS::EC2::SecurityGroup",
      "Properties" : {
        "GroupName": "http",
        "GroupDescription" : "Enable HTTP access via port 80",
        "SecurityGroupIngress" : [
          { "IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0"}],
        "VpcId" : { "Ref" : "VPC" }
      }
    },

    "DBEC2SecurityGroup": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties" : {
        "GroupDescription": "Open database for access",
        "SecurityGroupIngress" : [{
          "IpProtocol" : "tcp",
          "FromPort" : "3306",
          "ToPort" : "3306",
          "SourceSecurityGroupId" : { "Ref" : "WebServerSecurityGroup" }
        }],
        "VpcId" : { "Ref" : "VPC" }
      }
    },

    "MySQLDatabase": {
      "Type": "AWS::RDS::DBInstance",
      "Properties": {
        "Engine" : "MySQL",
        "DBName" : { "Ref": "DBName" },
        "MultiAZ" : { "Ref": "MultiAZDatabase" },
        "MasterUsername": { "Ref": "DBUser" },
        "MasterUserPassword": { "Ref" : "DBPassword" },
        "DBInstanceClass": { "Ref" : "DBInstanceClass" },
        "AllocatedStorage": { "Ref" : "DBAllocatedStorage" },
        "DBSubnetGroupName": { "Ref": "DBSubnetGroup" },
        "VPCSecurityGroups": [ { "Fn::GetAtt": [ "DBEC2SecurityGroup", "GroupId" ] } ]
      }
    }
  },

  "Outputs" : {
    "WebsiteURL" : {
      "Description" : "URL for newly created LAMP stack",
      "Value" : { "Fn::Join" : ["", ["http://", { "Fn::GetAtt" : [ "ApplicationLoadBalancer", "DNSName" ]}]] }
    }
  }
}

ローカルにあるテンプレート内容に問題がないか確認しておきます。

# テンプレートの検証
aws cloudformation validate-template --template-body file://`pwd`/VPC_Multi_RDS_AutoScaling_EC2_instance.json

問題なければスタックを作成します。 (パラメータ指定については、インスタンスタイプ、DB名、DBユーザー、DBパスワードなど省略してしまっていますー。)

# スタックの新規作成
aws cloudformation create-stack --stack-name sample-stack --template-body file://`pwd`/VPC_Multi_RDS_AutoScaling_EC2_instance.json \
--parameters ParameterKey=KeyName,ParameterValue=YOUR_KEY_NAME\
ParameterKey=SSHLocation,ParameterValue=xxx.xxx.xxx.xxx/32

できました。

f:id:moqrin3:20190126153137p:plain

アプリケーションを何も入れていないので、ALBの方ではunhealtyになっています。

f:id:moqrin3:20190126153158p:plain

なので、Wordpressでもインストールしてみますか。

2. スタックを変更セットを使って更新

変更セットを使用すると、スタックの変更案が実行中のリソースに与える可能性がある影響 (たとえば、変更によって重要なリソースが削除されたり置き換えられたりしないか) を確認できたり、変更セットの実行を確定したときに限り、スタックが変更されるため、変更案のまま続行するか別の変更セットを作成して他の変更を検討するかを決定できるようです。

まずは、既存のテンプレート内容を取得します。

# スタックを取得
aws cloudformation get-template --stack-name sample-stack > VPC_Multi_RDS_AutoScaling_EC2_instance_wp.json

元々のサンプルから"LaunchConfig"の部分をそのまま上書いて、諸々修正したのが下記テンプレートです。(冗長)

# スタックを取得して加筆修正
{
  "Description": "AWS CloudFormation VPC_RDS_ALB_AutoScaling_EC2Instances",
  "Parameters": {
    "MultiAZDatabase": {
      "Default": "false",
      "ConstraintDescription": "must be either true or false.",
      "Type": "String",
      "Description": "Create a Multi-AZ MySQL Amazon RDS database instance",
      "AllowedValues": [
        "true",
        "false"
      ]
    },
    "DBAllocatedStorage": {
      "Description": "The size of the database (Gb)",
      "Default": "5",
      "Type": "Number",
      "MaxValue": "1024",
      "MinValue": "5",
      "ConstraintDescription": "must be between 5 and 1024Gb."
    },
    "InstanceType": {
      "Default": "t2.micro",
      "ConstraintDescription": "must be a valid EC2 instance type.",
      "Type": "String",
      "Description": "WebServer EC2 instance type",
      "AllowedValues": [
        "t1.micro",
        "t2.nano",
        "t2.micro"
      ]
    },
    "SSHLocation": {
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x.",
      "Description": " The IP address range that can be used to SSH to the EC2 instances",
      "Default": "0.0.0.0/0",
      "MinLength": "9",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "MaxLength": "18",
      "Type": "String"
    },
    "KeyName": {
      "ConstraintDescription": "must be the name of an existing EC2 KeyPair.",
      "Type": "AWS::EC2::KeyPair::KeyName",
      "Description": "Name of an existing EC2 KeyPair to enable SSH access to the instances"
    },
    "DBPassword": {
      "ConstraintDescription": "must contain only alphanumeric characters.",
      "Description": "Password for MySQL database access",
      "Default": "samplePasswd",
      "MinLength": "8",
      "AllowedPattern": "[a-zA-Z0-9]*",
      "NoEcho": "true",
      "MaxLength": "41",
      "Type": "String"
    },
    "DBUser": {
      "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters.",
      "Description": "Username for MySQL database access",
      "Default": "root",
      "MinLength": "4",
      "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*",
      "NoEcho": "true",
      "MaxLength": "16",
      "Type": "String"
    },
    "DBInstanceClass": {
      "Default": "db.t2.micro",
      "ConstraintDescription": "must select a valid database instance type.",
      "Type": "String",
      "Description": "The database instance type",
      "AllowedValues": [
        "db.t1.micro",
        "db.t2.micro"
      ]
    },
    "DBName": {
      "ConstraintDescription": "must begin with a letter and contain only alphanumeric characters.",
      "Description": "MySQL database name",
      "Default": "myDatabase",
      "MinLength": "4",
      "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*",
      "MaxLength": "64",
      "Type": "String"
    },
    "WebServerCapacity": {
      "Description": "The initial number of WebServer instances",
      "Default": "1",
      "Type": "Number",
      "MaxValue": "3",
      "MinValue": "1",
      "ConstraintDescription": "must be between 1 and 3 EC2 instances."
    }
  },
  "AWSTemplateFormatVersion": "2010-09-09",
  "Outputs": {
    "WebsiteURL": {
      "Description": "URL for newly created LAMP stack",
      "Value": {
        "Fn::Join": [
          "",
          [
            "http://",
            {
              "Fn::GetAtt": [
                "ApplicationLoadBalancer",
                "DNSName"
              ]
            }
          ]
        ]
      }
    }
  },
  "Resources": {
    "NetworkAcl": {
      "Type": "AWS::EC2::NetworkAcl",
      "Properties": {
        "VpcId": {
          "Ref": "VPC"
        },
        "Tags": [
          {
            "Value": {
              "Ref": "AWS::StackId"
            },
            "Key": "Application"
          }
        ]
      }
    },
    "PublicRoute": {
      "Type": "AWS::EC2::Route",
      "Properties": {
        "GatewayId": {
          "Ref": "InternetGateway"
        },
        "DestinationCidrBlock": "0.0.0.0/0",
        "RouteTableId": {
          "Ref": "PublicRouteTable"
        }
      },
      "DependsOn": "AttachGateway"
    },
    "PublicSubnet1NetworkAclAssociation": {
      "Type": "AWS::EC2::SubnetNetworkAclAssociation",
      "Properties": {
        "SubnetId": {
          "Ref": "PublicSubnet1"
        },
        "NetworkAclId": {
          "Ref": "NetworkAcl"
        }
      }
    },
    "InternetGateway": {
      "Type": "AWS::EC2::InternetGateway",
      "Properties": {
        "Tags": [
          {
            "Value": {
              "Ref": "AWS::StackId"
            },
            "Key": "Application"
          }
        ]
      }
    },
    "Public1SubnetRouteTableAssociation": {
      "Type": "AWS::EC2::SubnetRouteTableAssociation",
      "Properties": {
        "SubnetId": {
          "Ref": "PublicSubnet1"
        },
        "RouteTableId": {
          "Ref": "PublicRouteTable"
        }
      }
    },
    "ALBListener": {
      "Type": "AWS::ElasticLoadBalancingV2::Listener",
      "Properties": {
        "Protocol": "HTTP",
        "DefaultActions": [
          {
            "TargetGroupArn": {
              "Ref": "ALBTargetGroup"
            },
            "Type": "forward"
          }
        ],
        "LoadBalancerArn": {
          "Ref": "ApplicationLoadBalancer"
        },
        "Port": "80"
      }
    },
    "ALBSecurityGroup": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "SecurityGroupIngress": [
          {
            "ToPort": "80",
            "IpProtocol": "tcp",
            "CidrIp": "0.0.0.0/0",
            "FromPort": "80"
          }
        ],
        "GroupName": "http",
        "VpcId": {
          "Ref": "VPC"
        },
        "GroupDescription": "Enable HTTP access via port 80"
      }
    },
    "LaunchConfig": {
      "Type" : "AWS::AutoScaling::LaunchConfiguration",
      "Metadata" : {
        "AWS::CloudFormation::Init" : {
          "configSets" : {
            "wordpress_install" : ["install_cfn", "install_wordpress" ]
          },
          "install_cfn" : {
            "files": {
              "/etc/cfn/cfn-hup.conf": {
                "content": { "Fn::Join": [ "", [
                  "[main]\n",
                  "stack=", { "Ref": "AWS::StackId" }, "\n",
                  "region=", { "Ref": "AWS::Region" }, "\n"
                ]]},
                "mode"  : "000400",
                "owner" : "root",
                "group" : "root"
              },
              "/etc/cfn/hooks.d/cfn-auto-reloader.conf": {
                "content": { "Fn::Join": [ "", [
                  "[cfn-auto-reloader-hook]\n",
                  "triggers=post.update\n",
                  "path=Resources.LaunchConfig.Metadata.AWS::CloudFormation::Init\n",
                  "action=/opt/aws/bin/cfn-init -v ",
                  "         --stack ", { "Ref" : "AWS::StackName" },
                  "         --resource LaunchConfig ",
                  "         --configsets wordpress_install ",
                  "         --region ", { "Ref" : "AWS::Region" }, "\n"
                ]]},
                "mode"  : "000400",
                "owner" : "root",
                "group" : "root"
              }
            },
            "services" : {
              "sysvinit" : {
                "cfn-hup" : { "enabled" : "true", "ensureRunning" : "true",
                  "files" : ["/etc/cfn/cfn-hup.conf", "/etc/cfn/hooks.d/cfn-auto-reloader.conf"]}
              }
            }
          },

          "install_wordpress" : {
            "packages" : {
              "yum" : {
                "php"       : [],
                "php-mysql" : [],
                "mysql"     : [],
                "httpd"     : []
              }
            },
            "sources" : {
              "/var/www/html" : "http://wordpress.org/latest.tar.gz"
            },
            "files" : {
              "/tmp/create-wp-config" : {
                "content" : { "Fn::Join" : [ "", [
                  "#!/bin/bash\n",
                  "cp /var/www/html/wordpress/wp-config-sample.php /var/www/html/wordpress/wp-config.php\n",
                  "sed -i \"s/'database_name_here'/'",{ "Ref" : "DBName" }, "'/g\" wp-config.php\n",
                  "sed -i \"s/'username_here'/'",{ "Ref" : "DBUser" }, "'/g\" wp-config.php\n",
                  "sed -i \"s/'password_here'/'",{ "Ref" : "DBPassword" }, "'/g\" wp-config.php\n",
                  "sed -i \"s/'localhost'/'",{ "Fn::GetAtt" : [ "MySQLDatabase", "Endpoint.Address" ] }, "'/g\" wp-config.php\n"
                ]]},
                "mode" : "000500",
                "owner" : "root",
                "group" : "root"
              }
            },
            "commands" : {
              "01_configure_wordpress" : {
                "command" : "/tmp/create-wp-config",
                "cwd" : "/var/www/html/wordpress"
              }
            },
            "services" : {
              "sysvinit" : {
                "httpd" : { "enabled" : "true", "ensureRunning" : "true" }
              }
            }
          }
        }
      },
      "Properties": {
        "AssociatePublicIpAddress" : true,
        "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },
          { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] },
        "InstanceType"   : { "Ref" : "InstanceType" },
        "SecurityGroups" : [ {"Ref" : "WebServerSecurityGroup"} ],
        "KeyName"        : { "Ref" : "KeyName" },
        "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [
          "#!/bin/bash -xe\n",
          "yum update -y aws-cfn-bootstrap\n",

          "/opt/aws/bin/cfn-init -v ",
          "         --stack ", { "Ref" : "AWS::StackName" },
          "         --resource LaunchConfig ",
          "         --configsets wordpress_install ",
          "         --region ", { "Ref" : "AWS::Region" }, "\n",

          "/opt/aws/bin/cfn-signal -e $? ",
          "         --stack ", { "Ref" : "AWS::StackName" },
          "         --resource WebServerGroup ",
          "         --region ", { "Ref" : "AWS::Region" }, "\n"
        ]]}}
      }
    },
    "DBSubnetGroup": {
      "Type": "AWS::RDS::DBSubnetGroup",
      "Properties": {
        "SubnetIds": [
          {
            "Ref": "PrivateSubnet1"
          },
          {
            "Ref": "PrivateSubnet2"
          }
        ],
        "DBSubnetGroupDescription": "This is a privateSubnet"
      }
    },
    "VPC": {
      "Type": "AWS::EC2::VPC",
      "Properties": {
        "CidrBlock": "10.0.0.0/16",
        "Tags": [
          {
            "Value": "Sample-VPC",
            "Key": "Name"
          },
          {
            "Value": {
              "Ref": "AWS::StackId"
            },
            "Key": "Application"
          }
        ]
      }
    },
    "AttachGateway": {
      "Type": "AWS::EC2::VPCGatewayAttachment",
      "Properties": {
        "VpcId": {
          "Ref": "VPC"
        },
        "InternetGatewayId": {
          "Ref": "InternetGateway"
        }
      }
    },
    "MySQLDatabase": {
      "Type": "AWS::RDS::DBInstance",
      "Properties": {
        "Engine": "MySQL",
        "MultiAZ": {
          "Ref": "MultiAZDatabase"
        },
        "MasterUsername": {
          "Ref": "DBUser"
        },
        "MasterUserPassword": {
          "Ref": "DBPassword"
        },
        "VPCSecurityGroups": [
          {
            "Fn::GetAtt": [
              "DBEC2SecurityGroup",
              "GroupId"
            ]
          }
        ],
        "AllocatedStorage": {
          "Ref": "DBAllocatedStorage"
        },
        "DBInstanceClass": {
          "Ref": "DBInstanceClass"
        },
        "DBSubnetGroupName": {
          "Ref": "DBSubnetGroup"
        },
        "DBName": {
          "Ref": "DBName"
        }
      }
    },
    "PublicRouteTable": {
      "Type": "AWS::EC2::RouteTable",
      "Properties": {
        "VpcId": {
          "Ref": "VPC"
        },
        "Tags": [
          {
            "Value": {
              "Ref": "AWS::StackId"
            },
            "Key": "Application"
          }
        ]
      }
    },
    "PrivateSubnet2": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "Tags": [
          {
            "Value": "PrivateSubnet1",
            "Key": "Name"
          },
          {
            "Value": {
              "Ref": "AWS::StackId"
            },
            "Key": "Application"
          }
        ],
        "VpcId": {
          "Ref": "VPC"
        },
        "CidrBlock": "10.0.3.0/24",
        "AvailabilityZone": {
          "Fn::Select": [
            "1",
            {
              "Fn::GetAZs": {
                "Ref": "AWS::Region"
              }
            }
          ]
        }
      }
    },
    "PrivateSubnet1": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "Tags": [
          {
            "Value": "PrivateSubnet1",
            "Key": "Name"
          },
          {
            "Value": {
              "Ref": "AWS::StackId"
            },
            "Key": "Application"
          }
        ],
        "VpcId": {
          "Ref": "VPC"
        },
        "CidrBlock": "10.0.2.0/24",
        "AvailabilityZone": {
          "Fn::Select": [
            "0",
            {
              "Fn::GetAZs": {
                "Ref": "AWS::Region"
              }
            }
          ]
        }
      }
    },
    "PublicSubnet2NetworkAclAssociation": {
      "Type": "AWS::EC2::SubnetNetworkAclAssociation",
      "Properties": {
        "SubnetId": {
          "Ref": "PublicSubnet2"
        },
        "NetworkAclId": {
          "Ref": "NetworkAcl"
        }
      }
    },
    "InboundPublicNetworkAclEntry": {
      "Type": "AWS::EC2::NetworkAclEntry",
      "Properties": {
        "NetworkAclId": {
          "Ref": "NetworkAcl"
        },
        "RuleNumber": "100",
        "Protocol": "-1",
        "Egress": "false",
        "RuleAction": "allow",
        "CidrBlock": "0.0.0.0/0"
      }
    },
    "WebServerSecurityGroup": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "SecurityGroupIngress": [
          {
            "ToPort": "80",
            "IpProtocol": "tcp",
            "CidrIp": "0.0.0.0/0",
            "FromPort": "80"
          },
          {
            "ToPort": "22",
            "IpProtocol": "tcp",
            "CidrIp": {
              "Ref": "SSHLocation"
            },
            "FromPort": "22"
          }
        ],
        "VpcId": {
          "Ref": "VPC"
        },
        "GroupDescription": "Enable HTTP access via port 80 locked down to the ELB and SSH access"
      }
    },
    "WebServerGroup": {
      "Type": "AWS::AutoScaling::AutoScalingGroup",
      "Properties": {
        "DesiredCapacity": {
          "Ref": "WebServerCapacity"
        },
        "MinSize": "1",
        "MaxSize": "3",
        "VPCZoneIdentifier": [
          {
            "Ref": "PublicSubnet1"
          },
          {
            "Ref": "PublicSubnet2"
          }
        ],
        "LaunchConfigurationName": {
          "Ref": "LaunchConfig"
        },
        "TargetGroupARNs": [
          {
            "Ref": "ALBTargetGroup"
          }
        ]
      },
      "UpdatePolicy": {
        "AutoScalingRollingUpdate": {
          "PauseTime": "PT15M",
          "WaitOnResourceSignals": "true",
          "MaxBatchSize": "1",
          "MinInstancesInService": "1"
        }
      }
    },
    "ApplicationLoadBalancer": {
      "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer",
      "Properties": {
        "Subnets": [
          {
            "Ref": "PublicSubnet1"
          },
          {
            "Ref": "PublicSubnet2"
          }
        ],
        "SecurityGroups": [
          {
            "Ref": "ALBSecurityGroup"
          }
        ],
        "Tags": [
          {
            "Value": "Sample-ALB",
            "Key": "Name"
          }
        ]
      }
    },
    "PublicSubnet1": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "Tags": [
          {
            "Value": "PublicSubnet1",
            "Key": "Name"
          },
          {
            "Value": {
              "Ref": "AWS::StackId"
            },
            "Key": "Application"
          }
        ],
        "VpcId": {
          "Ref": "VPC"
        },
        "CidrBlock": "10.0.0.0/24",
        "AvailabilityZone": {
          "Fn::Select": [
            "0",
            {
              "Fn::GetAZs": {
                "Ref": "AWS::Region"
              }
            }
          ]
        }
      }
    },
    "DBEC2SecurityGroup": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "SecurityGroupIngress": [
          {
            "ToPort": "3306",
            "IpProtocol": "tcp",
            "SourceSecurityGroupId": {
              "Ref": "WebServerSecurityGroup"
            },
            "FromPort": "3306"
          }
        ],
        "VpcId": {
          "Ref": "VPC"
        },
        "GroupDescription": "Open database for access"
      }
    },
    "PublicSubnet2": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "Tags": [
          {
            "Value": "PublicSubnet2",
            "Key": "Name"
          },
          {
            "Value": {
              "Ref": "AWS::StackId"
            },
            "Key": "Application"
          }
        ],
        "VpcId": {
          "Ref": "VPC"
        },
        "CidrBlock": "10.0.1.0/24",
        "AvailabilityZone": {
          "Fn::Select": [
            "1",
            {
              "Fn::GetAZs": {
                "Ref": "AWS::Region"
              }
            }
          ]
        }
      }
    },
    "Public2SubnetRouteTableAssociation": {
      "Type": "AWS::EC2::SubnetRouteTableAssociation",
      "Properties": {
        "SubnetId": {
          "Ref": "PublicSubnet2"
        },
        "RouteTableId": {
          "Ref": "PublicRouteTable"
        }
      }
    },
    "OutboundPublicNetworkAclEntry": {
      "Type": "AWS::EC2::NetworkAclEntry",
      "Properties": {
        "NetworkAclId": {
          "Ref": "NetworkAcl"
        },
        "RuleNumber": "100",
        "Protocol": "-1",
        "Egress": "true",
        "RuleAction": "allow",
        "CidrBlock": "0.0.0.0/0"
      }
    },
    "ALBTargetGroup": {
      "Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
      "Properties": {
        "HealthCheckIntervalSeconds": 10,
        "VpcId": {
          "Ref": "VPC"
        },
        "Protocol": "HTTP",
        "UnhealthyThresholdCount": 5,
        "HealthyThresholdCount": 2,
        "HealthCheckTimeoutSeconds": 5,
        "Port": 80
      }
    }
  },
  "Mappings": {
    "AWSInstanceType2Arch": {
      "t2.micro": {
        "Arch": "HVM64"
      },
      "t2.nano": {
        "Arch": "HVM64"
      },
      "t1.micro": {
        "Arch": "HVM64"
      }
    },
    "AWSRegionArch2AMI": {
      "us-west-2": {
        "HVM64": "ami-a0cfeed8",
        "HVMG2": "ami-0e09505bc235aa82d"
      },
      "us-east-1": {
        "HVM64": "ami-0ff8a91507f77f867",
        "HVMG2": "ami-0a584ac55a7631c0c"
      },
      "ap-northeast-1": {
        "HVM64": "ami-00a5245b4816c38e6",
        "HVMG2": "ami-053cdd503598e4a9d"
      },
      "us-west-1": {
        "HVM64": "ami-0bdb828fd58c52235",
        "HVMG2": "ami-066ee5fd4a9ef77f1"
      }
    }
  }
}

確認してみてOKだったら、変更セットを作成します。

# 変更セットの作成
aws cloudformation create-change-set --stack-name sample-stack \
--change-set-name sample-change-set --template-body file://`pwd`/VPC_Multi_RDS_AutoScaling_EC2_instance_wp.json \
--parameters ParameterKey=KeyName,ParameterValue=YOUR_KEY_NAME \
ParameterKey=SSHLocation,ParameterValue=xxx.xxx.xxx.xxx/32

変更セットを表示します。

# 変更セットの表示
aws cloudformation list-change-sets --stack-name sample-stack

表示した変更セット ID を指定して変更セットに関する情報を取得します。

(ARNじゃなきゃあかんです。下記のコマンドはsampleのarnです。)

# 変更セットの表示
aws cloudformation describe-change-set --change-set-name arn:aws:cloudformation:ap-northeast-1:123456789012:stack/sample-change-set/1a2345b6-0000-00a0-a123-00abc0abc000"

変更セットを実行します。 変更セットを実行すると、スタックに関連付けられた変更セットは AWS CloudFormation によってすべて削除されます。

# 変更セットの実行
aws cloudformation execute-change-set --change-set-name arn:aws:cloudformation:ap-northeast-1:123456789012:stack/sample-change-set/1a2345b6-0000-00a0-a123-00abc0abc000"

進行状況を確認します。

# 進行状況を確認
aws cloudformation describe-stacks --stack-name sample-stack

完了したら、変更セットを確認

# 変更セットを確認
aws cloudformation list-change-sets --stack-name sample-stack

f:id:moqrin3:20190126153345p:plain

更新が完了しているので、出力画面からALBのリンクに飛びます。

f:id:moqrin3:20190126153319p:plain

あ、あれ!? Apacheのトップページですけど、、、WP的な画面じゃないですね。。 何で?? (ちなみに、元々のサンプルをそのまま実行してみても何かうまくいきませんでした、、、主題ではないので、また後日、再度詳細の確認するようにしますー。。)

ということで、スタックを削除します。

# 変更セットを確認
aws cloudformation delete-stack --stack-name sample-stack

3. 宿題

  • CloudFormation ヘルパースクリプトの確認

  • テンプレートのネスト化

  • 何故かBlackBeltの資料には触られていないCloudFormerも利用してみたいですね。

所感

今回も実際に手を動かしてみて、色々と当惑しつつ、お勉強になりました。

  • NetworkACLをインバウンド&アウトバウンド含めて明示的に設定してあげないとダメとか。

  • DB用のSubnetを作成してあげないとダメとか。

  • 指定したVPC内にDB作成するには、DBSubnetGroupNameが必要とか。

参考:

AWS CloudFormation とは

AWS Black Belt / AWS CloudFormation

https://stackoverflow.com/questions/37038489/how-to-add-a-rds-instance-to-a-vpc-using-aws-cloudformationDBSubnetGroupDescription