Vagrant

Provisioning

You've seen how to boot a simple virtual machine using Vagrant, so next, let's look at how to provision that VM in a repeatable way. As already mentioned, Vagrant supports a wide range of provisioners, but for the sake of this article, bash scripts will be used due to their familiarity. Adding a provisioning step is a matter of adding a single line inside the configure block in the Vagrantfile. The bootstrap also can be an inline script, but for most cases, it makes sense to supply a script file that contains the provisioning steps.

Add the following bootstrap.sh script to the same directory as the Vagrantfile:


#!/usr/bin/env bash
# provisioning is run as root by default

# make sure system sources are up to date.
apt-get update

# install a web server
echo "==== Installing NGINX ====" 
apt-get install -y nginx

echo "==== Start the NGINX Server ===="
service nginx start

This bootstrap script makes sure the system sources are up to date first. It then installs the nginx Web server. The provisioning is added to the Vagrantfile by adding the following line to the configure block so that your Vagrantfile now looks like this:


Vagrant.configure("2") do |config|
  config.vm.box = "hashicorp/precise32"
  config.vm.provision :shell, :path => "bootstrap.sh"
end

To use the new provisioning, destroy the running VM. This will stop and remove the virtual machine. To destroy the VM, simply use the command vagrant destroy. The base box still will be available for use after running the destroy command. Now to test the new configuration, simply run vagrant up. When a provisioning step is present in the Vagrantfile, Vagrant automatically will provision the box once it has booted. You can turn off the provisioning by using the --no-provision flag.

When vagrant up is complete, you will be able to ssh in to the machine as before. As shown below, let's make sure that the provisioning has been successful. Note that nginx is now installed on the system and running on the system:


$ vagrant ssh
$ which nginx
/usr/sbin/nginx
$ netstat -at | grep http
tcp        0      0 *:http      *:*       LISTEN

The virtual machine has been provisioned, and nginx is set up and started. The Web server can be tested to be working, but it's more difficult if you have to ssh in to the box to test this. Next, let's look at how to configure networking.

The first option to access ports on your virtual machine is port forwarding. For this example, nginx serves http traffic on port 80. To forward the guest machine port 80 to port 8080 on the host, add the following line to the Vagrantfile:


config.vm.network "forwarded_port", guest: 80, host: 8080

As before, destroy the VM and do another vagrant up. Once the virtual machine has been brought up and provisioned, the Web server can be accessed on the host port 8080. On your favorite browser, visit http://localhost:8080/ to be greeted with the nginx home page. This is one of the easier ways to be able to connect to the virtual machine.

For example, let's modify the bootstrap.sh to link the "html" directory in the Vagrant directory to the nginx root directory. This would be an example of doing simple static HTML Web site development. This could be distributed to anybody to modify the HTML on the host and immediately see the results on the guest.

The revised bootstrap looks like this:


#!/usr/bin/env bash
# provisioning is run as root by default

# make sure system sources are up to date.
apt-get update
# install a web server
echo "==== Installing NGINX ====" 
apt-get install -y nginx

echo "==== Start the NGINX Server ===="
service nginx start

echo "==== Symlink Test Directory ===="
ln -s /vagrant/html/ /usr/share/nginx/www/html

You can access the html directory through the nginx Web server using the forwarded port once the VM has been booted and provisioned by Vagrant. For example, using curl:


$ curl "http://localhost:8080/html/tester.html"

This example assumes an html directory is in your Vagrant directory that has a tester.html file in it.

What if you want to provide a way to access the virtual machine as if it were another machine on the network? Vagrant provides a very easy way to use VirtualBox networking to set up the virtual machine with a static IP on the same LAN. Remove the port forwarding line from the Vagrantfile and replace it with the following:


config.vm.network "private_network", ip: "192.168.56.20"

Once you have re-ran vagrant up, your virtual machine will be accessible from your host using the IP address specified above. In my own projects, I use this private networking to assign my static IP a domain by modifying the /etc/hosts file. This allows testing of complicated http redirect flows that rely on certain domains.

It also allows multiple Vagrant virtual machines to contact one another, as it is a shared network between the host and all other virtual machines that are on the network. For distributed projects, being able to control the networking access of multiple standalone components all of which have their own Vagrantfile (or shared) is very powerful.

Again, in my work environment, it's not uncommon to spin up multiple virtual machines, one for a database, one for an app server and one for a squid proxy. Running functional tests against these tests a production-like setup. Vagrant allows you to do this in a simple, easy-to-repeat way, and the private network means there is no hassle in setting up intercommunication between the various components.

The last part of Vagrant that I want to cover is having multiple machines in the one Vagrantfile. I already have given an example of why you might want to have multiple Vagrantfiles for different components, so that they can be tested/developed on their own or as a collective. Having multiple machines in the one Vagrantfile is powerful when a single project can have multiple parts that don't belong on the one machine. Often in a Web development project, you will run the app server and database on the same machine for testing but will have multiple app servers and databases when that system gets into the production environment. This is the cause of a lot of production bugs due to untested component configuration.

Vagrant allows you to manage this all through a single Vagrantfile. If the idea of multiple machines is one that interests you, I also recommend looking closely at the new Docker container option for Vagrant. Docker is built on top of Linux containers and allows you to spin up machines much faster than VirtualBox. Multiple machine support in Vagrant is all the more powerful if the provider level is much faster to spin up machines.

For multiple machine support, the Vagrantfile allows you to configure multiple machines, each having their own configuration. The Vagrantfile below shows an example of what a MySQL master and slave Vagrantfile might look like:


Vagrant.configure("2") do |config|
  config.vm.box = "hashicorp/precise32"

  config.vm.define "master" do |master|
      master.vm.provision :shell, :path => "master_bootstrap.sh"
      master.vm.network "private_network", ip: "192.168.56.20"
  end
  config.vm.define "slave" do |slave|
      slave.vm.provision :shell, :path => "slave_bootstrap.sh"
      slave.vm.network "private_network", ip: "192.168.56.21"
  end
end

Each machine is defined with the first config block as separate blocks. They support all of the same options as the normal one-machine Vagrant config blocks. For example, for the master here, you define the shell provisioning script to be run as well as the static IP address to give this virtual machine. This allows you to test distributed systems in the same easy repeatable way as you could with a single machine.

When using a multiple machine configuration like the above, there are a few changes to the way you have to run Vagrant commands. Running vagrant up will bring up both boxes automatically. The master will be provisioned first and then the slave. You can choose to bring up only one of the machines by supplying the machine name:


$ vagrant up master

When the boxes have booted and been provisioned, you also have to specify the machine name when using vagrant ssh.

Conclusion

Let's reflect on what you have achieved here: multiple virtual machines booting, being provisioned automatically and consistent IP addresses using one command—vagrant up. This is repeatable across the three major operating systems and a number of different providers including the most popular virtualization technologies and cloud offerings.

This removes so many of the pain points from working on systems in local development. There are a lot of advantages to making the switch to using Vagrant for both local development and your continuous integration systems. All developers' test machines are running the same code and are provisioned in the same fashion. Test failures are not tied to a specific developer's setup and should be reproducable on anyone's machine by running vagrant up. This article covered only some of the amazing capabilities of Vagrant, so I encourage you also to read over the brilliant documentation at http://docs.vagrantup.com.

______________________

Richard Delaney is a software engineer with Demonware Ireland. Richard works on back-end Web services using Python and the Django Web framework. He has been an avid Linux user and evangelist for the past five years.