ruk·si

☁️ AWS CF
Single AZ ASG

Updated at 2016-02-24 10:35

This is a CloudFormation template that creates a VPC, a public subnet, a single availability zone ELB, an ASG and boot instances in there.

Save JSON blob at the end of this page to example-web-server-stack.json and create a new CF stack with it.

CloudFormation stack updated are usually non-destructive:

  • Updating desired instance count to 2 using the template:
    • Starts a new instance, does not touch the old ones.
  • Changing the allowed SSH source:
    • Doesn't reboot instances, only modifies the security group.

Here are a couple of scenarios I tried out:

New instance is created:

00:16 new server starts to boot
00:17 ASG = InService, Healthy
      ELB = OutOfService
00:19 ELB = InService, getting traffic

Server becomes unhealthy:

00:07 sent `sudo service httpd stop` to i-5f0f4385
      ELB = InService
      ASG = InService, Healthy
00:15 ELB = OutOfService, no more traffic
00:16 ASG = InService, Unhealthy
00:16 ASG = Terminating, Unhealthy

Server becomes unhealthy before the first health check:

23:54 sent `sudo service httpd stop` to i-fe0a4624 after boot, before
      any health checks
      Because ASG grace period is 5min, considers it `Healthy` and `InService`
      Because ELB starts checking right after bootup for routing,
      says that it's `OutOfService` so doesn't direct any traffic to it.
00:02 it got marked as Unhealthy and ASG starts replacing it.
00:03 new server starts to boot
      ELB = OutOfService
      ASG = InService, Healthy
00:06 ELB = InService, getting traffic

AWS CloudFront has this cfn toolbox of helper scripts:

  • aws-cfn-bootstrap is a yum package that contains helper scripts for CloudFormation ecosystem and is automatically installed on Amazon Linux machines.
  • /opt/aws/bin/cfn-init: Initialization helper script that..
    • Fetches and parses metadata from CloudFormation, from "Resource/WebServerLC/Metadata" in this example.
    • Installs packages.
    • Writes files to disk like initial configuration.
    • Start and stops services.
  • /opt/aws/bin/cfn-signal: Helper script for manual communication of instance state to CloudFromation.
    • Usually used with CreationPolicy and UpdatePolicy in auto scaling groups so CloudFormation knows how many signals to wait for.
  • /opt/aws/bin/cfn-get-metadata: Prints specified metadata to STDOUT, not used in this example.
  • /opt/aws/bin/cfn-hup: Background service that checks for CloudFormation updates and triggers custom hooks when detected like /etc/cfn/hooks.d/cfn-auto-reloader.conf in this example.
{

  "AWSTemplateFormatVersion":"2010-09-09",
  "Description":"VPC + Subnet + EBL + ASG with Web Servers",

  "Parameters":{
    "WebServerKey":{
      "Description":"EC2 SSH key pair name for web server instances.",
      "Type":"AWS::EC2::KeyPair::KeyName"
    },
    "WebServerAllowedSSHSource":{
      "Description":"Source CIDR where web server SSH key access is allowed.",
      "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 CIDR range of the form x.x.x.x/x."
    },
    "WebServerCount":{
      "Description":"Number of desired EC2 instances after launch / update.",
      "Type":"Number",
      "Default":"1"
    },
    "WebServerInstanceType":{
      "Description":"Web server instance type.",
      "Type":"String",
      "Default":"t2.small",
      "AllowedValues":[
        "t1.micro", "t2.nano", "t2.micro", "t2.small", "t2.medium", "t2.large"
      ]
    }
  },

  "Mappings":{
    "RegionToBitsToAMIMap":{
      "ap-northeast-1":{ "64":"ami-03cf3903" },
      "ap-southeast-1":{ "64":"ami-68d8e93a" },
      "ap-southeast-2":{ "64":"ami-fd9cecc7" },
      "eu-central-1":{ "64":"ami-a8221fb5" },
      "eu-west-1":{ "64":"ami-a10897d6" },
      "sa-east-1":{ "64":"ami-b52890a8" },
      "us-east-1":{ "64":"ami-1ecae776" },
      "us-west-1":{ "64":"ami-d114f295" },
      "us-west-2":{ "64":"ami-e7527ed7" }
    }
  },

  "Resources":{
    "MainVPC":{
      "Type":"AWS::EC2::VPC",
      "Properties":{
        "CidrBlock":"10.0.0.0/16",
        "EnableDnsSupport":"true",
        "EnableDnsHostnames":"true"
      }
    },
    "MainSubnet":{
      "Type":"AWS::EC2::Subnet",
      "Properties":{
        "VpcId":{ "Ref":"MainVPC" },
        "CidrBlock":"10.0.0.0/24"
      }
    },
    "MainInternetGateway":{
      "Type":"AWS::EC2::InternetGateway"
    },
    "MainGatewayAttachment":{
      "Type":"AWS::EC2::VPCGatewayAttachment",
      "Properties":{
        "VpcId":{ "Ref":"MainVPC" },
        "InternetGatewayId":{ "Ref":"MainInternetGateway" }
      }
    },
    "MainRouteTable":{
      "Type":"AWS::EC2::RouteTable",
      "Properties":{
        "VpcId":{ "Ref":"MainVPC" }
      }
    },
    "MainRoute":{
      "Type":"AWS::EC2::Route",
      "DependsOn":"MainGatewayAttachment",
      "Properties":{
        "RouteTableId":{ "Ref":"MainRouteTable" },
        "DestinationCidrBlock":"0.0.0.0/0",
        "GatewayId":{ "Ref":"MainInternetGateway" }
      }
    },
    "MainRouteTableAssociation":{
      "Type":"AWS::EC2::SubnetRouteTableAssociation",
      "Properties":{
        "SubnetId":{ "Ref":"MainSubnet" },
        "RouteTableId":{ "Ref":"MainRouteTable" }
      }
    },
    "MainELBSG":{
      "Type":"AWS::EC2::SecurityGroup",
      "Properties":{
        "GroupDescription":"ELB security for Internet access.",
        "VpcId":{ "Ref":"MainVPC" },
        "SecurityGroupIngress":[{
          "IpProtocol":"tcp",
          "FromPort":"80",
          "ToPort":"80",
          "CidrIp":"0.0.0.0/0"
        }],
        "SecurityGroupEgress":[{
          "IpProtocol":"tcp",
          "FromPort":"80",
          "ToPort":"80",
          "CidrIp":"0.0.0.0/0"
        }]
      }
    },
    "MainELB":{
      "Type":"AWS::ElasticLoadBalancing::LoadBalancer",
      "Properties":{
        "CrossZone":"true",
        "SecurityGroups":[{ "Ref":"MainELBSG" }],
        "Subnets":[{ "Ref":"MainSubnet" }],
        "Listeners":[{
          "LoadBalancerPort":"80",
          "InstancePort":"80",
          "Protocol":"HTTP"
        }],
        "HealthCheck":{
          "Target":"HTTP:80/",
          "HealthyThreshold":"3",
          "UnhealthyThreshold":"5",
          "Interval":"90",
          "Timeout":"60"
        }
      }
    },
    "WebServerASG":{
      "Type":"AWS::AutoScaling::AutoScalingGroup",
      "DependsOn":"MainRoute",
      "Properties":{
        "AvailabilityZones":[
          {"Fn::GetAtt":["MainSubnet", "AvailabilityZone"]}
        ],
        "VPCZoneIdentifier":[{ "Ref":"MainSubnet" }],
        "LaunchConfigurationName":{ "Ref":"WebServerLC" },
        "MinSize":"1",
        "MaxSize":"10",
        "DesiredCapacity":{ "Ref":"WebServerCount" },
        "LoadBalancerNames":[{ "Ref":"MainELB" }],
        "HealthCheckType": "ELB",
        "HealthCheckGracePeriod": 300
      },
      "CreationPolicy":{
        "ResourceSignal":{
          "Timeout":"PT45M",
          "Count":{ "Ref":"WebServerCount" }
        }
      },
      "UpdatePolicy":{
        "AutoScalingRollingUpdate":{
          "MinInstancesInService":"1",
          "MaxBatchSize":"1",
          "PauseTime":"PT15M",
          "WaitOnResourceSignals":"true"
        }
      }
    },
    "WebServerSG":{
      "Type":"AWS::EC2::SecurityGroup",
      "Properties":{
        "GroupDescription":"Allow access from balancer to web servers",
        "VpcId":{ "Ref":"MainVPC" },
        "SecurityGroupIngress":[
          {
            "IpProtocol":"tcp",
            "FromPort":"80",
            "ToPort":"80",
            "SourceSecurityGroupId":{ "Ref":"MainELBSG" }
          },
          {
            "IpProtocol":"tcp",
            "FromPort":"22",
            "ToPort":"22",
            "CidrIp":{ "Ref" : "WebServerAllowedSSHSource" }
          }
        ]
      }
    },
    "WebServerLC":{
      "Type":"AWS::AutoScaling::LaunchConfiguration",
      "Metadata":{
        "AWS::CloudFormation::Init":{
          "config":{
            "packages":{ "yum":{ "httpd":[] } },
            "files":{
              "/var/www/html/index.html":{
                "content":"<h1>Hello World!</h1>",
                "mode":"000644",
                "owner":"root",
                "group":"root"
              },
              "/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.WebServerLC.Metadata.AWS::CloudFormation::Init\n",
                      "action=/opt/aws/bin/cfn-init -v ",
                      "         --resource WebServerLC ",
                      "         --stack ", { "Ref":"AWS::StackName" },
                      "         --region ", { "Ref":"AWS::Region" },
                      "\n",
                      "runas=root\n"
                    ]
                  ]
                }
              }
            },
            "services":{
              "sysvinit":{
                "httpd":{
                  "enabled":"true",
                  "ensureRunning":"true",
                  "files":[
                    "/etc/httpd/conf.d/aptobackend.conf",
                    "/var/www/html/index.html"
                  ]
                },
                "cfn-hup":{
                  "enabled":"true",
                  "ensureRunning":"true",
                  "files":[
                    "/etc/cfn/cfn-hup.conf",
                    "/etc/cfn/hooks.d/cfn-auto-reloader.conf"
                  ]
                }
              }
            }
          }
        }
      },
      "Properties":{
        "ImageId":{
          "Fn::FindInMap":[
            "RegionToBitsToAMIMap", { "Ref":"AWS::Region" }, "64"
          ]
        },
        "SecurityGroups":[{ "Ref":"WebServerSG" }],
        "InstanceType":{ "Ref":"WebServerInstanceType" },
        "KeyName":{ "Ref":"WebServerKey" },
        "AssociatePublicIpAddress":"true",
        "UserData":{
          "Fn::Base64":{
            "Fn::Join":[
              "",
              [
                "#!/bin/bash -xe\n",
                "yum update -y aws-cfn-bootstrap\n",
                "# Install the sample application\n",
                "/opt/aws/bin/cfn-init -v ",
                "    --resource WebServerLC ",
                "    --stack ", { "Ref":"AWS::StackId" },
                "    --region ", { "Ref":"AWS::Region" },
                "\n",
                "# Signal copletion\n",
                "/opt/aws/bin/cfn-signal -e $? ",
                "    --resource WebServerASG ",
                "    --stack ", { "Ref":"AWS::StackId" },
                "    --region ", { "Ref":"AWS::Region" },
                "\n"
              ]
            ]
          }
        }
      }
    }
  },

  "Outputs":{
    "WebSite":{
      "Description":"URL of the website",
      "Value":{ "Fn::Join":[ "", [
          "http://", { "Fn::GetAtt":[ "MainELB", "DNSName" ] }
      ]]}
    }
  }

}

Sources