I was recently asked to stand up and configure a couple of VMs for automated deployment testing. We needed to provide the devs with a Linux environment in which to deploy and test code prior to checking it into subversion. Vagrant sounded like the perfect tool for the job since they wanted to be able to pass a VM around that people would be mucking with but always needed working. And since I’d inevitably be asked for updates and changes, and I was invariably going to make mistakes on the initial deployment, I definitely needed a way to clean up the environment I was working in and produce a fresh copy at a moment’s notice.

I quickly got the VMs up and networked together and moved on to the actual work of deploying the applications themselves by referencing the wonderful vagrant docs. I found a couple of tricks pretty handy, and people seem to find vagrant itself somewhat intimidating to approach, so I put this quick guide together as a resource for anyone else setting out on a similar path.

Default Vagrant Setup
By default, vagrant will spin up a VM with a dynamic ip address that’s placed behind what is effectively a NAT. This is easiest to see when you go and try to ssh to the VM without asking vagrant to do it for you. Access is enabled over localhost to a default port of 2222, and so you have to access it at:

    ssh -i ~/.vagrant.d/insecure_private_key vagrant@172.0.0.1:2222

Of course, any other kind of access to the box won’t be possible. That is, if you hit localhost:80 to try and access the apache instance on the VM, you’re going to really hit port 80 on the host machine.

You can try this kind of basic setup out by downloading a vagrant box and running “vagrant init ”. For example, I have a centos 6.4 box added to vagrant and called centos-6.4.

    mkdir -p “~/vms/test”
    cd “~/vms/test”
    vagrant init centos-6.4

This produces a basic Vagrantfile that we can mess around with:

    # Comments stripped out
    Vagrant.configure(“2”) do |config|
        config.vm.box = "centos-6.4"
    end

We can also give the generated VM a name and prepare ahead a little bit to allow for spawning multiple VMs:

    Vagrant.configure(“2”) do |config|
        config.vm.define “test” do |vm_config|
            vm_config.vm.box = “centos-6.4”
        end
    end

This defines a VM named “test” and only sets a single parameter: the box from which it is generated.

Accessing a single VM via IP
If we’d like to actually access this VM and do anything interesting with it, we’re going to have to set some networking settings. Add the following to the test VM block in the Vagrantfile:

    vm_config.vm.network “private_network”, ip: “10.0.0.2”

This sets up the networking on the VM and places it in a LAN allowing direct ip access from the host to the VM. We set the ip address to something private and not routable outside the LAN to avoid networking issues. Anything from the 10.0.0.0/8 or 172.0.0.0/8 blocks should do, though you’ll want to adjust this based on networking conditions within your corporate or private LANs. I’ve chosen 10.0.0.2 here as an example.

With that set, we can now launch the VM and log into it via:

    vagrant up
    ssh -i ~/.vagrant.d/insecure_private_key vagrant@10.0.0.2
    # The previous command is now equivalent to “vagrant ssh test”

As a side note, we could also ask Vagrant to pick the ip address dynamically via DHCP. However, if we do, then we’d need additional tools to pull the ip address back out of the VM in order to use it going forward.

Also, a VM brought up in this way will not be accessible from outside of the private network created by Vagrant. If you need to be able to share access to the generated VMs, I recommend reading up on the bridged networking options.

Finally, if you only need to work with a single VM and know exactly what applications need to run on the VM and what ports you’ll need to be able to access, you can instead just set up port fowarding rules.

Networking two or more VMs together
With that basic set up out of the way, we can move on to the slightly more complicated two VM set up. The essence of what we need to is add a new block that defines another VM just like the first and give it a different name and ip address. We can do this as follows:

    config.vm.define “test2” do |vm_config|
        vm_config.vm.box = “centos-6.4”
        vm_config.vm.network “private_network”, ip “10.0.0.3”
    end

And that’s all there really is to it. The VMs are now available together in a LAN and can access each other via ip address. Of course, there are a few things we can do to make life easier.

The first thing to note is that the boxes don’t come up with a routable hostname. This means we have to remember the ip address we set for each box in order to access them. Vagrant doesn’t provide any handy tools to solve this problem directly, but we can work around it by adding a couple of hosts file entries on the VMs and on the host. I’ll leave directions for the host out since that depends on what OS you’re using. But on the VMs, we can add a small provisioning script to write the appropriate values into the hosts file. We can define a script at the top of the Vagrantfile like this:

    $HOST1IP = "10.0.0.2"
    $HOST2IP = "10.0.0.3"
    $hostsedit = <<EOB
        echo "#{$HOST1IP} test" >> /etc/hosts
        echo "#{$HOST2IP} test2" >> /etc/hosts
    EOB

    Vagrant.configure(“2”) do |config|
     # ...SNIP

And we can run the script on each VM by adding the following line to each |vm_config| block:

    vm_config.vm.provision “shell”, inline: $hostsedit

This also allows us to refactor the ip addresses associated with each VM into a configurable variable at the top of the file. We can also add the host names at the same time:

    $HOST1 = "test"
    $HOST2 = "test2"

And replace all occurrences of the respective hostnames and ip addresses with their respective variables.

There’s one other useful trick in the case where you need to be able to ssh between VMs directly without relying on the host (i.e. when the host is a windows machine). In this case, I’ve found installing the vagrant private key into each VM as the vagrant user’s ssh key to provide the necessary access with the minimum fuss. We need to add two provisioning settings to each vm_config block and an associated script:

    $configureprivatekey = <<EOB
        echo "Configuring installed private key."
        chmod 600 /home/vagrant/.ssh/id_rsa
    EOB

    # ...SNIP…
    # Inside vm_config blocks
    vm_config.vm.provision "file", source: "~/.vagrant.d/insecure_private_key", destination: "/home/vagrant/.ssh/id_rsa"
    vm_config.vm.provision "shell", inline: $configureprivatekey

Now you can hop onto the test VM and ssh to test2 without issue:

    vagrant ssh test
     ssh test2 “echo ‘It works’”

And you should see “It works” echoed back to the terminal.

Finally, you can move files between the host and the VMs via the directory containing the Vagrantfile on the host. For example, on my machine, I set ran “vagrant init” inside “~/vms/test/”, and I’ve been working from that directory since. Vagrant automatically mounts this directory (~/vms/test/) inside each VM at “/vagrant”. So you can always move files to/from the host and between VMs via that directory. You can also add other shared folders via the synced_folder option.

The full Vagrantfile put together in this tutorial follows:

    $HOST1 = "test"
    $HOST2 = "test2"
    $HOST1IP = "10.0.0.2"
    $HOST2IP = "10.0.0.3"

    $hostsedit = <<EOB
        echo "#{$HOST1IP} test" >> /etc/hosts
        echo "#{$HOST2IP} test2" >> /etc/hosts
    EOB

    $configureprivatekey = <<EOB
        echo "Configuring installed private key."
        chmod 600 /home/vagrant/.ssh/id_rsa
    EOB

    Vagrant.configure("2") do |config|
        config.vm.define $HOST1 do |vm_config|
            vm_config.vm.box = "centos-6.4"
            vm_config.vm.hostname = $HOST1
            vm_config.vm.network "private_network", ip: $HOST1IP

            # Add Hosts File Entries
            vm_config.vm.provision "shell", inline: $hostsedit

            # Install Private Key
            vm_config.vm.provision "file", source: "~/.vagrant.d/insecure_private_key", destination: "/home/vagrant/.ssh/id_rsa"
            vm_config.vm.provision "shell", inline: $configureprivatekey
        end

        config.vm.define $HOST2 do |vm_config|
            vm_config.vm.box = "centos-6.4"
            vm_config.vm.hostname = $HOST2
            vm_config.vm.network "private_network", ip: $HOST2IP

            # Add Hosts File Entries
            vm_config.vm.provision "shell", inline: $hostsedit

            # Install Private Key
            vm_config.vm.provision "file", source: "~/.vagrant.d/insecure_private_key", destination: "/home/vagrant/.ssh/id_rsa"
            vm_config.vm.provision "shell", inline: $configureprivatekey
        end
    end

The Vagrant Docs are a fantastic and well-written resource and you should definitely check them out if you need to go beyond what I’ve described here. Once you’ve played with Vagrant for a little while you’ll start reaching for it any time you need to spin up a VM. Gone are the days of manually setting up VMs and tinkering with them by hand. Vagrant makes for an extremely powerful automation tool, especially when you want to do local deployment testing.

Leave a comment

Your email address will not be published. Required fields are marked *

X