Lately, at the office, my team and I were willing to improve our release process and reach Continuous Delivery state.
So automating our application’s infrastructure (1 database, 2 appservers, 1 search engine, 1 proxy) meant:
- automating the database migration: index, column, constraints, table, view, data, etc,
- automating the webapp deployment: the easy part,
- automating the search engine migration: aliases, indices, mappings, full reindex, partial reindex.
- automating the proxy configuration: register/unregister members.
When you add a Zero Downtime (french blog entry with english references) constraint you’re basically left with 2 choices:
- act on the existing cluster: migrate database, then index, then sequentially migrate the webapps. This technique is known as the Blue Green Deployment. This technique works as soon as versions N+1 and version N are fully compatible. You must maintain at least 2 versions of your code until version N is no longer used. Only then will you be able to cleanup what’s remaining of the version N in your code.
- act on a brand new cluster: reproduce an identical cluster, populate the new database and the new index with the current data, then switch to the new cluster (Immutable server à la Netflix). A little easier as you don’t have to maintain 2 versions of codebase. You still need to write search engine and database migration scripts.
While the first method is used pretty often, the second requires more recent tools and skills (mostly ops ones) making most dev teams uncomfortable with them. Should they really be? Being able to reproduce identical environments at will really looked like magic to me just 3 weeks ago (I have a strong java background). I got dragged into the huge but totaly addictive automation and configuration management ecosystem. I chose to give Chef and Vagrant a try to build the above cluster while using my TDD knownledge.
Before coding let’s set clear goals:
- Iteration 1: environment setup. Install ruby, virtualbox, vagrant and various vagrant plugins
- Iteration 2: proof of concept. Create database server in test first
- Iteration 3: refactor. Chef cookbooks and foodcritic (cookbook lint tool)
1. Install Ruby runtime
I will only cover linux install because covering other envs is not the purpose of the blog.
$ sudo aptitude upgrade $ sudo aptitude install ruby1.9.3 # check version $ ruby --version ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-linux]
2. Install VirtualBox
Avoid system packages because they are not compatible with vagrant latest version. Install the latest debian package from the website with dpkg.
$ wget http://download.virtualbox.org/virtualbox/4.2.16/virtualbox-4.2_4.2.16-86992~Ubuntu~raring_amd64.deb -O /tmp/virtualbox-4.2_4.2.16-86992~Ubuntu~raring_amd64.deb $ sudo dpkg -I /tmp/virtualbox-4.2_4.2.16-86992~Ubuntu~raring_amd64.deb $ vboxmanage --version 4.2.16r86992
3. Install Vagrant
$ wget http://files.vagrantup.com/packages/7ec0ee1d00a916f80b109a298bab08e391945243/vagrant_1.2.7_i686.deb -O /tmp/vagrant_1.2.7_i686.deb $ sudo dpkg -I /tmp/vagrant_1.2.7_i686.deb $ vagrant --version Vagrant version 1.2.2
4. Get familiar with Vagrant
Vagrant will help you create/provision/reload and destroy your machines. It’s a wrapper around virtualization tools such as virtualbox, vmware and lxc/docker. Using vagrant abstracts your from the underlying tool specifics. This is really great because switching provider becomes quite cheap.
Create a vagrant project: create an empty server and start it
# Create your project $ vagrant init ubuntu-server-12.10 http://goo.gl/wxdwM # Note that the server is not running with vagrant status $ vagrant status # Now start the server. The first time it might take some time depending on your connection because vagrant downloads the image and virtualbox is not really fast to boot $ vagrant up
The init command declares the server name and the url where to find the image.
The up command downloads the image and starts the virtual machine.
Log into the machine
# Note that your server is now running $ vagrant status # Log into your machine, you can run any valid linux command $ vagrant ssh # Logoout then destroy the server $ vagrant destroy -f
So far so good, we can create a server from scratch, run it and destroy it. That won’t take us that far. We need to provision it with services, modify the services default config then restart the services. That’s where chef comes into play.
Chef, like puppet or fabric, helps with these configuration steps. Chef uses plain ruby, puppet uses a ruby dsl and you can also use native ruby in later versions, fabric uses python. I chose chef because I’m already familiar with cucumber and plain ruby. So it seemed easier to me at first glance but I really think they are equivalent for my need.
For an exhaustive feature list either run vagrant –help or read the documentation
5. Get familiar with Chef
Let’s provision our server with the curl package.
Previously, the vagrant init created this Vagrant file:
Vagrant.configure("2") do |config| config.vm.box = 'ubuntu-server-12.10' config.vm.box_url = 'http://goo.gl/wxdwM' end
The following lines instruct vagrant to use chef-solo as a provisioner:
Vagrant.configure("2") do |config| config.vm.box = 'ubuntu-server-12.10' config.vm.box_url = 'http://goo.gl/wxdwM' config.vm.provision 'chef_solo' do |chef| chef.add_recipe 'curl' end end
Chef runs in 2 modes:
- chef solo: easy to use. From the client perspective it’s a push mode (like ansible)
but doesn’t fit in production as it’s a one shot run. recommended for academic goals.
- chef server + chef client: the server which acts as a reference and the clients who update their configuration according to the server’s.
suitable for a production environment. From the client perspective it’s a pull mode
Neither is better. it’s just a matter of flavor. Ansible for instance does a terrific job and is agentless.
If you run the above code ‘as is’ it won’t work because your virtual machine has no knowledge of chef. Either you use a box with chef preconfigured or you use Omnibus. This tool takes care of downloading your desired version of chef and installs it. The vagrant-omnibus plugin will ship omnibus into your vm then run it prior to any chef code. To install vagrant omnibus plugin run
$ vagrant plugin install vagrant-omnibus
Then enable it your Vagrantfile:
config.omnibus.chef_version = :latest
Guess what? If you run vagrant up it still won’t work because your server will find chef but will complain about not finding curl cookbook. You have to tell chef where to find cookbooks. The community writes many cookbooks but most of them are written by Opscode and made available on github.
Berkshelf fetches cookbooks from various places (be they remote or local) and makes them available to your local chef. The vagrant berkshelf plugin uploads them to your virtual machine.
Now you get it, installing vagrant-berkshelf is as easy as:
$ vagrant plugin install vagrant-berkshelf
Then edit a file named Berksfile next to your Vagrantfile and declare the curl cookbook:
cookbook 'curl', :git => 'https://github.com/retr0h/cookbook-curl.git'
Finally don’t forget to enable it in your Vagrantfile:
config.berkshelf.enabled = true
You should now be able to successfully run your machine and check that curl is installed.
$ vagrant reload [2013-08-23T13:04:45-03:00] INFO: Chef Run complete in 4.347576576 seconds [2013-08-23T13:04:45-03:00] INFO: Running report handlers [2013-08-23T13:04:45-03:00] INFO: Report handlers complete # Note the support of remote commands: awesome $ vagrant ssh -c 'dpkg -L curl'
That’s it for the 1st iteration. We learned how to create an empty machine with vagrant and to provision if with chef solo.
The next iteration will tell more about testing chef execution and configuring a cookbook for our needs.