AWS – Elastic Load Balancers and Cloudforms

First entry in ages, but those who know me will have seen I have been visiting a few places around the globe. Anyway, I promised some time ago the ability to add Amazon EC2 Instances to Amazon Elastic Load Balancers, here it is.

Problem was that whilst I have spent a couple of hours here and there on airplanes recently, its still quite difficult to connect to AWS from them! So any work on this had to wait until airport lounges. Its done now so here is the entry for it.

We start with the design, I wanted to deliver this as something that could be re-used and eat my own dog food when it comes to state-machines. Also because a load balancer best works with more than one workload the result of this was always going to include a n-tier service of instances. The following diagram show the high level design to the service.

Slide1

It is a service bundle that consists of two service items, each service item has some sort of service resource to it. The next diagram shows the actual service I am delivering in context to the first diagram.

Slide2

My service has a service bundle called “N-Tier AWS”, that has two service items called “Web Servers” and “Load Balancer”. The “Web Servers” delivers two AWS EC2 instances named “Web Server 1” and “Web Server 2”. There is nothing special about “Web Servers”, it’s a standard service item of AWS EC2 resources (AMI’s). The second service item is the new one I am blogging about. This is a generic item that connects to its own State Machine through a relationship connection. For this I had to edit the class of the out of the box state machine. Why? Because I wanted to nest my StateMachine with that of the out of the box one. Here is the change I made;

Edited /Factory / StateMachines / ServiceProvision_Template /

And added to the top of this class the following Field

  • Field Name – rel1
  • Field Type – Relationship

Accept all other defaults for the field.

It should look something like;

Screen Shot 2014-03-04 at 15.15.43

With the class change made, we can write into this relationship the call to the new statemachine. This means when ever you want to add the capability of AWS EC2 load balancers, all you need to do is wire the statemachine for loadbalancers to your service bundle state machine.

The state machine which is the download-able part of this, looks something like; Its simplicity is its power! If the state machine errors then we shall execute an error method called error_nlb otherwise, on entry to the state machine we shall run a method called add_to_nlb.

Screen Shot 2014-03-04 at 15.33.09

Download the state machine here -: https://github.com/jonnyfiveiq/CloudFORMSNOW/blob/master/Integrations/Provisioning/AWS_ELB/cfnow-aws-elb.xml

This will create a new namespace and class as follows;

Screen Shot 2014-03-04 at 16.09.57

You are then free to wire in my state machine to your service state machine, looking something like;

Screen Shot 2014-03-04 at 16.11.49

The service bundle is constructed with what ever instances you was instances are, in my case web server_a and web server_b , then a 3rd service item being the loadbalancer state machine, so this is a generic service item. Here is a screen shot of my bundle resources!

Screen Shot 2014-03-04 at 16.12.49

There service bundle detail is as follows, remember to create your own new state machine for the bundle and not just use default! In summary the state machine that this bundle points to does nothing at all other that what is configured in default!

Screen Shot 2014-03-04 at 16.12.41

And finally the generic item that is “AWS Load Balancer” and resides in automation location “/Factory/StateMachines/ServiceProvision_Template/aws_loadbalancers” is the state machine you will import from GitHub url on this page, here is the generic service item detail;

Screen Shot 2014-03-04 at 16.13.02

Notice no dialog, just the state machine to be called.

The Loadbalancer StateMachine

Some design considerations needed to be taken into account, for example when the instances first spin up will not immediately have IP addresses, the status checks will not be complete, as show here;

Screen Shot 2014-02-28 at 17.33.52

So we need to wait for the instances to be come available, the best way for this is to allow Cloudforms to tell the statemachine when an IP address turns up for each service resource in the service bundle. Now we can not actually have the message come to us, so we use the statemachine re-entrancy concept. Sounds complicated by easy to explain. We simply launch our loadbalncer statemachine at the same time as the AWS EC2 Instance statemachine. On initial run of the load balancer statemachine we check to the service resources for ip addresses, if blank we then put the load balancer statemachine into retry mode, with an interval of 30 seconds. The AWS EC2 instance statemachine will continue to process the service resources and insatiate the instances, 30 seconds later our loadbalancer statamachine will re-enter and run again. Repeating the same initial tests, if the instances now have IP addresses then the instances will be added to a load balancer, if the instances are still without IP addresses as with the first run, the loadbalancer statemachine will go into retry mode again.

The other features of the statamachine are to read what the user selected when ordering the service bundle. There are three parameters, shown here in the graphic;

Screen Shot 2014-02-28 at 16.55.59

The 3 elements we need are;

  • Elastic Load Balancer – Check Box
  • Elastic Load Balancer Name – Text Box
  • Existing Elastic Load Balancer – Dynamic Drop Down

The user must first select “Elastic Load Balancer – Check Box “ to enable our loadbalancer statemachine. Once this is enabled the user can choose either “Elastic Load Balancer Name – Text Box” to create a NEW elastic load balancer or select from the “Existing Elastic Load Balancer – Dynamic Drop Down” an existing Elastic Load Balancer. The dynamic drop down has behind it a method to populate the drop down with load balancers from a specified AWS account.

StateMachine Method Walk-Through

First we start with my bad habit of  “We are here” really loudly! Actually when I first wrote this method, I pumped my log entries to a completely new log file. It made it easy to debug whilst two statemachine are running synconlosy one of them delivering 2 instances or more, it can be tricky to read the automate log file real time.

As part of the starting code we have the gem statement, we are using the out of the box ruby gem for AWS-SDK, this is an awesome gem providing huge amounts of functions covering both S3 and EC2.

</pre>
require 'aws-sdk'

$evm.log("info", "------------------------------ADD TO AWS LOAD BALANCER------------------------------n")

Next we have a section of code that deals with setting up the objects;

</pre>
stp_task = $evm.root["service_template_provision_task"]

miq_request_id = $evm.vmdb('miq_request_task', stp_task.get_option(:parent_task_id))

dialogOptions = miq_request_id.get_option(:dialog)

$evm.log("info", "ELB New ELB Name --> #{dialogOptions['dialog_elbName)']}n")

$evm.log("info", "ELB Existing ELB --> #{dialogOptions['dialog_existingELB']}n")

$evm.log("info", "Add to ELB Enabled --> #{dialogOptions['dialog_elb']}n")

miqr = $evm.vmdb('service', stp_task.get_option(:parent_service_id))

Line 1, “stp_task”, is fetching the service_template_provision_task from the current statemachine. This would result in the service_template_provision_task for loadbalancers, which is not very useful to us by itself because its not the loadbalancers that have the detail about what is going to be insatiated, but the other service in the service bundle. Also the loadbalancer service did not collect the user input either that was the bundled server so Line 2, “miq_request_id” fetches from the VMDB the bundle service instance, the subsequent lines then extract from the bundle service the dialog options that were set. Lastly we fetch the live service instance for the bundle, this holds a very special helper attribute that will be populated by cloudforms as new VM’s are provisioned called “VMS”, at start its value is nil, but as instances are provisioned their object references are placed into an array in this attribute.

</pre>
i = 0

unless miqr.vms.nil?

miqr.vms.each do |vm|

i = i.to_i + 1

$evm.log("info", "------------ VM #{i} ----------n")

$evm.log("info", "Instance Name --> #{vm.name}n")

$evm.log("info", "Instnace ID --> #{vm.ems_ref}n")

$evm.log("info", "DNS Name --> #{vm.location}n")

$evm.log("info", "PowerState --> #{vm.power_state}n")

$evm.log("info", "Active --> #{vm.active}n")

instanceA << {:instance_id => vm.ems_ref}

$evm.log("info", "------------ VM #{i} ----------n")

end

end

This code is fairly simple,  mainly logging so I can see the instances as they turn up, but most importantly its adding them to a new Ruby hash called instance as they turn up. Here is the segment that reports the number of instances (VMs) in the miqr objects (Service Request) vs that of the provisioned instances (isntanceCount)

</pre>
vmCount = miqr.vms.count

instanceCount = instanceA.size

$evm.log("info", "VMs Count --> #{vmCount}n")

$evm.log("info", "instanceA Count --> #{instanceCount}n")

Now we start the if block, to simply understand “did the user enable ELB?” and if they did, “did they want a new one or use an existing ELB”?

First if is “Have we the same number of instances as requested instances live and running?

</span>

if vmCount > 0 && vmCount = instanceCount

if dialogOptions['dialog_elb'].to_s == "1"

Now we deal with new vs old ELB,


if dialogOptions['dialog_existingELB'] != " None"
 $evm.log("info", "Using Existing ELB #{dialogOptions['dialog_existingELB']}n")
 elbName = dialogOptions['dialog_existingELB']
 elsif dialogOptions['dialog_elbName'] != ""
 $evm.log("info", "Creating NEW ELB #{dialogOptions['dialog_elbName']}n")
 elbName = dialogOptions['dialog_elbName']

#create the ELB

else
 $evm.log("info", "Neither New ELB or Existing ELB was set!n")
 end

and finally the bit that actually adds the instances to the ELB.


$evm.log("info", "Using ELB #{elbName}n")
 AWS.config(:access_key_id => '<your_user>', :secret_access_key => '<your_key>')

ec2 = AWS::EC2.new
 elb = AWS::ELB.new

miqr.vms.each do |vm|
 $evm.log("info", "Adding #{vm.ems_ref} to ELB #{elbName}n")
 i = ec2.instances["#{vm.ems_ref}"]
 elb.load_balancers["#{elbName}"].instances.register(i)
 end
 end

I do admit I took a short cut in hardcoding the authentication details in the method, you should do better and get these from the provider!

Now here is the part of the method thats actually more important than adding the instances to the ELB! The re-entrancy part of the method.


else
 passNo = stp_task.get_option(:pass)
 passNo = passNo.to_i + 1
 stp_task.set_option(:pass, "#{passNo}")
 $evm.log("info", "Pass Number #{passNo}n")
 $evm.root['ae_result'] = 'retry'
 $evm.root['ae_retry_interval'] = '30.seconds'
 $evm.log("info", "-------------------RETRY-----------------------n")

exit MIQ_OK
end

Notice that in the block, we have check previously should we be here, else do this. Then we set the “ae_result” to retry and “ae_retry_interval” to every 30 seconds. This means that when we exit this method state, the state machine will put this into retry 30 seconds later, and we do the whole method again and again until we pass the check of should we be here or not, and add the instances to the ELB.

User Experience

Here is what the consumers or users would see when ordering an item in Cloudforms with ELB supports.

1. Select the item from the catalog

Screen Shot 2014-02-28 at 16.55.23

2. Complete the dialog for the catalog item, selecting the ELB configuration. (ignore the other detail to this dialog – that may form part two of this demo!)

Screen Shot 2014-02-28 at 16.55.38

3. Request enters queue.

Screen Shot 2014-02-28 at 16.58.19

Now if we have a look at AWS, (Not that we need to as we have Cloudforms!!!) The ELB’s during provisioning look empty;

Screen Shot 2014-02-28 at 16.57.11

And when Cloudforms has finished provisioning the instances, and added them to the ELB, AWS lists them as so;

Screen Shot 2014-03-04 at 15.29.30

This concludes this post, I hope its of value! Remember that the design goal here was to create a state machine that could be easily hooked to any AWS based service to provide ELB support.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s