Agile Zone is brought to you in partnership with:

Entrepreneur. Creator of Groovy++ Alex is a DZone Zone Leader and has posted 31 posts at DZone. You can read more from them at their website. View Full User Profile

Cheapest possible EC2 instances with Groovy++

05.18.2010
| 9615 views |
  • submit to reddit

Cloud computing is really hot topic now. The promise of no capital investment in to computing infrastructure and paying for only what you actually use is really big thing. More and more companies (especially startups and small entities) either consider or already moving their projects on to a cloud. And of course Amazon EC2 is pioneer and real leader on this space.

But even when you pay "as you go" you want to have as small bill as possible. Today, I want to share with one trick used by my company MBTE Sweden AB (the company behind Bindows Framework and Groovy++ Programming Language) to minimize our expences on virtualized infrastructure.

Thechnically the article consist of two parts. First we describe the business idea and then provide Groovy++ script, which implements it.

The idea is based on choosing most optimal from three options to purchase computing power from Amazon EC2. The options are following

  • regular instance (no commitment, pay per hour as you go)
  • reserved instance (fixed payment for 1 or 3 years commitment + smaller payment per hour as you go)
  • spot instances (even smaller auction like payments but only if instances are available)
There is one important trick about spot instances

When you purchase Spot Instance you bid for maximal price you are ready to pay per hour. If current spot price, which fluctate periodically based on current demand and available capacity, exceed your bid the instance will be terminated

To feel it better let us analize current pricing for one particular type of instances (so called "m1.large" for Linux/UNIX) The picture will be pretty much the same for other types of instances

Large Instance

7.5 GB memory
4 EC2 Compute Units (2 virtual cores with 2 EC2 Compute Units each)
850 GB instance storage (2×420 GB plus 10 GB root partition)
64-bit platform
I/O Performance: High

As of today the prices are the following:

  • Regular instance - $0.34 per hour
  • Reserved instance - $0.12 per hour + $910 per year(or $1400 for 3 years), which is $0.22 (or $0.17) per hour
  • Spot instances - $0.114 per hour

If we only manage to run our system on spot instances we would pay just one third of regular price.

The important observasion behind our approach is the fact that spot prices are fluctuate a lot but usually in a small range and peaks are not too long and often and never exceed price of regular instance. For example, here is spot price history for "m1.large"

OK, our plan is very simple (in fact, almost trivial) - we always purchase spot instances but with as high maximal price we are ready to pay as price of regular instance.

Now we are ready to switch to technical part of our article and provide simple Groovy++ script, which start given number of spot instances.

Truly speaking, there is nothing really non-trivial in our script. We use brilliant AWS SDK for Java and just use opportunity to demonstrate how amazing expressiveness of Groovy++ helps us in real life

We start with utility method, which checks if security group with given name exists and create new one if needed.

Security group in EC2 is kind of firewall settings. It defines from what addresses we can access instaces belonging to the security group

If we create new group below we allow access from any addresses on the same group and by HTTP & SSHfrom anywhere

    @Typed static void ensureSecurityGroupExists(AmazonEC2Client client, String groupName, String accountId) {
client.with {
def sgs = describeSecurityGroups()
if (!sgs.securityGroups.any { group -> group.groupName == groupName }) {
createSecurityGroup([groupName: groupName, description: "$groupName security group"])
authorizeSecurityGroupIngress([
groupName: groupName,
sourceSecurityGroupName: groupName,
sourceSecurityGroupOwnerId: accountId
])
[22, 80].each { port ->
authorizeSecurityGroupIngress([
groupName: groupName,
ipProtocol: "tcp",
fromPort: port,
toPort: port,
cidrIp: "0.0.0.0/0"
])
}
}
}
}

There are several interesting things to notice about the code above

  1. It is 100% statically typed. We can be sure that compiler understands us and no MissingMethod(Property)Exception willhappen at runtime
  2. Thanks to very powerful Groovy++ type inference we don't need to provide unnecessary type information. In our case just types of method's parameters were enough. All the rest (including type of parameters for method calls and different closures) are deducted by compiler
  3. client.with{...} call magically makes all methods of AmazonEC2Client available inside the closure. In fact, it is not something special about 'with' method but use of some very general technique
  4. [:] notation for creating new typed instances with initialized properties are extremely powerful technique
  5. Groovy++ standard library provides typed versions of classical methods like 'each'/'any' etc.

Now we are ready to write our main method, which starts new spot instances and returns list of created ids.

The general logic is the following

  • we send request to create spot instances
  • we periodically poll EC2 checking if request is already fulfiled
    List<String> startSpotInstances (AmazonEC2Client client, String awsGroup, int requestedInstanceCount, String imageName, String keyPair) {
client.with {
ensureSecurityGroupExists(awsGroup)

def spotInstanceRequestIds = requestSpotInstances([
spotPrice: "0.5",
type: "one-time",
instanceCount: requestedInstanceCount,
launchSpecification: [
imageId:imageName,
keyName:keyPair,
securityGroups: [awsGroup],
instanceType: "m1.large",
placement: [availabilityZone:"us-east-1a"]
]
]).spotInstanceRequests*.spotInstanceRequestId

while(true) {
Thread.currentThread().sleep(20000)

def spotInstanceRequests = describeSpotInstanceRequests([spotInstanceRequestIds:spotInstanceRequestIds]).spotInstanceRequests
if(!spotInstanceRequests.any { it.state != "active"}) {
def instanceIds = spotInstanceRequests*.instanceId
while (true) {
Thread.currentThread().sleep(20000)
def describeInstancesRes = describeInstances([instanceIds:instanceIds])
if(!describeInstancesRes.reservations*.instances.iterator().map{it.iterator()}.flatMap{it}.any{ it.state.name != "running"} ) {
break
}
}

cancelSpotInstanceRequests([spotInstanceRequestIds:spotInstanceRequestIds])
return instanceIds
}
}
}
}

Additionally to all notices above there are few more things to notice about this code

  1. We use static method 'ensureSecurityGroupExists' without specifying first parameter. In fact, we use it as if it was method of AmazonEC2Client. This is particualr case of very powerful technique of extension methods
  2. Spread operator(*.) plays well with Groovy++
  3. Traditional functional methods like 'map'/'flatMap'/etc. also available in Groovy++ standard library

We are done with our main goal. It is not too complicated now to complete it to a full script useful in real production environment.

I hope it was interesting and maybe you will wish to learn more about Amazon and Groovy++

Thank you for reading and till next time

Published at DZone with permission of its author, Alex Tkachman.