Tag Archives: vagrant

Dynamic multi-machine Vagrantfile

Often I am facing the need to be able to rapidly test a multi-machine environment.
My tool of choice for this kind of tasks is Vagrant. Vagrant is really powerful.
For those of you who have not yet heard about it:

Vagrant is a tool for building complete development environments. With an easy-to-use workflow and focus on automation, Vagrant lowers development environment setup time, increases development/production parity, and makes the “works on my machine” excuse a relic of the past.
http://www.vagrantup.com/about.html

It’s usage is pretty straightforward. Simply install it on any of the supported platforms and run:

vagrant init
vagrant up

BAM, you got your VM.

What this will do is generate a default Vagrantfile in your current working directory and boot a virtual machine in VirtualBox.
This is fine for most purposes. However, In case you need something more sophisticated please read on.

In my case I wanted to use a Fedora 19 VM instead of the default Ubuntu VM.
This was quite simple. There are plenty of good boxes on vagrantbox.es this is somewhat easier and quicker than spinning your own using Veewee.

To add a box to vagrant just run the following command:

vagrant box add fedora19 https://dl.dropboxusercontent.com/u/86066173/fedora-19.box

The next step is to create a new Vagrantfile. This can be achieved in two ways. The first way is to write one by hand. The second way is to use this command:

vagrant init fedora19

After removing comments etc from the Vagrantfile we end up with something like:

# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.box = "fedora19"
end

This Vagrantfile can be used to boot a fresh Fedora 19 VM.

I needed more customization, like being able to specify the amount of CPUs and RAM for the VM.
In addition I required a second network interface connected to an internal network.
Vagrant basically allows you to run vboxmanage so I ended up with this:

config.vm.provider "virtualbox" do |v|
  v.customize ["modifyvm", :id, "--memory", 256]
  v.customize ["modifyvm", :id, "--cpus", 2]
  v.customize ["modifyvm", :id, "--nic2", "intnet", "--intnet2", "glusternet"]
end

The next step was to execute some command on VM boot ( installing packages etc… )
Just create a script bootstrap.sh next to the Vagrantfile and modify the Vagrantfile

config.vm.provision :shell, :path => "bootstrap.sh"

There are other means of provisioning, puppet and chef are by far the most popular tools, and are nicely integrated into vagrant.

So far so good…

Since I needed more than one VM my initial approach was to define multiple VMs in the Vagrantfile.
This became somewhat tedious, and resulted in many duplicate lines.
Because a Varantfile is actually a ruby script, this can be greatly simplyfied.
So I ended up defining the number of VMs I want and put everything into a loop.

NODE_COUNT = 4
  NODE_COUNT.times do |i|
    node_id = "node#{i}"
    config.vm.define node_id do |node|
    node.vm.box_url = "https://dl.dropboxusercontent.com/u/86066173/fedora-19.box"
    node.vm.box = "fedora19"
    node.vm.hostname = "#{node_id}.intranet.local"
  end
end

This will result in 4 running VMs named node0, node1, node2 and node3.
I didn’t want this environment conflicting with others so I set the name of the internal network to some random string.

INTNET_NAME = [*('A'..'Z')].sample(8).join
v.customize ["modifyvm", :id, "--nic2", "intnet", "--intnet2", "#{INTNET_NAME}"]

This will give this internal network its own namespace.

Putting it all together, the finished Vagrantfile looks somewhat like:

# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"
NODE_COUNT = 4

INTNET_NAME = [*('A'..'Z')].sample(8).join

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

  config.vm.provider "virtualbox" do |v|
    v.customize ["modifyvm", :id, "--memory", 256]
    v.customize ["modifyvm", :id, "--cpus", 2]
    v.customize ["modifyvm", :id, "--nic2", "intnet", "--intnet2", "#{INTNET_NAME}"]
  end
  config.vm.provision :shell, :path => "bootstrap.sh"


  NODE_COUNT.times do |i|
    node_id = "node#{i}"
    config.vm.define node_id do |node|
      node.vm.box_url = "https://dl.dropboxusercontent.com/u/86066173/fedora-19.box"
      node.vm.box = "fedora19"
      node.vm.hostname = "#{node_id}.intranet.local"
    end
  end


end

If you want to know more about Vagrant, go ahead and visit their website.