Earlier this week I described how I had dipped my toe in the waters of Docker and determined in the end that while the solution was pretty neat, it smacked of being too much solution for the problem at hand.
After the post was published, Twitter user Mark Snow (@highspeedsnow) wondered whether I’d tried Vagrant. Vagrant has been on my “must get round to trying” list, so despite the fact that pretty much any solution will be “too much solution” for the specific use case I had in mind, it seems churlish not to try out another solution that I can, in time, most likely reject as pointless.
Thus, today’s post is on Vagrant. Perhaps you’ll be reading this while you’re lining up at the Apple Store hoping to get your hands on some brand new fruity goodness?
Vagrant vs Docker
I should probably start with a simple explanation of how Vagrant and Docker differ. I failed to explain what Docker / LxC do in my last post, so maybe I can make up for it here instead. Very simply, it’s like this:
Docker is a tool that makes using Linux Containers (LxC) almost bearable. It wraps up lots of functions into nicer front end scripts/tools, it offers integrated access to the Docker Hub (a repository of Docker images), and ultimately makes it so simple to build and run a container that even I can get it working. Seriously. I doubt I could have achieved the same without Docker.
Linux Containers are considered “Virtual Environment” (VM). That is, rather than a virtual machine where the hypervisor emulates hardware from the ground up, a virtual environment runs right on top of the existing host machine’s kernel with a degree of isolation, or containment. The container is really just another application running int the OS but it is contained in terms of kernel namespaces, chroot, control groups and more. In other words, it feels like an isolated environment but it’s actually running on the same shared kernel and hardware as everything else in the underlying OS. You can run many containers within a single OS instance, and that underlying OS can be a Virtual Machine (VM) too, so you can see multiple VMs spawned, each with multiple containers running. Linux Containers are accepted as being much faster than VMs because there’s no emulation layer getting in the way and slowing things down. On the other hand, 100 containers running in an OS are all at the mercy of the single parent OS’ stability.
I don’t think I mentioned in my last post on Docker, but instantiating a container is very fast. On my MacBook Pro, kicking off an ubuntu container takes less than a second before you’re at a bash prompt:
Vagrant is similar to Docker in that it makes using Virtualbox (and other hypervisors) almost bearable. Vagrant has its own repository of images available to use, so you can get started quickly, and it wraps the virtual machine automatically and neatly for you. The virtual machines are lighter than a full VM image, and utilize shared readonly bases to minimize local storage. Note though that ultimately this is a proper virtual machine – Vagrant uses Oracle’s Virtualbox (by default) as the virtualization platform to spin up a new machine. Consequently, spinning up a new ubuntu image (in this case) is much slower than it is with docker:
That’s not to call the startup slow, mind you, but it’s significantly slower in comparison. Note that vagrant automatically mapped an SSH port and makes it easily available using
vagrant ssh – a nice touch. The local directory on the host OS is automatically mapped into the VM as /vagrant, which is another helpful touch as it makes passing data between the two systems effortless. Like Docker, Vagrant helps you build customized environments for your VM by editing a configuration file, and the VM that loads will be based on those requirements. Cool, right?
Both Docker and Vagrant effectively offer me an option to create that consistent shared development environment I’m looking for.
Building A Vagrant Environment
I’d like to build the same environment that I created in docker, but this time using Vagrant, just so I have a comparison of sorts. From what I can see, the Vagrant configuration file supports CFEngine, Puppet and Chef provisioning, but in my case I should be able to just run a script when the image loads. The Provisioning guide in the Vagrant Docs tells me I can achieve this by creating a script in the same directory as the Vagrantfile (which, remember is mapped to /vagrant), and adding a pointer to it in Vagrantfile. So here’s the script mirroring my Docker provisioning, based on the example in the guide (although I am using apt-get to install cpanminus this time as I had some issues using cpan to install it):
#!/usr/bin/env bash # Set stuff up apt-get update apt-get install -y perl apt-get install -y git apt-get install -y make apt-get install -y libyaml-appconfig-perl apt-get install -y build-essential apt-get install -y cpanminus # CPAN Minus cpanm LWP::UserAgent cpanm JSON cpanm JSON::Path cpanm DateTime cpanm MIME::Base64 cpanm Data::Dumper
Now I have to edit the Dockerfile to let it know that this is how I want to provision the image:
Vagrant.configure("2") do |config| # Base image for the VM config.vm.box = "hashicorp/precise32" # Provisioning config.vm.provision :shell, path: "provision.sh" end
The path is assumed to be relative to the Vagrantfile location; in this case the script is in the same directory so no additional path specification is required beyond the filename. Prior to this provisioning, the perl JSON modules are not installed and I see an error if I try to use them:
Welcome to your Vagrant-built virtual machine. Last login: Fri Sep 14 06:22:31 2012 from 10.0.2.2 vagrant@precise32:~$ vagrant@precise32:~$ perl -e "use JSON;" Can't locate JSON.pm in @INC (@INC contains: /etc/perl /usr/local/lib/perl/5.14.2 /usr/local/share/perl/5.14.2 /usr/lib/perl5 /usr/share/perl5 /usr/lib/perl/5.14 /usr/share/perl/5.14 /usr/local/lib/site_perl .) at -e line 1. BEGIN failed--compilation aborted at -e line 1. vagrant@precise32:~$
So now to run the image and see if this all works and my perl modules have been installed as requested. Here’s the initialization for the VM (this is the first time it’s being run):
~ $ vagrant up Bringing machine 'default' up with 'virtualbox' provider... ==> default: Importing base box 'hashicorp/precise32'... ==> default: Matching MAC address for NAT networking... ==> default: Checking if box 'hashicorp/precise32' is up to date... ==> default: Setting the name of the VM: ubuntu-precise32_default_1410976513104_78347 ==> default: Clearing any previously set network interfaces... ==> default: Preparing network interfaces based on configuration... default: Adapter 1: nat ==> default: Forwarding ports... default: 22 => 2222 (adapter 1) ==> default: Booting VM... ==> default: Waiting for machine to boot. This may take a few minutes... default: SSH address: 127.0.0.1:2222 default: SSH username: vagrant default: SSH auth method: private key ==> default: Machine booted and ready! [...] default: VirtualBox Version: 4.3 ==> default: Mounting shared folders... default: /vagrant => /Users/johherbe/vagrant/ubuntu-precise32 ==> default: Running provisioner: shell... default: Running: /var/folders/v5/drjl3mps5n39q6fslzsg4js40000gp/T/vagrant-shell20140917-13503-1uol9ds.sh [...] ==> default: --> Working on Data::Dumper ==> default: Fetching http://search.cpan.org/CPAN/authors/id/S/SM/SMUELLER/Data-Dumper-2.151.tar.gz ... ==> default: OK ==> default: Configuring Data-Dumper-2.151 ... ==> default: OK ==> default: Building and testing Data-Dumper-2.151 ... ==> default: OK ==> default: Successfully installed Data-Dumper-2.151 (upgraded from 2.130_02) ==> default: 1 distribution installed ~ $
Now I’ll SSH into the VM and test again:
~ $ vagrant ssh Welcome to Ubuntu 12.04 LTS (GNU/Linux 3.2.0-23-generic-pae i686) * Documentation: https://help.ubuntu.com/ New release '14.04.1 LTS' available. Run 'do-release-upgrade' to upgrade to it. Welcome to your Vagrant-built virtual machine. Last login: Fri Sep 14 06:22:31 2012 from 10.0.2.2 vagrant@precise32:~$ vagrant@precise32:~$ vagrant@precise32:~$ perl -e "use JSON;" vagrant@precise32:~$
The provisioning only needs to be done once; from now on the VM will skip the provisioning process. Neat.
But Is It Any Good?
Well, Vagrant is, inevitably, slower. But there’s some hope here – vagrant itself was a downloadable installer and I gather it includes virtualbox, so it would be an easy process for my coworker. Plus, because the directory containing Vagrantfile is mapped into /vagrant in the VM, what I can do is to add the specific Vagrantfile and provisioning script into my existing git repository for this project, then when he next syncs the repo he’ll get the Vagrantfile definition and script downloaded to his machine. He can then just run ‘vagrant up’ in that folder and he’ll have a nice (identical) environment ready to roll, and he can access the scripts via /vagrant within the environment.
While slower, this has more potential than containers with Docker not for technical reasons, but simply because this is more easily installed and integrated into git.
Of course, having been treated to sub-second OS startup with Docker, it’s a hard sell to persuade me that a full (even a lightweight) VM could possibly be as cool. More testing required, I suspect!
Meanwhile, I hope that this has proved interesting if these are new technologies to you, as they are to me. In particular I hope it has showed how easily you can do this yourself and experiment with it. Have fun!