As developers, one of the most frustrating types of bugs are those that only happen in production. Continuous integration servers have gone a long way in helping prevent these types of configuration bugs, but wouldn’t it be nice to avoid these bugs altogether?
Developers’ machines tend to be a mess of software. Multiple versions of languages, random services with manually tweaked configs, and debugging tools all contribute to a cluttered and unpredictable environment.
Many developers are familiar with sandboxing development. Tools like virtualenv and rvm were created to install packages in isolated environments, allowing for multiple versions of packages to be installed on the same machine. Newer languages like nodejs install packages in a sandbox by default. These are very useful tools for managing package versions in both development and production, but they also lead to some of the aforementioned clutter. Additionally, these tools are specific to particular languages. They do not allow for easily installing multiple versions of services like databases.
Luckily, there is Vagrant to address all these issues (and a few more).
Vagrant at its core is simply a wrapper around virtual machines. One of Vagrant’s strengths is its ease of use. With just a few commands, virtual machines can be created, provisioned, and destroyed.
First grab the latest download for your OS from Vagrant’s download page (https://www.vagrantup.com/downloads.html).
NOTE: For Linux users, although your distro may have a version of Vagrant via its package manager, it is most likely outdated. You probably want to use the version from the link above to get the latest features.
Also install Virtualbox (https://www.virtualbox.org/wiki/Downloads) using your preferred method. Vagrant supports other virtualization providers as well as docker, but Virtualbox is the easiest to get started with.
When most Vagrant commands are run, they will look for a file named Vagrantfile (or vagrantfile) in the current directory. All configuration for Vagrant is done in this file and it is isolated to a particular Vagrant instance.
Create a new Vagrantfile:
$ vagrant init hashicorp/precise64
This creates a Vagrantfile using the base virtual image, hashicorp/precise64, which is a stock Ubuntu 12.04 image provided by Hashicorp. This file contains many useful comments about the various configurations that can be done. If this command is run with the –minimal flag, it will create a file without comments, like the following:
# -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "hashicorp/precise64" end
Now create the virtual machine:
$ vagrant up
This isn’t too difficult. You now have a nice clean machine in just two commands. What did Vagrant up do? First, if you did not already have the base box, hashicorp/precise64, it was downloaded from Vagrant Cloud. Then, Vagrant made a new machine based on the current directory name and a unique number. Once the machine was booted it created a shared folder between the host’s current directory and the guest’s /vagrant. To access the new machine run:
$ vagrant ssh
At this point, additional software needs to be installed. While a clean Ubuntu install is nice, it does not have the software to develop or run many applications. While it may be tempting to just start installing services and libraries with apt-get, that would just start to cause the same old clutter. Instead, use Vagrant’s provisioning infrastructure. Vagrant has support for all of the major provision tools like Salt (http://www.saltstack.com/), Chef (http://www.getchef.com/chef/) or Ansible (http://www.ansible.com/home). It also supports calling shell commands.
For the sake of keeping this post focused on Vagrant, an example using the shell provisioner will be used. However, to unleash the full power of Vagrant, use the same provisioner used for production systems. This will enable the virtual machine to be configured using the same provisioning steps as production, thus ensuring that the virtual machine mirrors the production environment.
To continue this example, add a provision section to the Vagrantfile:
# -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "hashicorp/precise64" config.vm.provision "shell", path: "provision.sh" end
This tells Vagrant to run the script provision.sh. Create this file with some install commands:
#!/bin/bash # Install nginx apt-get -y install nginx
Now tell Vagrant to provision the machine:
$ vagrant provision
NOTE: When running Vagrant for the first time, Vagrant provision is automatically called. To force a provision, call vagrant up –provision.
The virtual machine should now have nginx installed with the default page being served. To access this page from the host, port forwarding can be set in the Vagrantfile:
# -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "hashicorp/precise64" config.vm.network "forwarded_port", guest: 80, host: 8080 config.vm.provision "shell", path: "provision.sh" end
For the forwarding to take effect, the virtual machine must be restarted:
$ vagrant halt && vagrant up
Now in a browser go to http://localhost:8080 to see the nginx welcome page.
This simple example shows how easy Vagrant is to use, and how quickly machines can be created and configured. However, to truly battle issues like “it works on my machine”, Vagrant needs to leverage the same provisioning tools that are used for production servers. If these provisioning scripts are cleanly implemented, Vagrant can easily leverage them and make creating a pristine production-like environment easy. Since all configuration is contained in a single file, it becomes simple to include a Vagrant configuration along with code in a repository. This allows developers to easily create identical environments for testing code.
When evaluating any open source tool, at some point you need to examine the health of the community supporting the tool. In the case of Vagrant, the community seems very healthy. The primary developer continues to be responsive about bugs and improvements, despite having launched a new company in the wake of Vagrant’s success. New features continue to roll in, all the while keeping a very stable product. All of this is good news since there seem to be no other tools that make creating clean sandbox environments as effortless as Vagrant.
About the author
Timothy Messier is involved in many open source devops projects, including Vagrant and Salt, and can be contacted at [email protected]