I’ve been working a bit on streamlining the builds I do on EC2 and wanted a better way to provision my machines. I use CentOS and things are pretty rough to non existent for nicely built EC2 images. I’ve used the Rightscale ones till now and while they’re nice they are also full of lots of code copyrighted by Rightscale.
What I really wanted was something as full featured as Ubuntu’s CloudInit but also didn’t feel much like touching any Python. I hacked up something that more or less do what I need. You can get it on GitHub. It’s written and tested on CentOS 5.5.
The idea is that you’ll have a single multi purpose AMI that you can easily bootstrap onto your puppet/mcollective infrastructure using this system. Below for some details.
I prepare my base CentOS AMI with the following mods:
- Install Facter and Puppet – but not enabled
- Install the EC2 utilities
- Setup the usual getsshkeys script
- Install the ec2-boot-init RPM
- Add a custom fact that reads /etc/facts.txt – see later why. Get one here.
With this in place you need to create some ruby scripts that you will use to bootstrap your machines. Examples of this would be to install mcollective, configure it to find your current activemq. Or to set up puppet and do your initial run etc.
We host these scripts on any webserver – ideally S3 – so that when a machine boots it can grab the logic you want to execute on it. This way you can bug fix your bootstrapping without having to make new AMIs as well as add new bootstrap methods in future to existing AMIs.
Here’s a simple example that just runs a shell command:
newaction("shell") do |cmd, ud, md, config| if cmd.include?(:command) system(cmd[:command]) end end |
You want to host this on any webserver in a file called shell.rb. Now create a file list.txt in the same location that just have this:
shell.rb |
You can list as many scripts as you want. Now when you boot your instance pass it data like this:
--- :facts: role: webserver :actions: - :url: http://your.net/path/to/actions/list.txt :type: :getactions - :type: :shell :command: date > /tmp/test |
The above will fetch the list of actions – our shell.rb – from http://your.net/path/to/actions/list.txt and then run using the shell action the command date > /tmp/test. The actions are run in order so you probably always want getactions to happen first.
Other actions that this script will take:
- Cache all the user and meta data in /var/spool/ec2boot
- Create /etc/facts.txt with all your facts that you passed in as well as a flat version of the entire instance meta data.
- Create a MOTD that shows some key data like AMI ID, Zone, Public and Private hostnames
The boot library provides a few helpers that help you write scripts for this environment specifically around fetching files and logging:
["rubygems-1.3.1-1.el5.noarch.rpm", "rubygem-stomp-1.1.6-1.el5.noarch.rpm", "mcollective-common-#{version}.el5.noarch.rpm", "mcollective-#{version}.el5.noarch.rpm", "server.cfg.templ"].each do |pkg| EC2Boot::Util.log("Fetching pkg #{pkg}") EC2Boot::Util.get_url("http://foo.s3.amazonaws.com/#{pkg}", "/mnt/#{pkg}") end |
This code fetches a bunch of files from a S3 bucket and save them into /mnt. Each one gets logged to console and syslog. Using this GET helper has the advantage that it has sane retrying etc built in for you already.
It’s fairly early days for this code but it works and I am using it, I’ll probably be adding a few more features soon, let me know in comments if you need anything specific or even if you find it useful.