Skip to content

aws sysops 05 cli ec2 elb

ebarault edited this page May 17, 2017 · 7 revisions

Using the AWS CLI to manage EC2 Load Balancing and Auto Scaling

CLI Reference: ELB v2
CLI Reference: Auto Scaling
CLI Reference: Amazon CloudWatch

Prerequisites

In order to operate an Elastic Load Balancer (ELB), we need:

  • 1 VPC
  • 1 Internet Gateway
  • 2 Subnets in different Availability Zones (AZ)
  • 1 Routing Table with a default route to the Internet Gateway
  • 1 Security Group in the VPC allowing traffic on port 22 (SSH)
  • 2 EC2 instances
    • 1 in each Subnet
    • 1 public IP per subnet

Let's create them first if required.

$ aws ec2 create-vpc --cidr-block 128.0.0.0/16
# "VpcId": "vpc-77aa341f",

$ aws ec2 create-internet-gateway
# "InternetGatewayId": "igw-6e091f07"

$ aws ec2 attach-internet-gateway --vpc-id vpc-77aa341f --internet-gateway-id igw-54988f3d

$ aws ec2 create-subnet --vpc-id vpc-77aa341f \
  --availability-zone eu-central-1a \
	--cidr-block 128.0.0.0/24
# "SubnetId": "subnet-7ea0fb16",

$ aws ec2 create-subnet --vpc-id vpc-77aa341f \
	--availability-zone eu-central-1b \
  --cidr-block 128.0.1.0/24
# "SubnetId": "subnet-90fe98ea"

$ aws ec2 create-route-table --vpc-id vpc-77aa341f
# "RouteTableId": "rtb-e0f1a588"

$ aws ec2 create-route --route-table-id rtb-e0f1a588 \
  --destination-cidr-block 0.0.0.0/0 --gateway-id igw-6e091f07

$ aws ec2 create-security-group --group-name LoadBalancing --vpc-id vpc-77aa341f \
  --description "Security group for application Load Balancing"
# "GroupId": "sg-11f2787a"

$ aws ec2 authorize-security-group-ingress --group-id sg-11f2787a --protocol tcp --port 22 --cidr 0.0.0.0/0

$ aws ec2 run-instances --image-id ami-060cde69 --count 1 \
	--instance-type t2.micro --key-name sysops --security-group-ids sg-11f2787a \
	--subnet-id subnet-7ea0fb16
# "InstanceId": "i-05a3fe7a54affda6f"

$ aws ec2 run-instances --image-id ami-060cde69 --count 1 \
	--instance-type t2.micro --key-name sysops --security-group-ids sg-11f2787a \
	--subnet-id subnet-90fe98ea
# "InstanceId": "i-08889a65ce7e0e7ce"

Create an Elastic Load Balancer

CLI Reference: create-load-balancer
CLI Reference: create-target-group
CLI Reference: create-listener
CLI Reference: register-targets

We use the create-load-balancer to create an ELB:

NOTE:

  • Mind the elbv2, otherwise you'd create a classic ELB instead of an application ELB.
$ aws elbv2 create-load-balancer --name myLoadBalancer  \
  --subnets subnet-7ea0fb16 subnet-90fe98ea --security-groups sg-11f2787a
{
    "LoadBalancers": [
        {
            "IpAddressType": "ipv4",
            "VpcId": "vpc-77aa341f",
            "LoadBalancerArn": "arn:aws:elasticloadbalancing:eu-central-1:832922743754:loadbalancer/app/myLoadBalancer/6b10d007807ede6f",
            "State": {
                "Code": "provisioning"
            },
            "DNSName": "myLoadBalancer-571877962.eu-central-1.elb.amazonaws.com",
            "SecurityGroups": [
                "sg-11f2787a"
            ],
            "LoadBalancerName": "myLoadBalancer",
            "CreatedTime": "2017-05-13T13:42:55.200Z",
            "Scheme": "internet-facing",
            "Type": "application",
            "CanonicalHostedZoneId": "Z215JYRZR1TBD5",
            "AvailabilityZones": [
                {
                    "SubnetId": "subnet-7ea0fb16",
                    "ZoneName": "eu-central-1a"
                },
                {
                    "SubnetId": "subnet-90fe98ea",
                    "ZoneName": "eu-central-1b"
                }
            ]
        }
    ]
}

Note the Amazon Resource Name (ARN) of the load balancer:

LoadBalancerArn`: `arn:aws:elasticloadbalancing:eu-central-1:832922743754:loadbalancer/app/myLoadBalancer/6b10d007807ede6f

Note the DNS of the Load Balander: "DNSName": "myLoadBalancer-571877962.eu-central-1.elb.amazonaws.com"

Next, we use the create-target-group command to create target group:

$ aws elbv2 create-target-group --name targetInstances --protocol HTTP --port 80 \
  --vpc-id vpc-77aa341f
{
    "TargetGroups": [
        {
            "HealthCheckPath": "/",
            "HealthCheckIntervalSeconds": 30,
            "VpcId": "vpc-77aa341f",
            "Protocol": "HTTP",
            "HealthCheckTimeoutSeconds": 5,
            "HealthCheckProtocol": "HTTP",
            "UnhealthyThresholdCount": 2,
            "HealthyThresholdCount": 5,
            "TargetGroupArn": "arn:aws:elasticloadbalancing:eu-central-1:832922743754:targetgroup/targetInstances/611964f09d978756",
            "Matcher": {
                "HttpCode": "200"
            },
            "HealthCheckPort": "traffic-port",
            "Port": 80,
            "TargetGroupName": "targetInstances"
        }
    ]
}

Note the ARN of the target group:

TargetGroupArn`: `arn:aws:elasticloadbalancing:eu-central-1:832922743754:targetgroup/targetInstances/611964f09d978756

Next, we Use the create-listener command to create a listener for the load balancer with a default rule that forwards requests to your target group:

$ aws elbv2 create-listener \
  --load-balancer-arn arn:aws:elasticloadbalancing:eu-central-1:832922743754:loadbalancer/app/myLoadBalancer/6b10d007807ede6f \
  --protocol HTTP --port 80  \
  --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:eu-central-1:832922743754:targetgroup/targetInstances/611964f09d978756
{
    "Listeners": [
        {
            "Protocol": "HTTP",
            "DefaultActions": [
                {
                    "TargetGroupArn": "arn:aws:elasticloadbalancing:eu-central-1:832922743754:targetgroup/targetInstances/611964f09d978756",
                    "Type": "forward"
                }
            ],
            "LoadBalancerArn": "arn:aws:elasticloadbalancing:eu-central-1:832922743754:loadbalancer/app/myLoadBalancer/6b10d007807ede6f",
            "Port": 80,
            "ListenerArn": "arn:aws:elasticloadbalancing:eu-central-1:832922743754:listener/app/myLoadBalancer/6b10d007807ede6f/e79db64400204ed4"
        }
    ]
}

At that stage we need to make sure the Load Balancer can receive traffic on port 80.
If not done already, add a rule to the Security Group to allow HTTP traffic from anywhere:

$ aws ec2 authorize-security-group-ingress --group-id sg-11f2787a \
  --protocol tcp --port 80 --cidr 0.0.0.0/0

Finally we register instances with the target group with the register-targets command:

$ aws elbv2 register-targets \
  --target-group-arn arn:aws:elasticloadbalancing:eu-central-1:832922743754:targetgroup/targetInstances/611964f09d978756 \
  --targets Id=i-05a3fe7a54affda6f,Port=3001 Id=i-08889a65ce7e0e7ce,Port=3001
# The command does nos return anything

NOTE:

  • use the deregister-targets command with the same signature as register-targets to deregister a target from a Target Group
aws elbv2 deregister-targets \
  --target-group-arn arn:aws:elasticloadbalancing:eu-central-1:832922743754:targetgroup/targetInstances/611964f09d978756 \
  --targets Id=i-05a3fe7a54affda6f,Port=3001

Optionally, verify the health of the registered targets for your target group using this describe-target-health command:

$ aws elbv2 describe-target-health \
  --target-group-arn arn:aws:elasticloadbalancing:eu-central-1:832922743754:targetgroup/targetInstances/611964f09d978756
{
    "TargetHealthDescriptions": [
        {
            "Target": {
                "Id": "i-08889a65ce7e0e7ce",
                "Port": 3001
            },
            "TargetHealth": {
                "State": "initial",
                "Reason": "Elb.RegistrationInProgress",
                "Description": "Target registration is in progress"
            }
        },
        {
            "Target": {
                "Id": "i-05a3fe7a54affda6f",
                "Port": 3001
            },
            "TargetHealth": {
                "State": "initial",
                "Reason": "Elb.RegistrationInProgress",
                "Description": "Target registration is in progress"
            }
        }
    ]
}

NOTE:

  • "Target registration is in progress" means that the no target is considered by healthy by the Load Balancer: no instance is currently able to handle traffic on port 3001, we need to fix this by
    • setting-up a server on instances to handle traffic on port 3001
    • tune the Target Group health check to target port 3001

Launch a simple http server

We will use Docker on intances to set up a simple http server configured to handle traffic on port 3001

First install Docker. You can follow this tutorial.

Then, have docker fetch and launch the crccheck/hello-world image from Docher Hub:

$ sudo docker run -d --name web-test -p 3001:8000 crccheck/hello-world

NOTE:

  • The -p 3001:8000 parameter tells the Docker image to listen on port 3001 and redirect internally traffic to port 8000 on which is locally running the http server

Let's pay attention to the content of the Dockerfile:

# Boostrap from a busybox Liux distribution
FROM busybox

# Add local file index.html as /index.html
ADD index.html /index.html

# Listen on port 8000
EXPOSE 8000

# A shell-powered web server using nc (netcat)
# http://www.busybox.net/downloads/BusyBox.html#nc
CMD while true ; do nc -l -p 8000 < /index.html ; done

Modify the Target Group health check

CLI Reference: modify-target-group

$ aws elbv2 modify-target-group \
  --target-group-arn arn:aws:elasticloadbalancing:eu-central-1:832922743754:targetgroup/targetInstances/611964f09d978756 \
  --health-check-protocol HTTP --health-check-port 3001

Now check our service is available on the our Load Balander DNS: http://myLoadBalancer-571877962.eu-central-1.elb.amazonaws.com

Did it work? "Et Voila !" 🙌

Creating advanced Load Balancer routing rules

CLI Reference: create-rule

The create-rules can be used to create regex-matching load balancing routing rules on host name or url path.

The following example creates a rule that forwards requests to the specified target group if the URL contains the specified pattern (for example, /img/*):

$ aws elbv2 create-rule \
  --listener-arn arn:aws:elasticloadbalancing:eu-central-1:832922743754:loadbalancer/app/myLoadBalancer/6b10d007807ede6f \
  --priority 10 \
  --conditions Field=path-pattern,Values='/img/*' \
  --actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:eu-central-1:832922743754:targetgroup/targetInstances/611964f09d978756

NOTE:

  • --priority: as one can expect represents the order in which the rule is parsed, the lower the greater
  • --conditions's syntax is Field=string,Values=string,string ...
    • Field value can be
      • host-header for rules on host name (my.example.com)
      • path-pattern for rules on the url path (/static/img/...)

Create an Auto-Scaling Group to operate with the Elastic Load Balancer

CLI Reference: autoscaling

Create a Launch Configuration

CLI Reference: create-launch-configuration

We use the create-launch-configuration command to create a Launch Configuration

$ aws autoscaling create-launch-configuration --launch-configuration-name myWebServerTemplate \
  --image-id ami-cbdd04a4 --instance-type t2.micro \
  --security-groups sg-11f2787a
  # The command does not return anything

Inspect the created Auto Scaling Group using the describe-launch-configurations command:

$ aws autoscaling describe-launch-configurations --launch-configuration-name myWebServerTemplate
{
    "LaunchConfigurations": [
        {
            "UserData": "",
            "EbsOptimized": false,
            "LaunchConfigurationARN": "arn:aws:autoscaling:eu-central-1:832922743754:launchConfiguration:f1d47441-89a2-419f-be12-7c9e7eef7014:launchConfigurationName/myWebServerTemplate",
            "InstanceMonitoring": {
                "Enabled": true
            },
            "ClassicLinkVPCSecurityGroups": [],
            "CreatedTime": "2017-05-14T09:14:47.120Z",
            "BlockDeviceMappings": [],
            "KeyName": "",
            "SecurityGroups": [
                "sg-11f2787a"
            ],
            "LaunchConfigurationName": "myWebServerTemplate",
            "KernelId": "",
            "RamdiskId": "",
            "ImageId": "ami-cbdd04a4",
            "InstanceType": "t2.micro"
        }
    ]
}

Create an Auto Scaling Group

CLI Reference: create-auto-scaling-group

Now, create an Auto Scaling Group to automatize the launch of instances matching the Launch Configuration we just created:

$ aws autoscaling create-auto-scaling-group --auto-scaling-group-name myAutoScalingGroup \
  --launch-configuration-name myWebServerTemplate \
  --vpc-zone-identifier "subnet-7ea0fb16" \
  --vpc-zone-identifier "subnet-90fe98ea" \
  --max-size 5 --min-size 1 --desired-capacity 2
# The command does not return anything

Inspect the created Auto Scaling Group using the describe-launch-configurations command:

$ aws autoscaling describe-auto-scaling-groups --auto-scaling-group-name myAutoScalingGroup
{
    "AutoScalingGroups": [
        {
            "AutoScalingGroupARN": "arn:aws:autoscaling:eu-central-1:832922743754:autoScalingGroup:c9ef1262-fe70-4fcb-a50b-580894936dec:autoScalingGroupName/myAutoScalingGroup",
            "TargetGroupARNs": [],
            "SuspendedProcesses": [],
            "DesiredCapacity": 2,
            "Tags": [],
            "EnabledMetrics": [],
            "LoadBalancerNames": [],
            "AutoScalingGroupName": "myAutoScalingGroup",
            "DefaultCooldown": 300,
            "MinSize": 1,
            "Instances": [
                {
                    "ProtectedFromScaleIn": false,
                    "AvailabilityZone": "eu-central-1b",
                    "InstanceId": "i-02b7c30abd3395bc1",
                    "HealthStatus": "Healthy",
                    "LifecycleState": "Pending",
                    "LaunchConfigurationName": "myWebServerTemplate"
                },
                {
                    "ProtectedFromScaleIn": false,
                    "AvailabilityZone": "eu-central-1b",
                    "InstanceId": "i-08e012c42760fd01b",
                    "HealthStatus": "Healthy",
                    "LifecycleState": "Pending",
                    "LaunchConfigurationName": "myWebServerTemplate"
                }
            ],
            "MaxSize": 5,
            "VPCZoneIdentifier": "subnet-90fe98ea",
            "HealthCheckGracePeriod": 0,
            "TerminationPolicies": [
                "Default"
            ],
            "LaunchConfigurationName": "myWebServerTemplate",
            "CreatedTime": "2017-05-14T09:16:55.264Z",
            "AvailabilityZones": [
                "eu-central-1b"
            ],
            "HealthCheckType": "EC2",
            "NewInstancesProtectedFromScaleIn": false
        }
    ]
}

NOTE:

  • As you can see 2 new instances are being created just as specified in the Auto Scaling Group configuration (--desired-capacity 2).

Attach a Load Balancer to an Auto Scaling GroupId

CLI Reference: create-auto-scaling-group

To make things clean, let's create a new Load Balancer rather than reusing the one from the beginning of the lesson.

$ aws elbv2 create-load-balancer --name myOtherLoadBalancer  \
  --subnets subnet-7ea0fb16 subnet-90fe98ea --security-groups sg-11f2787a
# "LoadBalancerArn": "arn:aws:elasticloadbalancing:eu-central-1:832922743754:loadbalancer/app/myOtherLoadBalancer/ade199a586a5c0a1"

$ aws elbv2 create-target-group --name autoScaledInstances --protocol HTTP --port 3001 \
  --vpc-id vpc-77aa341f
# "TargetGroupArn": "arn:aws:elasticloadbalancing:eu-central-1:832922743754:targetgroup/autoScaledInstances/475b305ae810e515"

$ aws elbv2 create-listener \
  --load-balancer-arn arn:aws:elasticloadbalancing:eu-central-1:832922743754:loadbalancer/app/myOtherLoadBalancer/ade199a586a5c0a1 \
  --protocol HTTP --port 80  \
  --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:eu-central-1:832922743754:targetgroup/autoScaledInstances/475b305ae810e515

NOTE:

  • When creating the Load Balancer's Target Group we specified the port on which instances receive traffic with paramater --port.

Next, attach the Auto Scaling Group to the Load Balancer's Target Group.

$ aws autoscaling attach-load-balancer-target-groups \
  --auto-scaling-group-name myAutoScalingGroup \
  --target-group-arns arn:aws:elasticloadbalancing:eu-central-1:832922743754:targetgroup/autoScaledInstances/475b305ae810e515

Inspect the Auto Scaling Group using the describe-launch-configurations command, the TargetGroupARNs should now be filled with our Load Balancer's Target Group ARN:

"TargetGroupARNs": [
    "arn:aws:elasticloadbalancing:eu-central-1:832922743754:targetgroup/autoScaledInstances/475b305ae810e515"
],

NOTE:

  • we could also have attached a Load Balancer right away to an Auto Scaling Group at creation time using the --target-group-arns parameter:
--target-group-arns "arn:aws:elasticloadbalancing:eu-central-1:832922743754:targetgroup/autoScaledInstances/475b305ae810e515"

Auto Scaling Actions

Scheduled Actions

CLI Reference: put-scheduled-update-group-action

You can specify a recurrence schedule, in UTC, using the Cron format with the put-scheduled-update-group-action command. - See also Crontab.guru.

$ aws autoscaling put-scheduled-update-group-action \
  --scheduled-action-name scaleInAtNight --auto-scaling-group-name myAutoScalingGroup --recurrence "0 2 * * *" --desired-capacity 1
  # every night at 02:00
React on Metrics Alarms
Create Scaling Policies

CLI Reference: put-scaling-policy

Use the put-scaling-policy command to create a scaling policy with an adjustment type ChangeInCapacity that increases the capacity of the group by 1 instance:

We also specify warmup and cooldown values of 30 seconds:

  • --estimated-instance-warmup defines the time, in seconds, until a newly launched instance can contribute to the CloudWatch metrics
  • --cooldown defines the time, in seconds, after a scaling activity completes and before the next scaling activity can start.
$ aws autoscaling put-scaling-policy --policy-name myScaleoutPolicy \
  --auto-scaling-group-name myAutoScalingGroup \
  --adjustment-type ChangeInCapacity \
  --scaling-adjustment 1
  --estimated-instance-warmup 30
  --cooldown 30
  --
{
    "PolicyARN": "arn:aws:autoscaling:us-west-2:123456789012:scalingPolicy:ac542982-cbeb-4294-891c-a5a941dfa787:autoScalingGroupName/myAutoScalingGroup:policyName/myScaleoutPolicy"
}

Use once more the put-scaling-policy command to create a scaling policy with an adjustment type ChangeInCapacity that decreases the capacity of the group by 2 instances:

$ aws autoscaling put-scaling-policy --policy-name myScaleinPolicy \
  --auto-scaling-group-name myAutoScalingGroup \
  --adjustment-type ChangeInCapacity \
  --scaling-adjustment -2
  --estimated-instance-warmup 30
  --cooldown 30
{
    "PolicyARN": "arn:aws:autoscaling:us-west-2:123456789012:scalingPolicy:4ee9e543-86b5-4121-b53b-aa4c23b5bbcc:autoScalingGroupName/myAutoScalingGroup:policyName/myScaleinPolicy"
}
Create CloudWatch Alarms

CLI Reference: put-metric-alarm

We just created Scaling Policies* that provided instructions to the Auto Scaling Group about how to scale out and scale in when the conditions that you specify change.

In this step, we create Actions triggerd on Alarms* by

  • identifying the metrics to watch
  • defining the conditions for scaling
  • associating the alarms with the scaling policies

Use the CloudWatch put-metric-alarm command to create an alarm that increases the size of the Auto Scaling Group when the value of the CPUUtilization metric breaches over 60.

$ aws cloudwatch put-metric-alarm --alarm-name AddCapacity \
  --metric-name CPUUtilization --namespace AWS/EC2 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --statistic Average --period 60 --threshold 60 \
  --dimensions "Name=AutoScalingGroupName,Value=myAutoScalingGroup" \
  --evaluation-periods 2 \
  --alarm-actions "arn:aws:autoscaling:us-west-2:123456789012:scalingPolicy:ac542982-cbeb-4294-891c-a5a941dfa787:autoScalingGroupName/myAutoScalingGroup:policyName/myScaleoutPolicy"
  # Scale-out Policy ARN

Note:

  • The --dimensions parameter allows defining the range of resources over which the metric is computed. For example a single instance, a whole Auto Scaling Group, all instances of a given type or image.
  • --evaluation-periods defines the consecutive number of periods the metric has to breach the threshold to trigger the action

Use again the CloudWatch put-metric-alarm command to create an alarm that decreases the size of the Auto Scaling Group when the value of the CPUUtilization metric breaches under 30.

$ aws cloudwatch put-metric-alarm --alarm-name RemoveCapacity \
  --metric-name CPUUtilization --namespace AWS/EC2 \
  --comparison-operator LessThanOrEqualToThreshold \
  --statistic Average --period 60 --threshold 30   
  --dimensions "Name=AutoScalingGroupName,Value=myAutoScalingGroup" \
  --evaluation-periods 2 --alarm-actions "arn:aws:autoscaling:us-west-2:123456789012:scalingPolicy:4ee9e543-86b5-4121-b53b-aa4c23b5bbcc:autoScalingGroupName/myAutoScalingGroup:policyName/myScaleinPolicy"
  # Scale-in Policy ARN

Monitoring Scaling Activities

CLI Reference: describe-scaling-activities

$ aws autoscaling describe-scaling-activities --auto-scaling-group-name myAutoScalingGroup
# Returns activites for a given Auto Scaling Group

$ aws autoscaling describe-scaling-activities --max-items 5
# Returns a specific number of activities

$ aws autoscaling describe-scaling-activities --activity-ids 980bbf70-465c-43c0-ad4c-a2a1d92ebc98
# Returns specific(s) activities
{
    "Activities": [
      {
          "Description": "Launching a new EC2 instance: i-08e012c42760fd01b", 
          "AutoScalingGroupName": "myAutoScalingGroup", 
          "ActivityId": "980bbf70-465c-43c0-ad4c-a2a1d92ebc98", 
          "Details": "{\"Subnet ID\":\"subnet-7ea0fb16\",\"Availability Zone\":\"eu-central-1b\"}", 
          "StartTime": "2017-05-14T09:16:59.007Z", 
          "Progress": 100, 
          "EndTime": "2017-05-14T09:17:31Z", 
          "Cause": "At 2017-05-14T09:16:55Z a user request created an AutoScalingGroup changing the desired capacity from 0 to 2.  At 2017-05-14T09:16:57Z an instance was started in response to a difference between desired and actual capacity, increasing the capacity from 0 to 2.", 
          "StatusCode": "Successful"
      }, 
    ]
}

Deleting an Auto Scaling Group

CLI Reference: delete-auto-scaling-group

If the group has instances or scaling activities in progress, you must specify the option to force the deletion in order for it to succeed (--force-delete).

To remove instances from the Auto Scaling group before deleting it, call detach-instances with the list of instances and the option to decrement the desired capacity so that Auto Scaling does not launch replacement instances.

To terminate all instances before deleting the Auto Scaling group, call update-auto-scaling-group and set the minimum size and desired capacity of the Auto Scaling group to zero.

$ aws autoscaling update-auto-scaling-group --auto-scaling-group-name myAutoScalingGroup --min-size 0
$ aws autoscaling update-auto-scaling-group --auto-scaling-group-name myAutoScalingGroup --desired-capacity 0
$ aws autoscaling delete-auto-scaling-group --auto-scaling-group-name myAutoScalingGroup
$ aws autoscaling delete-launch-configuration --launch-configuration-name myWebServerTemplate