Auto Scaling in Amazon Web Services: EC2 + CloudWatch

by Andrii Kozhokaru

Hello all!

Auto scaling configuration based on Amazon EC2 is a particularly cunning task I once solved and have been using the solution ever since. No doubt, there can be a pack of options, but I will only cover the simplest one: horizontal scaling of a single server under a single balancer.

We are going to setup CloudWatch as scaling initiator, while using our own custom Load Average metrics.

So then, let us begin. Being a true Linux guy, I advise and recommend using Command Line Tools (CLT) to configure auto scaling for the following reasons:

  • You can do virtually anything using CLT;
  • Copy-and-pasting to the console is much handier than doing the mouse clicking.

The first thing to do is set up our terminal by downloading and unpacking the following tools:

Place the 'pk-***.pem' and 'cert-***.pem' certificates to the /opt/aws/keys directory.

Then configure a bash profile, inserting the following lines to '.bash_profile':

export JAVA_HOME=/usr/java/latest

export EC2_CERT=/opt/aws/cert-***.pem
export EC2_PRIVATE_KEY=/opt/aws/pk-***.pem

export EC2_HOME=/opt/aws/ec2
export PATH=$PATH:$EC2_HOME/bin

export AWS_CLOUDWATCH_HOME=/opt/aws/mon
export PATH=$PATH:$AWS_CLOUDWATCH_HOME/bin

export AWS_ELB_HOME=/opt/aws/elb
export PATH=$PATH:$AWS_ELB_HOME/bin

export AWS_AUTO_SCALING_HOME=/opt/aws/as
export PATH=$PATH:$AWS_AUTO_SCALING_HOME/bin

This increases the number of available commands to the extent we require.

1. We actually start from creating a balancer:

$ elb-create-lb testlb --headers --listener "lb-port=80,instance-port=8080,protocol=http" --listener "lb-port=443,instance-port=8443,protocol=tcp" --availability-zones us-east-1c
DNS_NAME DNS_NAME
DNS_NAME testlb-1678377526.us-east-1.elb.amazonaws.com

Where:

  • testlb - name of LB.
  • lb-port=80,instance-port=8080,protocol=http - external LB port and internal server port.

Same for HTTPS.

Suppose we have a ready-to-go instance with ID i-12345678. Let's put it under LB:

$ elb-register-instances-with-lb testlb --instances i-12345678
INSTANCE_ID i-12345678

This way the instance becomes available by the LB's address.

3. Create an image (Amazon Machine Image - AMI) of the working instance:

$ ec2-create-image i-12345678 --no-reboot -n 'Image Name' -d 'Image Description'
ami-87654321

4. Create a Launch config using the obtained AMI.

$ as-create-launch-config testlc -i ami-87654321 --key=keypair --group mygroup --instance-type m1.large
OK-Created launch config

Where:

  • keypair - a pair of keys we use to log in.
  • mygroup - a security group containing the servers to be launched.

5. Create an auto scaling group with the config we got from the previous step:

$ as-create-auto-scaling-group testsg -l testlc --availability-zones us-east-1c --min-size 0 --max-size 2 --load-balancers testlb
OK-Created AutoScalingGroup

Where:

  • testl - our config.
  • us-east-1c - zone containing instances we run.
  • 0 & 2 - minimum and maximum size of the group respectively.
  • testlb - name of the balancer covering the whole group.

This done, the group becomes ready to be scaled and balanced.

What we do now is setup CloudWatch alarms. CloudWatch-based auto scaling employs a policy - policy, to be configured. An example of such policy is increasing the number of instances by 1. That is first we configure a policy and then we create an alarm, which goes off, let's say, when the Load Average value is exceeding 5 for a minute straight.

6. Next comes the scale-up policy:

$ as-put-scaling-policy ScaleUpPolicy --auto-scaling-group testsg --adjustment=1 --type ChangeInCapacity --cooldown 120
arn:aws:autoscaling:us-east-1:278634951321:scalingPolicy:2f3482d2-ca6c-4653-970c-eb68a593cf26:autoScalingGroupName/testsg:policyName/ScaleUpPolicy

Where:

  • --adjustment=1 - +1 host to a group.
  • --cooldown 120 - all alarms will be ignored for 2 minutes, and no new instances will be started.

7. Finally, an alarm for scale up:

$ mon-put-metric-alarm HighLoadAvAlarm --comparison-operator GreaterThanOrEqualToThreshold --evaluation-periods 1 --metric-name LoadAverage --namespace CustomMetric --period 60 --statistic Average --threshold 5 --alarm-actions arn:aws:autoscaling:us-east-1:278634951321:scalingPolicy:2f3482d2-ca6c-4653-970c-eb68a593cf26:autoScalingGroupName/testsg:policyName/ScaleUpPolicy
OK-Created Alarm

Where:

  • --comparison-operator GreaterThanOrEqualToThreshold - operator greater or equal to threshold.
  • --evaluation-periods 1 - goes off at the first prompt.
  • --metric-name LoadAverage --namespace CustomMetric - our own custom metrics.
  • --period 60 - a minute for activation.
  • --statistic Average - calculated as average.
  • --threshold 5 - Load Average no greater than 5, remember?
  • --alarm-actions - policy description.

8. Scale-down policy:

$ as-put-scaling-policy ScaleDownPolicy --auto-scaling-group testsg --adjustment=-1 --type ChangeInCapacity --cooldown 420
arn:aws:autoscaling:us-east-1:278634951321:scalingPolicy:2f3482d2-ca6c-4653-970c-eb68a593cf26:autoScalingGroupName/testsg:policyName/ScaleDownPolicy

The cool-down period is longer here. After instance downscale the group does not downscale for 6 minutes minimum. Also, the adjustment is -1.

9. Alarm for scale down:

$ mon-put-metric-alarm LoLoadAvAlarm --comparison-operator LessThanThreshold --evaluation-periods 1 --metric-name LoadAverage --namespace CustomMetric --period 120 --statistic Average --threshold 3 --alarm-actions arn:aws:autoscaling:us-east-1:278634951321:scalingPolicy:2f3482d2-ca6c-4653-970c-eb68a593cf26:autoScalingGroupName/testsg:policyName/ScaleDownPolicy
OK-Created Alarm

The alarm goes off when the Load Average remains less than 3 for over 2 minutes straight.

And this is it. Depending on the load, our scale group can expand up to 3 instances and 1 of them always stays up. Once again, this has been the simplest example of auto scaling. Naturally, you can change the conditions and play around with numbers as your own pleasure.