Adding an Auto Scaling capability

We are now going to add the Auto Scaling capability to our application. This will be done in three major phases:

  1. Create a launch configuration that the Auto Scaling group uses to launch the EC2 instances.
  2. Create the actual Auto Scaling group.
  3. Create the scaling policies that will enable us to have a dynamic scaling capability by relying on CloudWatch metrics.

At the top of the script, in the import section, add the following:

from troposphere.autoscaling import ( 
    AutoScalingGroup,
    LaunchConfiguration,
    ScalingPolicy, 
) 

We will be able to reuse all the resources previously created when launching an EC2 instance. Most of them will stay untouched except for the security group. In the Auto Scaling group, we need to specify which VPC ID to use. Find your security group and add the following to reference the VPC ID that we already configured for the load balancer:

t.add_resource(ec2.SecurityGroup( 
    "SecurityGroup", 
    GroupDescription="Allow SSH and TCP/{} access".format(ApplicationPort), 
    SecurityGroupIngress=[...], 
    VpcId=Ref("VpcId"), 
)) 

We are going to make our CloudFormation template very flexible when it comes to creating the infrastructure to host our application. As mentioned before, we can scale vertically and horizontally, which, in other words, means what type of instance and how many of them. We can easily implement those two scaling strategies thanks to the parameter section of CloudFormation. We will first create a parameter to define how many instances to use at launch time. After the PublicSubnet parameter, add the following:

t.add_parameter(Parameter( 
    "ScaleCapacity", 
    Default="3", 
    Type="String", 
    Description="Number servers to run", 
)) 

Below this parameter, we will add another one to allow for the selection of the instances using a drop-down menu as follows:

t.add_parameter(Parameter( 
    'InstanceType', 
    Type='String', 
    Description='WebServer EC2 instance type', 
    Default='t2.micro', 
    AllowedValues=[ 
        't2.micro', 
        't2.small', 
        't2.medium', 
        't2.large', 
    ], 
    ConstraintDescription='must be a valid EC2 T2 instance type.', 
)) 

We are illustrating the vertical component with only the t2 class, but you can, of course, allow anything.

The next component of an Auto Scaling group is the launch configuration. Every time the Auto Scaling group decides to add an instance, it will refer to that configuration to know what type of instance to launch, which AMI to use, and all the configurations associated with it. This is essentially what replaces the ec2.Instance() call we removed at the top of this section and therefore it's no surprise that most parameters are parameters we defined in Chapter 4, Adding Continuous Integration and Continuous Deployment, when we were creating our initial EC2 instance. At the bottom of the file, just above the output parameter, add the following:

t.add_resource(LaunchConfiguration( 
    "LaunchConfiguration", 
    UserData=ud, 
    ImageId="ami-a4c7edb2", 
    KeyName=Ref("KeyPair"),
    SecurityGroups=[Ref("SecurityGroup")], 
    InstanceType=Ref("InstanceType"), 
    IamInstanceProfile=Ref("InstanceProfile"), 
)) 

Finally, the last block of code needed is the actual Auto Scaling group resource. We will create it like most other resources and start by referring to our resource and providing it with a name. Below the LaunchConfiguration, start creating your AutosScalingGroup as follows:

t.add_resource(AutoScalingGroup( 
    "AutoscalingGroup", 

The next thing we will do is refer to the capacity parameter and launch the configuration resource that we just created:

    DesiredCapacity=Ref("ScaleCapacity"), 
    LaunchConfigurationName=Ref("LaunchConfiguration"), 

Auto Scaling groups take min. and max. arguments to limit the number of EC2 instances a given Auto Scaling group can manage; we are going to set our limits to 2 and 5 as follows:

    MinSize=2, 
    MaxSize=5, 

Auto Scaling groups have the ability to be connected to ELBs. Through that connection, ELB and Auto Scaling groups work hand in hand. Every time the Auto Scaling group makes a decision to add or remove an instance, it will also add or remove it from the load balancer. This is done very simply by providing our load balancer reference as follows:

    LoadBalancerNames=[Ref("LoadBalancer")], 

Finally, we will specify which subnets to use and close the opened parentheses:

    VPCZoneIdentifier=Ref("PublicSubnet"), 
)) 

Our Auto Scaling group is ready. We could launch our stack and benefit from our improved architecture with no single point of failure and the ability to scale up and down the number of instances with just a few clicks, but we can do even better. Our application is so basic that we can easily guess that the limiting factor is the CPU. By looking at how the CPU on our instance trends, we can automatize this process of scaling up or down the number of instances running our services. This is done by combining scaling policies and CloudWatch alarms. Here is how.

We will first add the necessary import at the top:

from troposphere.cloudwatch import ( 
    Alarm, 
    MetricDimension, 
) 

Now, at the bottom of the script, after the creation of the Auto Scaling group, we will create two scaling policies, one to scale down the number of instances and one to scale it up:

t.add_resource(ScalingPolicy( 
    "ScaleDownPolicy", 
    ScalingAdjustment="-1", 
    AutoScalingGroupName=Ref("AutoscalingGroup"), 
    AdjustmentType="ChangeInCapacity", 
)) 
 
t.add_resource(ScalingPolicy( 
    "ScaleUpPolicy", 
    ScalingAdjustment="1", 
    AutoScalingGroupName=Ref("AutoscalingGroup"), 
    AdjustmentType="ChangeInCapacity", 
)) 

As you can see, the policies are tied to the Auto Scaling group through the AutoScalingGroupName parameter. We will now attach CloudWatch alarms to those policies, starting with the alarm to trigger a scale-down action.

Under the scaling policies, add the following:

t.add_resource(Alarm( 
    "CPUTooLow", 
    AlarmDescription="Alarm if CPU too low", 

We will learn more about CloudWatch in Chapter 7, Monitoring and Alerting, but at a high level, CloudWatch breaks down metrics by service and metric type. We want to extract the metric called CPUUtilization for EC2. Here is how:

    Namespace="AWS/EC2", 
    MetricName="CPUUtilization", 

CloudWatch has a concept of key value pairs that can be associated with an alarm. It is used to expand further on the identity of a metric. In our case, we want the scaling decision to be made only on the instances present in our Auto Scaling group. We will specify it as follows:

    Dimensions=[ 
        MetricDimension( 
            Name="AutoScalingGroupName", 
            Value=Ref("AutoscalingGroup") 
        ), 
    ], 

We will now tell, that we want to look at the average value of the CPU utilization over a period of 1 minute and take the average value:

    Statistic="Average", 
    Period="60", 
    EvaluationPeriods="1", 

We will set the threshold to 30, meaning that if the average usage of our CPU is more than 30 for a minute, we will scale down our cluster:

    Threshold="30", 
    ComparisonOperator="LessThanThreshold", 

And finally, we will tie that CloudWatch alarm to the scaling policy previously created through the AlarmActions parameter and close the parentheses as follows:

    AlarmActions=[Ref("ScaleDownPolicy")], 
)) 

We will do approximately the same work for scaling up our cluster. We will change the threshold to be higher than 60% for 1 minute and, in addition, also trigger the same event if we don't have any data, which could indicate that an instance is down. We can do that through the InsufficientDataActions parameter as follows:

t.add_resource(Alarm( 
    "CPUTooHigh", 
    AlarmDescription="Alarm if CPU too high", 
    Namespace="AWS/EC2", 
    MetricName="CPUUtilization", 
    Dimensions=[ 
        MetricDimension( 
            Name="AutoScalingGroupName", 
            Value=Ref("AutoscalingGroup") 
        ), 
    ], 
    Statistic="Average", 
    Period="60", 
    EvaluationPeriods="1", 
    Threshold="60", 
    ComparisonOperator="GreaterThanThreshold", 
    AlarmActions=[Ref("ScaleUpPolicy"), ], 
    InsufficientDataActions=[Ref("ScaleUpPolicy")], 
)) 

Our newly updated template is ready to be tested. If everything went well, it should look like this:

http://bit.ly/2vahX7e

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset