Showing posts with label API. Show all posts
Showing posts with label API. Show all posts

Monday, November 12, 2012

Automatically Manage your AWS EC2 Instance Public IP Addresses with Route53

aws-route-53-rest-api-dns-ec2-call


Our Goal: Easy access to our Instances by Name instead to locate them through EC2 Console after an IP change caused by a stop/start action.

Is quite tedious the need to open the AWS Console to find an instance Public IP after a stop/start action or if we forgot which previously it was. Here I show you a tool that consists in a script executed inside the instance that updates its DNS records in Route53 using the instance Tag "Name". This is and optional Tag we can use to store the "Host Name" when launching a new instance or edit it anytime we need afterwards. If this optional tag is not present, the script I show you here, will use the instance ID to update (or create) the corresponding DNS A Record. This way we will have always the instance accessible through its FQDN and it will be stable (It won't change overtime).
Example: My-Instance-Host-Name.ec2.My-Domain.com

$ ssh -i juankeys.pem ec2-user@webserver1.ec2.donatecpu.com

Last login: Mon Nov 12 00:14:35 2012 from 77.224.98.33
       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|
https://aws.amazon.com/amazon-linux-ami/2012.09-release-notes/ 
There are 4 total update(s) available
Run "sudo yum update" to apply all updates.

[ec2-user@webserver1 ~]$ 


Instance Tag Name
Configure your EC2 instance with a Tag Name using the Console. Usually the Instance Launch Wizard will ask you for it but if is empty, you can update it any time you want. In this example the Tag Name will be "webserver1".

aws-route-53-ec2-tag-name-donatecpu-com


Preparations
Log into your instance and make sure that the EC2 API is ready to run. Follow this previous post if you need help with that. You will need a IAM user with admin permissions on Route53.


Route53
Create a new zone in Route53 (if you don't have any created yet) and save the assigned Hosted Zone ID:

aws-route-53-ec2-tag-name-donatecpu-com


dnscurl.pl
dnscurl.pl is an AWS Perl tool that will help you to use the Route53 API. Unlike other AWS APIs, Route53's API uses REST methods. This means that is accessible using HTTP calls (similar to accessing instance metadata) which looks good but the authentication process is a painfuldnscurl.pl simplifies the authentication process to generate the calls (GET and POST) to the Route 53 API.

Create a directory called /root/bin/ to store our tools, download dnscurl.pl, and make it executable:

# cd /root

# mkdir bin

# cd bin

# wget -q http://awsmedia.s3.amazonaws.com/catalog/attachments/dnscurl.pl

# chmod u+x dnscurl.pl

Note: You can also download the dnscurl.pl from here using a browser.

Create in the same folder a file called ".aws-secrets" (note the dot at the begining of the file name) with the following content and make it only readable for root:

%awsSecretAccessKeys = (
    '(your key name without parentheses)' => {
        id => '(your access key without parentheses)',
        key => '(your secret key without parentheses)', 
    },
);

# chmod go-rwx .aws-secrets 

Test dnscurl.pl with a simple read-only call. If everything is good, you should see something like this:

# ./dnscurl.pl --keyfile ./.aws-secrets --keyname juan -- -v -H "Content-Type: text/xml; charset=UTF-8" https://route53.amazonaws.com/2012-02-29/hostedzone/Z1F5BRDVBM
                                                                           0.0%
* About to connect() to route53.amazonaws.com port 443 (#0)
*   Trying 72.21.194.53...
* connected
* Connected to route53.amazonaws.com (72.21.194.53) port 443 (#0)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
  CApath: none
* SSL connection using SSL_RSA_WITH_RC4_128_MD5
* Server certificate:
* subject: CN=route53.amazonaws.com,O=Amazon.com Inc.,L=Seattle,ST=Washington,C=US
* start date: Nov 05 00:00:00 2010 GMT
* expire date: Nov 04 23:59:59 2013 GMT
* common name: route53.amazonaws.com
* issuer: CN=VeriSign Class 3 Secure Server CA - G3,OU=Terms of use at https://www.verisign.com/rpa (c)10,OU=VeriSign Trust Network,O="VeriSign, Inc.",C=US
> GET /2012-02-29/hostedzone/Z1F5BRDVBM HTTP/1.1
> User-Agent: curl/7.24.0 (x86_64-redhat-linux-gnu) libcurl/7.24.0 NSS/3.13.5.0 zlib/1.2.5 libidn/1.18 libssh2/1.2.2
> Host: route53.amazonaws.com
> Accept: */*
> Content-Type: text/xml; charset=UTF-8
> Date: Sun, 11 Nov 2012 23:21:26 GMT
> X-Amzn-Authorization: AWS3-HTTPS AWSAccessKeyId=AKIAJ5,Algorithm=HmacSHA1,Signature=/i+0d=

< HTTP/1.1 200 OK
< x-amzn-RequestId: 843632ca-2c56-11e2-94bf-3b3ef9a8f457
< Content-Type: text/xml
< Content-Length: 582
< Date: Sun, 11 Nov 2012 23:21:26 GMT

<?xml version="1.0"?>
* Connection #0 to host route53.amazonaws.com left intact
<GetHostedZoneResponse xmlns="https://route53.amazonaws.com/doc/2012-02-29/"><HostedZone><Id>/hostedzone/Z1F5BRDVBM</Id><Name>donatecpu.com.</Name><CallerReference>454848C9-18D1-2DDB-AC24-B629E</CallerReference><Config/><ResourceRecordSetCount>2</ResourceRecordSetCount></HostedZone><DelegationSet><NameServers><NameServer>ns-1146.awsdns-15.org</NameServer><NameServer>ns-1988.awsdns-56.co.uk</NameServer><NameServer>ns-228.awsdns-28.com</NameServer><NameServer>ns-783.awsdns-33.net</NameServer></NameServers></DelegationSet></GetHostedZoneResponse>* Closing connection #0

You should see a correctly created AWSAccessKeyId and Signature, no error messages and at the bottom and XML output showing the DNS Servers for you Zone.


start-up-names.sh
Download my script start-up-names.sh and make it executable:
# wget -q http://www.domenech.org/files/start-up-names.sh 

# chmod u+x start-up-names.sh

Or copy and paste the following text into a file called start-up-names.sh

#!/bin/bash
# start-up-names.sh
# http://blog.domenech.org

logger start-up-name.sh Started

#More environment variables than we need but... we always do that
export AWS_CREDENTIAL_FILE=/opt/aws/apitools/mon/credential-file-path.template
export AWS_CLOUDWATCH_HOME=/opt/aws/apitools/mon
export AWS_IAM_HOME=/opt/aws/apitools/iam
export AWS_PATH=/opt/aws
export AWS_AUTO_SCALING_HOME=/opt/aws/apitools/as
export AWS_ELB_HOME=/opt/aws/apitools/elb
export AWS_RDS_HOME=/opt/aws/apitools/rds
export EC2_AMITOOL_HOME=/opt/aws/amitools/ec2
export EC2_HOME=/opt/aws/apitools/ec2
export JAVA_HOME=/usr/lib/jvm/jre
export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/opt/aws/bin:/root/bin

# *** Configure these values with your settings ***
#API Credentials
AWSSECRETS="/root/bin/.aws-secrets"
KEYNAME="juan"
#Hosted Zone ID obtained from Route53 Console once the zone is created
HOSTEDZONEID="Z1F5BRDVBM"
#Domain name configured in Route53 and used to store our server names
DOMAIN="ec2.donatecpu.com"
# *** Configuration ends here ***

#Let's get the Credentials that EC2 API needs from .aws-secrets dnscurl.pl file
ACCESSKEY=`cat $AWSSECRETS | grep id | cut -d\' -f2`
SECRETKEY=`cat $AWSSECRETS | grep key | cut -d\' -f2`

#InstanceID Obtained from MetaData 
INSTANCEID=`wget -q -O - http://169.254.169.254/latest/meta-data/instance-id`

#Public Instance IP obtained from MetaData
PUBLICIP=`wget -q -O - http://169.254.169.254/latest/meta-data/public-ipv4`

#IP Currently configured in the DNS server (if exists)
CURRENTDNSIP=`dig $INSTANCEID"."$DOMAIN A | grep -v ^\; | sort | tail -1 | awk '{print $5}'`

#Instance Name obtained from the Instance Custom Tag NAME
WGET="`wget -q -O - http://169.254.169.254/latest/meta-data/instance-id`"
INSTANCENAME=`ec2-describe-instances -O $ACCESSKEY -W $SECRETKEY $WGET --show-empty-fields | grep TAG | grep Name | awk '{ print $5 }'`

echo $INSTANCEID $PUBLICIP $CURRENTDNSIP $INSTANCENAME
logger $INSTANCEID $PUBLICIP $CURRENTDNSIP $INSTANCENAME

#Set the new Hostname using the Instance Tag OR the Instance ID
if [ -n "$INSTANCENAME" ]; then
hostname $INSTANCENAME
logger Hostname from InstanceName set to $INSTANCENAME
else
hostname $INSTANCEID
logger Hostname from InstanceID set to $INSTANCEID
fi

#dnscurl.pl Delete Current InstanceID Public IP A Record to allow Later Update
COMMAND="<?xml version=\"1.0\" encoding=\"UTF-8\"?><ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2012-02-29/\"><ChangeBatch><Changes><Change><Action>"DELETE"</Action><ResourceRecordSet><Name>"$INSTANCEID"."$DOMAIN".</Name><Type>A</Type><TTL>600</TTL><ResourceRecords><ResourceRecord><Value>"$CURRENTDNSIP"</Value></ResourceRecord></ResourceRecords></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>"

/root/bin/dnscurl.pl --keyfile $AWSSECRETS --keyname $KEYNAME -- -v -H "Content-Type: text/xml; charset=UTF-8" -X POST https://route53.amazonaws.com/2012-02-29/hostedzone/$HOSTEDZONEID/rrset -d "$COMMAND"

#dnscurl.pl Create InstanceID Public IP A Record
COMMAND="<?xml version=\"1.0\" encoding=\"UTF-8\"?><ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2012-02-29/\"><ChangeBatch><Changes><Change><Action>"CREATE"</Action><ResourceRecordSet><Name>"$INSTANCEID"."$DOMAIN".</Name><Type>A</Type><TTL>600</TTL><ResourceRecords><ResourceRecord><Value>"$PUBLICIP"</Value></ResourceRecord></ResourceRecords></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>"

/root/bin/dnscurl.pl --keyfile $AWSSECRETS --keyname $KEYNAME -- -v -H "Content-Type: text/xml; charset=UTF-8" -X POST https://route53.amazonaws.com/2012-02-29/hostedzone/$HOSTEDZONEID/rrset -d "$COMMAND"

logger Entry $INSTANCEID.$DOMAIN sent to Route53

#Create DNS A record for Instance Name (if exists)
if [ -n "$INSTANCENAME" ]; then

#dnscurl.pl Delete Current Instance Name Public IP A Record to allow Later Update
COMMAND="<?xml version=\"1.0\" encoding=\"UTF-8\"?><ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2012-02-29/\"><ChangeBatch><Changes><Change><Action>"DELETE"</Action><ResourceRecordSet><Name>"$INSTANCENAME"."$DOMAIN".</Name><Type>A</Type><TTL>600</TTL><ResourceRecords><ResourceRecord><Value>"$CURRENTDNSIP"</Value></ResourceRecord></ResourceRecords></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>"

/root/bin/dnscurl.pl --keyfile $AWSSECRETS --keyname $KEYNAME -- -v -H "Content-Type: text/xml; charset=UTF-8" -X POST https://route53.amazonaws.com/2012-02-29/hostedzone/$HOSTEDZONEID/rrset -d "$COMMAND"

#dnscurl.pl Create Instance Name Public IP A Record
COMMAND="<?xml version=\"1.0\" encoding=\"UTF-8\"?><ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2012-02-29/\"><ChangeBatch><Changes><Change><Action>"CREATE"</Action><ResourceRecordSet><Name>"$INSTANCENAME"."$DOMAIN".</Name><Type>A</Type><TTL>600</TTL><ResourceRecords><ResourceRecord><Value>"$PUBLICIP"</Value></ResourceRecord></ResourceRecords></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>"

/root/bin/dnscurl.pl --keyfile $AWSSECRETS --keyname $KEYNAME -- -v -H "Content-Type: text/xml; charset=UTF-8" -X POST https://route53.amazonaws.com/2012-02-29/hostedzone/$HOSTEDZONEID/rrset -d "$COMMAND"

logger Entry $INSTANCENAME.$DOMAIN sent to Route53
fi

logger start-up-names.sh Ended

Edit the script and adapt the variables from the "*** Configure these values with your settings ***" section with your parameters.

Test it:

# ./start-up-names.sh

(text output)

# tail /var/log/messages

Nov 11 23:30:57 ip-10-29-30-48 ec2-user: start-up-name.sh StartedNov 11 23:30:59 ip-10-29-30-48 ec2-user: i-87eef4e1 54.242.191.68 ns-1146.awsdns-15.org. webserver1
Nov 11 23:30:59 ip-10-29-30-48 ec2-user: Hostname from InstanceName set to webserver1
Nov 11 23:31:00 ip-10-29-30-48 ec2-user: Entry i-87eef4e1.ec2.donatecpu.com sent to Route53
Nov 11 23:31:00 ip-10-29-30-48 ec2-user: Entry webserver1.ec2.donatecpu.com sent to Route53
Nov 11 23:31:00 ip-10-29-30-48 ec2-user: start-up-names.sh Ended

Reading /var/log/messages you should have something like this output. First the script gathers the Instance ID and the Public IP reading the Instance Metadata. Then the current IP ($CURRENTDNSIP) configured at the DNS (if any) using dig and the Instance Tag Name using the ec2-describe-instances command. The first change to happen is the Host Name. If the Instance Tag Name is present it will become the machine Host Name and if not, the Instance ID will play this role. One way or the other we will have a stable way to identify our servers. The Instance ID is unique and won't change over time. Then we call the Route53 API using dnscurl.pl four times. There is no API call to "overwrite" and existing DNS record so we need to Delete it first and Create it afterwards. The Delete call has to include the exact values the current entry has (quite silly if you ask me...) so that is why the scripts needs the current Public IP configured. We Delete using the old values and Create using the new ones. One dnscurl execution for the Instance ID (that always exists) and again for the Instance Tag Name (if present).

Two entries should have been automatically created in your Hosted Zoned and present at Route53 console for our Instance:

aws-route-53-dns-record-set

Those entries are ready to use and now you can forget its Instance ID or volatile Public IP and just ping or ssh to the name. Example: webserver1.ec2.donatecpu.com.


Auto Start
The main purpose is to maintain our servers IPs automatically updated in our DNS so we need that the main script is executed every time the machine starts. Once we've verified that it works fine is time to edit /etc/rc.local and add start-up-names.sh full path to it:

#!/bin/sh
#
# This script will be executed *after* all the other init scripts.
# You can put your own initialization stuff in here if you don't
# want to do the full Sys V style init stuff.

touch /var/lock/subsys/local

/root/bin/start-up-names.sh

And that is it. I suggest you to manually stop and start your instance and verify that its new assigned Public IP is updated in the DNS. All AMIs you generate from this Instance will include this described  configuration and therefore they will dynamically maintain their IPs. Cool!

Note: When playing with changes in DNS Records their TTL value matters. In this exercise we've used a value of 600 seconds so a change could take up to 10 minutes to be available in your local area network if your DNS server has cached it.

Saturday, November 3, 2012

AWS EC2 Auto Scaling: Basic Configuration

aws-ec2-auto-scaling-basic-configuration-diagram

Our goal: Create an Auto Scaling EC2 Group in a single Availability Zone and use a HTTP status page as a Health Monitor for our Load Balancer and the Auto Scaling group instances.

This exercise will show us some Auto Scaling basics and will be useful to understand the concepts beneath but the Auto Scaling Group will not automatically "scale" responding to external influence like Average CPU Usage or Total Apache Connections (This aspect is covered in this post: AWS EC2 Auto Scaling: External CloudWatch Metric). With the Auto Scaling configuration described here, we will obtain a web server cluster that can be increased and decreased in members with a simple Auto Scaling API call and we will transfer the monitoring role to the ELB to automatically replace failed EC2 instances or web servers.


What we need for the exercise:
This exercise assumes you have previous experience with EC2 Instances, Security Groups, Custom AMIs and EC2 Load Balancers.

- An empty ELB.
- A custom AMI with HTTP server installed.
- A custom Test Web Page called "ping.html".
- A EC2 Keys Pair to use to access our instances.
- A EC2 Security Group.
- Auto Scaling API. If you need help configuring the access to the Auto Scaling API check this post.


Preparation:
Is important to be sure that all the ingredients are working as expected. Auto Scaling could be difficult to debug and nasty situations may occur like: A group of instances starting while you are away or a new instance starting and stoping every 20 seconds with bad billing consequences (AWS will charge you a full hour for any started instance, despite it has been only one minute running).
I strongly suggest to manually test your setup before create a Auto Scaling configuration.

- Create your Key Pair (In my example "juankeys").

- Deploy an ELB (In my example is named "elb-prueba") in your default AZ ("a"). Configure the ELB to use your custom /ping.html page as Instance Health Monitor. You should see something like this:


- Create a Security Group for your Web Server instances (In my example "wed-servers"). Add to this Security Group the ELB Security Group for Port 80. It should look like the capture below. In this example this SG allows to Ping and TCP access from my home to the Instances AND allows access to port 80 to the connections originated in my Load Balancers (amazon-elb-sg). The Web Server port 80 is not open to Internet, is only open to the ELB.



- Deploy a EC2 Instance using the previous created Key Pair and Security Group. Install a HTTP server and be sure it is configured to start automatically. Create a Test Page called /ping.html at the web sever root folder. This text page can print out ant text you like. Its only mission is to be present. A HTTP 200 is OK and anything else is KO.

- Create your Custom AMI from the previous created temporal instance. Terminate the previous created temporal instance when finished.

- Deploy a new instance using the recently created AMI  (In my example "ami-1ceb5075") to test it. Check if the HTTP Server starts automatically.

- Manually add the recently created instance under the ELB. Verify that the Load Balancer Check works and it gives you the Status "In Service" for this instance. Verify that the /ping.html page can be accessed from Internet using a browser and the ELB public DNS name ("http://(you-ELB-DNS-name)/ping.html").

- Once everything checks OK, remove the instance from the ELB and Terminate the instance.


Definition:

# as-create-launch-config config-prueba --image-id ami-1ceb5075 --instance-type t1.micro --monitoring-disabled --group web-servers --key juankeys

OK-Created launch config

# as-create-auto-scaling-group grupo-prueba --launch-configuration config-prueba --availability-zones us-east-1a --min-size 0 --max-size 4 --load-balancers elb-prueba --health-check-type ELB --grace-period 120

OK-Created AutoScalingGroup

With as-create-launch-config we define the Instance configuration we will be using in our Auto Scaling Group: Launch config name, AMI ID, Intance Type, Advanced Monitoring (1 minute monitoring) disabled, Security Group and Key Pair to use.

With as-create-auto-scaling-group we define the group itself: Group Name, Launch Confing to use, AZs to deploy in, the minimum number of running instances that our application needs to run,  the maximum number of instances we desire to scale up to, ELB name, the Health Check type set to ELB (by default is the EC2 System Status) and the grace period of time grant to a instance before is checked after launch (in seconds).

Note: By default all the API calls are sent to the us-east-1 Region (N.Virginia).


Describe:

# as-describe-launch-configs --headers

LAUNCH-CONFIG  NAME           IMAGE-ID      TYPE
LAUNCH-CONFIG  config-prueba  ami-1ceb5075  t1.micro  

# as-describe-auto-scaling-groups --headers


AUTO-SCALING-GROUP  GROUP-NAME    LAUNCH-CONFIG  AVAILABILITY-ZONES  LOAD-BALANCERS  MIN-SIZE  MAX-SIZE  DESIRED-CAPACITY  TERMINATION-POLICIES
AUTO-SCALING-GROUP  grupo-prueba  config-prueba  us-east-1a          elb-prueba      0         4         0                 Default 

We use "as-describe-" commands to read the result of our last configuration. Special attention to as-describe-auto-scaling-instances:

# as-describe-auto-scaling-instances --headers   

No instances found

This command give us quick look to the running instances within our AS Groups. This is very useful when dealing with AS to find out the amount of instances running and its state. Now the result is "No instances found" and this is correct. Our current configuration says that zero is the minimum healthy instances our application needs to work and therefore, zero is the result.

Bring it to Production:

Let's say to AS that minimum is now 1 and describe the configuration:

# as-update-auto-scaling-group grupo-prueba --min-size 1

OK-Updated AutoScalingGroup

#  as-describe-auto-scaling-groups --headers

AUTO-SCALING-GROUP  GROUP-NAME    LAUNCH-CONFIG  AVAILABILITY-ZONES  LOAD-BALANCERS  MIN-SIZE  MAX-SIZE  DESIRED-CAPACITY  TERMINATION-POLICIES
AUTO-SCALING-GROUP  grupo-prueba  config-prueba  us-east-1a          elb-prueba      1         4         1                 Default          
INSTANCE  INSTANCE-ID  AVAILABILITY-ZONE  STATE    STATUS   LAUNCH-CONFIG
INSTANCE  i-5bb9e427   us-east-1a         Pending  Healthy  config-prueba

Notice that now Minimum is 1 in the AS configuration and now there is a new instance under our AS Group ("i-5bb9e427" in this example). This instance has been automatically deployed by AS to match the desired number of healthy instances for our application. Notice the "Pending" status that means that it is still in the initialization process. We can follow this process with as-describe-auto-scaling-instances:

#  as-describe-auto-scaling-instances
INSTANCE  i-5bb9e427  grupo-prueba  us-east-1a  Pending  HEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-5bb9e427  grupo-prueba  us-east-1a  Pending  HEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-5bb9e427  grupo-prueba  us-east-1a  Pending  HEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-5bb9e427  grupo-prueba  us-east-1a  Pending  HEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-5bb9e427  grupo-prueba  us-east-1a  InService  HEALTHY  config-prueba 
# as-describe-auto-scaling-instances
INSTANCE  i-5bb9e427  grupo-prueba  us-east-1a  InService  HEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-5bb9e427  grupo-prueba  us-east-1a  InService  HEALTHY  config-prueba

Now the recently deployed instance is in service. That means that its Health Check (ELB ping.html test page) verifies OK. If you open the AWS Console and read the current ELB "Instances" Tab, the new instance ID should be there automatically added to the Load Balancer and your application up and running.

Common problem scenarios:
- If you observe that the new instances are constantly Deployed and Terminated by AS this probably means that ping.html page fails. Stop the experiment with "as-update-auto-scaling-group grupo-prueba --min-size 0" and verify your components.
- If your web server and test page is verified OK but the AS is still Deploying and Terminating the instances without a chance to rise to the Healthy status then you should increase the value of "--grace-period" in the AS Group definition to give more time to your AMI to start a initialize its services.
- If the instances start but they fail to automatically be added to the ELB then probably the Instances are deployed in a incorrect Availability Zone. Either correct your AS Launch Configuration or expand the ELB to the rest of AZs in your Region.


Sabotage:

Log-in as root to the recently deployed AS Instance and force it to fail with this command "mv /var/www/html/ping.html /var/www/html/ping.html.KO". You can see at the /var/log/httpd/access_log file that the ELB is looking for the test page and it is failing:

- 10.29.36.216 - - [03/Nov/2012:12:23:45 +0000] "GET /ping.html HTTP/1.1" 200 49 "-" "ELB-HealthChecker/1.0"
- 10.29.36.216 - - [03/Nov/2012:12:23:51 +0000] "GET /ping.html HTTP/1.1" 200 49 "-" "ELB-HealthChecker/1.0"
- 10.29.36.216 - - [03/Nov/2012:12:23:57 +0000] "GET /ping.html HTTP/1.1" 404 286 "-" "ELB-HealthChecker/1.0" 
- 10.29.36.216 - - [03/Nov/2012:12:24:03 +0000] "GET /ping.html HTTP/1.1" 404 286 "-" "ELB-HealthChecker/1.0"
- 10.29.36.216 - - [03/Nov/2012:12:24:09 +0000] "GET /ping.html HTTP/1.1" 404 286 "-" "ELB-HealthChecker/1.0"
- 10.29.36.216 - - [03/Nov/2012:12:24:15 +0000] "GET /ping.html HTTP/1.1" 404 286 "-" "ELB-HealthChecker/1.0"

Let's see what happens soon after.

# as-describe-auto-scaling-instances
INSTANCE  i-5bb9e427  grupo-prueba  us-east-1a  InService  HEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-5bb9e427  grupo-prueba  us-east-1a  InService  HEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-5bb9e427  grupo-prueba  us-east-1a  InService  HEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-5bb9e427  grupo-prueba  us-east-1a  InService  UNHEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-5bb9e427  grupo-prueba  us-east-1a  InService  UNHEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-5bb9e427  grupo-prueba  us-east-1a  Terminating  UNHEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-5bb9e427  grupo-prueba  us-east-1a  Terminating  UNHEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-5bb9e427  grupo-prueba  us-east-1a  Terminating  UNHEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-5bb9e427  grupo-prueba  us-east-1a  Terminating  UNHEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-3dce9341  grupo-prueba  us-east-1a  Pending      HEALTHY    config-prueba
INSTANCE  i-5bb9e427  grupo-prueba  us-east-1a  Terminating  UNHEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-3dce9341  grupo-prueba  us-east-1a  Pending      HEALTHY    config-prueba
INSTANCE  i-5bb9e427  grupo-prueba  us-east-1a  Terminating  UNHEALTHY  config-prueba  
# as-describe-auto-scaling-instances
INSTANCE  i-3dce9341  grupo-prueba  us-east-1a  Pending  HEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-3dce9341  grupo-prueba  us-east-1a  Pending  HEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-3dce9341  grupo-prueba  us-east-1a  Pending  HEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-3dce9341  grupo-prueba  us-east-1a  InService  HEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-3dce9341  grupo-prueba  us-east-1a  InService  HEALTHY  config-prueba
# as-describe-auto-scaling-instances
INSTANCE  i-3dce9341  grupo-prueba  us-east-1a  InService  HEALTHY  config-prueba

After a while, our initial instance (i-5bb9e427) is declared UNHEALTHY and put to Termination due the Test Page fails several times. At the same time a new instances is deployed (i-3dce9341), tested and aggregated to the ELB to match our "minimum=1" criteria. Auto Scaling (together with ELB) monitors our cluster and any failed instance will be removed and a new one will be launched.

We have learned something here: An instance in an AS environment is volatile. It could disappear at any time because it is Terminated and with the instance its EBS volumes. You have to take that into account when designing your infrastructure. If your web server needs to store some information that you could need later you should save it elsewhere: Cloudwatch, external log server, data base, etc.


Maneuvers:

Changing the minimum number of instances in the AS configuration is a way to change the amount of running instances but there are others.

- We can force the number of running instances by changing the "--desired-capacity" in the AS Group definition:

as-update-auto-scaling-group grupo-prueba --desired-capacity X 

- You can scale by Schedule: AWS Scaling by Schedule documentation.

- And you can scale by Policy. This aspect is covered in this post: AWS EC2 Auto Scaling: External CloudWatch Metric.


Cleaning:

You don't want an AS Group doing things while you sleep so I suggest you to delete all your AS configurations after your test is done.

# as-update-auto-scaling-group grupo-prueba --min-size 0

OK-Updated AutoScalingGroup

# as-update-auto-scaling-group grupo-prueba --desired-capacity 0

OK-Updated AutoScalingGroup

# as-delete-auto-scaling-group grupo-prueba


    Are you sure you want to delete this AutoScalingGroup? [Ny]y

OK-Deleted AutoScalingGroup


# as-delete-launch-config config-prueba


    Are you sure you want to delete this launch configuration? [Ny]y  

OK-Deleted launch configuration


# as-describe-auto-scaling-instances 

No instances found

Tuesday, October 30, 2012

AWS API How To

Dealing with the Amazon Web Services API could be frustrating for a beginner. Here you are a small example that will help you to start with.

Some concepts:
The AWS API is a resource that could be accessed from everywhere by an authenticated application to manage all kind of elements into the AWS infrastructure. You can create a new EC2 instance, manage the contents of your S3 Bucket, modify an alarm in Cloudwatch, etc. (the "programmable data center" concept). You could either create your own application to interact with the AWS API (example: create and Smartphone App to Start/Stop your EC2 instances) or you could use someone else application to do that (That's what I do). Amazon Web Services provides a convenient ready-to-use command line tools to use their API.
There are different API methods inside the AWS cloud and different methods of authentication. Currently, the official way to authenticate is using you Access Key and the Secret Key and the Certificate authentication is now obsolete.
By default all API calls are directed to the us-east-1 Region (N.Virgina).


First Step:
Deploy an EC2 instance using the Amazon Linux AMI. The basic AWS Linux AMI includes command line tools to interact with the previous mentioned APIs (and others). You still have the option to download those command line tools and use them from your laptop but to use this preconfigured AMI is the easiest way to start.

This is a list of the current APIs included in EC2 Amazon Linux and its versions:

$ ssh -i juankeys.pem ec2-user@ec2-50-16-155-40.compute-1.amazonaws.com
Last login: Tue Oct 30 10:25:19 2012 from 28.red-28-28-28.adsl.static.ccgg.telefonica.net
       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|
https://aws.amazon.com/amazon-linux-ami/2012.09-release-notes/

$ sudo -i

# ll /opt/aws/apitools/
total 36
lrwxrwxrwx 1 root root   11 Oct 25 18:51 as -> as-1.0.61.1
drwxr-xr-x 4 root root 4096 Oct 25 18:51 as-1.0.61.1
lrwxrwxrwx 1 root root   22 Oct 25 18:51 cfn-init -> ./cfn-init-1.3-6.amzn1
drwxr-xr-x 5 root root 4096 Mar 24  2012 cfn-init-1.1-0.amzn1
drwxr-xr-x 5 root root 4096 Oct 25 18:51 cfn-init-1.3-6.amzn1
lrwxrwxrwx 1 root root   11 Oct 25 18:51 ec2 -> ec2-1.6.3.0
drwxr-xr-x 4 root root 4096 Oct 25 18:51 ec2-1.6.3.0
lrwxrwxrwx 1 root root   12 Oct 25 18:51 elb -> elb-1.0.17.0
drwxr-xr-x 4 root root 4096 Oct 25 18:51 elb-1.0.17.0
lrwxrwxrwx 1 root root    9 Oct 25 18:51 iam -> iam-1.5.0
drwxr-xr-x 4 root root 4096 Oct 25 18:51 iam-1.5.0
lrwxrwxrwx 1 root root   12 Oct 25 18:51 mon -> mon-1.0.13.4
drwxr-xr-x 4 root root 4096 Oct 25 19:45 mon-1.0.13.4
lrwxrwxrwx 1 root root   12 Oct 25 18:51 rds -> rds-1.10.003
drwxr-xr-x 4 root root 4096 Oct 25 18:51 rds-1.10.003
lrwxrwxrwx 1 root root   14 Oct 25 18:51 ses -> ses-2012.07.09
drwxr-xr-x 3 root root 4096 Oct 25 18:51 ses-2012.07.09


Credentials for EC2 API command line tools:
(Logged with root) Export the variables AWS_ACCESS_KEY and AWS_SECRET_KEY with your credentials and test the configuration with a simple EC2 command like ec2-desbribe-regions The Access Key and the Secret Key are obtained when you create a new user using the IAM console. You have an example of creating a new user in this article. Please note you will need a user with admin privileges to interact with AWS API.

# export AWS_ACCESS_KEY=(your access key without parentheses)
# export AWS_SECRET_KEY=(your secret key without parentheses)

# ec2-describe-regions
REGION eu-west-1 ec2.eu-west-1.amazonaws.com
REGION sa-east-1 ec2.sa-east-1.amazonaws.com
REGION us-east-1 ec2.us-east-1.amazonaws.com
REGION ap-northeast-1 ec2.ap-northeast-1.amazonaws.com
REGION us-west-2 ec2.us-west-2.amazonaws.com
REGION us-west-1 ec2.us-west-1.amazonaws.com
REGION ap-southeast-1 ec2.ap-southeast-1.amazonaws.com 


Credentials for Auto Scaling, Cloudwatch, RDS and ELB API command line tools:
- Create a text file with this name and path: /opt/aws/apitools/mon/credential-file-path.template with the following contents:

AWSAccessKeyId=(your access key without parentheses)                      
AWSSecretKey=(your secret key without parentheses)

- Prevent other users from reading it:

# chmod go-rwx /opt/aws/apitools/mon/credential-file-path.template

# ll /opt/aws/apitools/mon/credential-file-path.template
-rw------- 1 root root 91 Oct 25 19:45 /opt/aws/apitools/mon/credential-file-path.template

- Export the AWS_CREDENTIAL_FILE variable with the file location:

export AWS_CREDENTIAL_FILE=/opt/aws/apitools/mon/credential-file-path.template

- And test the configuration with some simple commands like as-describe-scaling-activities, mon-list-metrics, elb-describe-lbs and rds-describe-db-engine-versions:

# as-describe-scaling-activities
ACTIVITY  fddbfad9-3383-4cdd-bbaa-fb843ff1141a  2012-10-29T14:30:50Z  grupo-prueba  Successful
ACTIVITY  02ff2071-1ec5-45c4-936d-76620a8ff0b0  2012-10-29T13:57:28Z  grupo-prueba  Successful
ACTIVITY  a18a5ce2-c28f-4531-abe6-6bde9d3713fd  2012-10-29T13:57:13Z  grupo-prueba  Successful
ACTIVITY  320d8cf7-adab-4085-becf-25fbd29d89ee  2012-10-29T13:43:49Z  grupo-prueba  Successful

# mon-list-metrics | head
"             AutoScalingGroupName             grupo-prueba        
"             AutoScalingGroupName             grupo-prueba        
"             AutoScalingGroupName             grupo-prueba        
"             AutoScalingGroupName             grupo-prueba        
"             AutoScalingGroupName             grupo-prueba        
"             AutoScalingGroupName             grupo-prueba                  "    

# elb-describe-lbs
LOAD_BALANCER  domenech    domenech-1821931935.us-east-1.elb.amazonaws.com   2012-05-31T15:16:17.630Z  internet-facing
LOAD_BALANCER  elb-prueba  elb-prueba-926661513.us-east-1.elb.amazonaws.com  2012-10-29T12:49:17.750Z  internet-facing

# rds-describe-db-engine-versions | head
VERSION  mysql          5.1.45            mysql5.1            MySQL Community Edition                  MySQL 5.1.45                                
VERSION  mysql          5.1.49            mysql5.1            MySQL Community Edition                  MySQL 5.1.49                                
VERSION  mysql          5.1.50            mysql5.1            MySQL Community Edition                  MySQL 5.1.50                                
VERSION  mysql          5.1.57            mysql5.1            MySQL Community Edition                  MySQL 5.1.57                                
VERSION  mysql          5.1.61            mysql5.1