Crafting the perfect development environment is difficult. Reproducing this environment for every member of your team, across development machines, running on different platforms and filled up with dependencies from previous projects is even harder. When you add staging and production environments into the mix, the level of subtle inconsistency is normally quite high, even if you're being careful.
Divergence in configuration is subtle, yet evil, and will breed bugs which slow down your development process, and reduce the confidence you can have in the finished product.
Enter Vagrant, a lightweight command-line wrapper around VirtualBox which can spin up the perfect isolated development/test environment for your projects in a single command. This environment is always consistent, can be destroyed and rebuilt at any time, and used by any member of your team, on any of the major platforms.
Thanks to our adoption of Vagrant, picking up any existing project at Red Badger is as simple as a 'git clone' and a 'vagrant up'. Deployment of the same project is equally simple, executing an ./ec2-package bash script and copying the deployment package to a cloud instance of our choosing.The small initial investment of time at the start of each project is repaid over and over during the course of development.
The key to Vagrant is the Vagrantfile, this is a Ruby file which sits in your project root and allows you to specify your perfect environment declaratively.
- A web server running Nginx on port 80 which reverse proxies requests to Node.js, on port 3000
- A database server which runs MongoDB and Redis
Each 'config.vm.define' block specifies a virtual machine, with an arbitrary name, based on an Ubuntu Precise 64 box we downloaded automatically from vagrantup.com. This base box contains the VirtualBox Additions so we don't have to do this ourselves.
Each 'add_recipe' declaration is referring to a Chef recipe, explained in the next section.
Chef is the embodiment of a principle known as 'Infrastructure as Code', it is a configuration management tool which can be used to script your specific infrastructure requirements flexibly and maintainably. It again uses a Ruby based DSL to specify these requirements (notice a trend here?).
Chef itself comes in two forms: Chef Solo, and Chef Server. I will only discuss Chef Solo in this post, but Chef Server is definitely worth a look if you have more complex requirements (we typically don't).
The core principle of Chef is the cookbook; a cookbook specifies the configuration of a piece of software - How to obtain the latest version of it, how to configure it, and how to start it up. Cookbooks can be broken down into recipes, where software has distinct setup paths. Eg. a MySQL master vs a slave.
Chef recipes are easy to understand and generally platform independent and maintainable, but can be time consuming to write. Luckily you can build upon the hard work already done by the Opscode community and download pre-written cookbooks for popular open source packages, and modify these to suit your own needs.
Vagrant + Chef Integration
Vagrant provides the ideal testbed for Chef cookbooks. Vagrant VM's are lightweight and can be destroyed and recreated at any time. We often destroy and recreate VM's iteratively until they are absolutely perfect. At this point, the rest of the team can get on board or we can use these scripts to provision instances in Amazon's EC2, for example.
In our Vagrantfile we have specified chef_solo as the provisioning tool. By default, chef_solo cookbooks are stored in the /cookbooks directory relative to the Vagrantfile.
In our cookbooks directory we have the following cookbooks:
Most of these cookbooks were downloaded from Opscode and tweaked to our individual requirements. After a few iterations of 'vagrant up' and 'vagrant destroy', we have a configuration we are satisfied with. We can now gain shell access to our brand new VM with the command 'vagrant ssh'. Execution of the code is all performed inside this SSH session throughout the course of development.
From Chef to EC2 and beyond
One major benefit of Infrastructure as Code is the ability to transition your infrastructure to the cloud rapidly and consistently. We do this at the beginning of most projects to set up a staging environment, and nearer the end of the project for when it's ready to go live.
We use a very simple technique to provision servers on EC2. It works because we don't typically have very large deployments involving a large number of instances. For these more complex situations, I would recommend the use of Chef server.
We simply added a 'package_for_ec2' method to our Vagrantfile to export our cookbooks and run_list as JSON (remember it's just Ruby!).
We call 'package_for_ec2' from every instance declaration, this will produce a .json file for each instance. We SCP this file along with the cookbooks and an install shell script up to /tmp on a new EC2 server, and then execute the command: ec2-provision.sh
Our ec2-provision shell script looks like this:
When this finishes executing, we have an EC2 instance with an identical configuration to our local setup. Easy.
Within the last few days, Vagrant 1.1 has been announced which packages a built in AWS provisioning plugin. I'm sure we will switch to this form of EC2 provisioning when Vagrant 1.1 is released as it looks fantastic. See the screencast here: http://www.hashicorp.com/blog/preview-vagrant-aws.html
Also related, Amazon have just announced AWS OpsWorks, which brings official support for provisioning EC2 instances with Chef cookbooks, this is a sign that Chef is starting to gain real traction amongst the DevOps community.