ik

Ivan Kusalic - home page

Vagrant: Intro

I’ve recently stumbled upon Vagrant and I am really happy about this discovery. In this post I’m going to name some of the reasons why Vagrant is useful. At the end, I’m going to implement a small real-world example.

So let’s get started.

What is Vagrant

Vagrant is “a tool for building complete development environments”. It is used to create virtual machines in a consistent and repeatable way. Virtual machines are provisioned on top of chosen virtualization software, typically VirtualBox. When the virtual machine has started, it can be provisioned with tools like Chef or shell scripts. With automated provisioning you can spawn a new virtual machine in a matter of minutes, fully configured to your specific needs.

Don’t worry if this all sounds a bit fuzzy. Just continue reading and you’ll get it by the end of the post.

Why is Vagrant useful

Vagrant enables you to create new, clean, fully configured virtual machine in a matter of minutes. You can use this virtual environment to execute some experiments and then dispose of it, or you can reuse it as normal virtual machine for as long as you want. This is especially useful if you want to try something in an operating system that isn’t installed on your machine.

I’m a Mac OS X user, but I often need to do something in Linux. So I just spawn a new Ubuntu virtual machine via Vagrant. This VM will be preconfigured with all the essential tools I need to be productive, e.g. fully configured Vim, properly set up Ruby and Python, Git, etc. After some experimentation I can discard the VM if I dirtied it somehow, or keep it for the future experiments.

Vagrant also enables you to effortlessly share your environment with colleges. This is extremely useful for collaboration purposes, as now the whole team can develop or debug in the same environment.

You just need to share the Vagrantfile (Vagrant’s configuration file) and provisioning recipes. The Vagrantfile is written in Ruby and the provisioning recipes are usually scripts or programs as well. Therefore you can put the whole environment under source control tool like Git.

Installation

First install one of the supported virtualization providers. I use VirtualBox. It is free and runs on all the major operating systems.

Next, choose the appropriate installer and run it to install Vagrant itself.

Useful commands

Vagrant is used from the command line. Here’s a short list of useful commands to get you started1:

  • vagrant init – initializes the current directory to be a Vagrant environment by creating an initial Vagrantfile
  • vagrant up – creates and configures guest machines according to your Vagrantfile
  • vagrant ssh – SSH into a running Vagrant machine and give you access to a shell
  • vagrant destroy – stops the running machine Vagrant is managing and destroys all resources that were created during the machine creation process
  • vagrant suspand – suspends the guest machine Vagrant is managing, rather than fully shutting it down or destroying it
  • vagrant provision – Runs any configured provisioners against the running Vagrant managed machine.

Simple example

I’m going to show you how to use Vagrant in a real-world situation so you can get a better feel for Vagrant.

In this example I want to use Linux traffic-control tool tc to limit the outgoing traffic generated by a Python script. At the same time, I want to sustain the SSH connection. I need to set up the tc rules and test the setup. The problem is, if I misconfigure the tc, I won’t be able to SSH to that machine anymore. I’m also working on a Mac machine and that complicates things even further. Luckily, I can use Vagrant to solve all the problems.

The full source code for this example is available on github.

Note that Vagrant provides quite a few advanced configuration options that I’m not going to cover in this simple example. Those include: port forwarding and network configuration, VM CPU/Memory management, custom plugins, etc.

New Vagrant environment

To get started, create a new directory, e.g. vagrant-tc-example, and initialize new Vagrant environment:

1
2
3
mkdir vagrant-tc-example
cd vagrant-tc-example
vagrant init

The vagrant init command creates the Vagrantfile. This is the Vagrant configuration file, written in Ruby. It contains lots of configuration possibilities in the comments, so take a look around.

Base boxes

Next, you need to select a ‘base box’. The base box is a virtual machine image you want to use as base for your environment.

Although you could build a base box yourself, it’s much easier to use one of the publicly available boxes.

I’ve decided to use the Ubuntu Precise image from ubuntu.com.

Modify the configuration file to use this image:

Vagrantfile
1
2
3
4
5
6
7
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = 'precise64cloudimagesubuntu'
  config.vm.box_url = 'http://cloud-images.ubuntu.com/precise/current/precise-server-cloudimg-vagrant-amd64-disk1.box'
end

The box attribute is used to identify the image on the local system. Since the box doesn’t exist yet, you are free to name the image however you want. The box_url attribute specifies the download URL for the image.

With this you have the minimal settings needed to start the new Vagrant VM. You can do this by running:

1
vagrant up

If you get the warning that “The guest additions on this VM do not match the installed version of VirtualBox!”, just ignore it.

Now that the VM is up and running, you can log into it by running:

1
vagrant ssh

Congratulation, you are inside of a Vagrant VM.

Provisioning with Bash script

Now it’s time to provision the VM with chosen tc settings.

Create a new Python script, called attack.py, to generate the egress traffic:

attack.py
1
2
3
4
5
6
7
8
9
import random
import socket

packet = random._urandom(1450)
socks = [socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for _ in range(100)]

while True:
    for sock in socks:
        sock.sendto(packet, ('8.8.8.8', 80))

The script implements simple UDP flooding. To prevent the flooding, I’m going to use the following tc rules:

tc rules
1
2
3
4
5
6
7
tc qdisc add dev eth0 root handle 1:0 htb default 1
tc class add dev eth0 parent 1:0 classid 1:1 htb rate 80kbit burst 2k
tc qdisc add dev eth0 parent 1:1 handle 2:0 drr
tc class add dev eth0 parent 2:0 classid 2:1 drr
tc filter add dev eth0 parent 2:0 prio 20 handle 1 basic flowid 2:1
tc class add dev eth0 parent 2:0 classid 2:2 drr
tc filter add dev eth0 parent 2:0 prio 10 handle 2 cgroup

Without going too deeply into workings of traffic-control tool tc, those rules limit total outgoing traffic to 10kb/s (80kbit/s). The traffic is shared between two cgroups and the fairness is enforced. This prevents flooding from interfering with the SSH connection to VM.

Those rules need to be incorporated into the provisioning process of the VM. Change the Vagrantfile to:

Vagrantfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = 'precise64cloudimagesubuntu'
  config.vm.box_url = 'http://cloud-images.ubuntu.com/precise/current/precise-server-cloudimg-vagrant-amd64-disk1.box'

  config.vm.provision :shell, :inline => <<-HERE
    apt-get install cgroup-lite
    modprobe cls_cgroup
    echo 'cls_cgroup' >> /etc/modules
    cgroups-umount
    cgroups-mount

    apt-get install iproute
    tc qdisc add dev eth0 root handle 1:0 htb default 1
    tc class add dev eth0 parent 1:0 classid 1:1 htb rate 80kbit burst 2k
    tc qdisc add dev eth0 parent 1:1 handle 2:0 drr
    tc class add dev eth0 parent 2:0 classid 2:1 drr
    tc filter add dev eth0 parent 2:0 prio 20 handle 1 basic flowid 2:1
    tc class add dev eth0 parent 2:0 classid 2:2 drr
    tc filter add dev eth0 parent 2:0 prio 10 handle 2 cgroup
    echo '0x00020001' > /sys/fs/cgroup/net_cls/net_cls.classid
    mkdir /sys/fs/cgroup/net_cls/test_group
    echo '0x00020002' > /sys/fs/cgroup/net_cls/test_group/net_cls.classid

    nohup python /vagrant/attack.py > /dev/null 2>&1 &
    echo $! > /sys/fs/cgroup/net_cls/test_group/tasks
  HERE
end

Embedded provisioning shell script installs all the necessary tools and creates the tc rules. It starts automatically the attack.py script and places it into a separate cgroup.

If you take a closer look, you’ll see that the Python script is invoked from /vagrant/attack.py. This works because by default, the project directory (the one with the Vagrantfile) is shared with the VM at /vagrant. You can also configure shared directories explicitly if you want.

Now you can provision the VM:

1
vagrant provision

(You can also start from scratch, by destroying the VM and starting it again with vagrant destroy and vagrant up.)

You can safely ignore warnings like “stdin: is not a tty” or “dpkg-preconfigure: unable to re-open stdin: No such file or directory”.

SSH again into the VM and check that the setup is in place and working:

1
2
vagrant ssh
watch -d -n 0.5 'tc -s class ls dev eth0'

You should see something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class htb 1:1 root leaf 2: prio 0 rate 80000bit ceil 80000bit burst 2Kb cburst 1600b
 Sent 7452150 bytes 5283 pkt (dropped 174270308, overlimits 0 requeues 0)
 rate 79224bit 10pps backlog 0b 1001p requeues 0
 lended: 4279 borrowed: 0 giants: 0
 tokens: -1637392 ctokens: -2337392

class drr 2:1 root quantum 1514b
 Sent 86146 bytes 346 pkt (dropped 0, overlimits 0 requeues 0)
 backlog 102b 1p requeues 0
 deficit 1514b
class drr 2:2 root quantum 1514b
 Sent 7366004 bytes 4937 pkt (dropped 174270308, overlimits 0 requeues 0)
 backlog 1492000b 1000p requeues 0
 deficit 272b

Class 2:2 (attack.py script) should generate traffic faster than class 2:1 (system traffic, in this case mostly SSH connection traffic).

You can play around with tc rules and provisioning process, e.g. you can remove the tc rules and start the flooding script to see your SSH connection being flooded and terminated.

That’s all for this simple example. The example itself is not really important. Instead, you should get the feeling of how easy to use and useful Vagrant is.

Conclusion

Vagrant is tool for managing different development environments. You can use it to experiment or develop in customised VMs. You can also used it to share the environment with your colleges.

Check out the “Vagrant: development environment” post for details on how I’m using Vagrant to set up my personal, on-demand, Linux development environment. In Using Thoughtworks’ Go (Continuous Delivery) With Vagrant you can see how easy it is to use Vagrant to explore and try out new software.

If you use Vagrant in an innovative way or just want to chat about it, send me an email.


  1. All the descriptions are taken from the official documentation.