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 [email protected]: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 [email protected] # 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.