If you are a developer or DevOps engineer, there is a very good chance that you have already used Vagrant — a personal virtualization management tool that can be used to create virtual machines on your laptop or desktop. Usually, that means Linux VMs on a Windows or Mac laptop.
Vagrant is actually an application that manages the lifecycle of virtual machines on the personal computer. Under the hood, it requires a Hypervisor to create and run a VM and most often that application is VirtualBox, because it is license-free. It also supports Hyper-V and VMWare.
Install VirtualBox and Vagrant on your personal computer — or a host machine in virtualization terminology. The VMs created on your computer are guest machines.
Download the VirtualBox image that is suitable for your machine’s OS (Windows, OS-X, etc.) and platform and architecture (Intel x86, AMD64, etc.). Choose the defaults for installation.
Download Vagrant for the target OS and install it, again choosing the defaults.
With the above prep, you are ready to create your first VM and SSH into it.
Creating an Ubuntu Machine
Bring up Command Prompt on Windows or Terminal on Mac, and run the following commands to start an Ubuntu machine.
cd into a directory where you want to keep the Vagrant stuff. Create a new one if needed.
vagrant init hashicorp/precise64
There are lots of things that happen behind the scenes (which we will go over soon), but that’s all you need to bring up your Ubuntu host on your local machine.
Logging Into a Guest Machine
On the UNIX friendly Mac, SSH-ing into the guest machine is very simple: Just use the following Vagrant command:
$ vagrant ssh
On Windows, it is relatively difficult because it doesn’t have a native SSH client. When a guest machine is created, the user vagrant is added to it, along with its public key. The private key for that user is available on the host machine, and the path to the public key and the port where SSHD runs on the guest machine (to which SSH clients should connect), can be listed using the command “vagrant ssh-config”.
$ vagrant ssh-config Host HOST HostName 127.0.0.1 User vagrant Port 2222 IdentityFile /Users/USERNAME/vagrant/.vagrant/machines/HOST/virtualbox/private_key
Obtain that info with an SSH client like PuTTY to access the guest machine.
Under the Hood
If your requirement is only to create an Ubuntu host and do something with it, then there isn’t much you need to know on what happens when commands like init and up are running. However, if you want to use Vagrant as an environment for development and automation, you need to know more than the simple provisioning steps above to rollout Vagrant-based solutions.
All the configurations used to provision the VMs using Vagrant are stored in the Vagrantfile. Though the name and location of this file are configurable, normally, it is in the current directory from where the Vagrant commands are run.
As a sample, the Vagrant init command generates the following Vagrantfile, which was used for provisioning the Ubuntu VM in the last example:
# -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.configure(2) do |config| config.vm.box = "hashicorp/precise64" end
Though the default version of the Vagrantfile is long, most of it is commented out with explanations for the supported options. The active config settings are seen above.
Provisioning Guest Machines
The installation of VirtualBox is a one time setup on your laptop/desktop. After that you can configure the machines you need in the Vagrantfile, and those could be started up.
It is important locate a suitable base image to build your guest machine of choice, mainly the OS flavor that provides. The base image is known as a “box” and that is specified by the config item config.vm.box. The box “hashicorp/precise64” installs a version of Ubuntu 64 bit architecture. The examples of boxes with other operating systems that you can use are these:
Once the guest machine is started up, it can be further configured using various provisioning methods such as inline shell commands, and scripts both local and accessed over a URL. For example, a local script can be run as a provisioning step by adding the following section in the Vagrantfile:
Vagrant.configure("2") do |config| config.vm.provision "shell", path: "script.sh" end
In a DevOps driven environment, VMs are provisioned by some Configuration Management (CM) tools such as Ansible. Vagrant support that option also. For example, Ansible playbook playbook.yml can be run against a newly created guest machine by specifying that as the provisioner in Vagrantfile:
Vagrant.configure(2) do |config| config.vm.provision "ansible" do |ansible| ansible.playbook = "playbook.yml" end end
Lifecycle of Guest Machine
A guest machine would go through few phases before it would be deleted from the host machine. If the provisioning steps are fully captured in the Vagrantfile and the scripts and playbooks Vagrant would use to manage the lifecycle of the VM, it can be recreated anytime.
Following are the life cycle stages and related Vagrant commands used:
up - This command is used to startup a VM as defined in the Vagrantfile. If it is not already on the host, it will be created new. Once the guest machine is up and running, users can login into it.
provision - A provisioner defined in Vagrantfile can run against and already running guest machine.
reload - If the Vagrantfile is changed after a guest machine is created, this command can be used to restart the VM with the changed configurations.
suspend/resume - As the commands suggest those can be used to suspend or resume a guest machine.
halt - Shuts down the guest machine but the underlying resources are still retained.
destroy - Shuts down a guest machine and removes its related resources from the host machine.
As it is possible to create multiple guest machines on the host, various network operational tasks could be done with them as well, all managed by Vagrant.
A port opened on a guest machine is not directly accessible. A guest machine port is accessed on the host machine via port forwarding from a host machine port to the required guest machine port. For example, look at the following definition in Vagrantfile for a VM:
config.vm.network "forwarded_port", guest: 80, host: 8080
In this case, if an HTTP server is running on port 80 on the guest machine it could be accessed as http://host-machine-name:8080.
The guest machines could use DHCP to get IP addresses assigned or they could be assigned static IPs from the Vagrantfile. Following are the related, sample configurations in Vagrantfile:
config.vm.network "private_network", type: "dhcp" config.vm.network "private_network", ip: "10.30.2.144"
There is a public network option also supported by Vagrant to expose the guest machines to public Internet. I am not very clear about its use as Vagrant is primarily used as a development infrastructure.
The project root directory where the Vagrantfile exists on the host machine is mounted on the guest machine as /vagrant, and that is configurable also. Additional disk locations can be mapped as follows:
config.vm.synced_folder "www/", "/srv/www"
The first entry is the actual folder is on the host machine and the second entry is how it would be mounted on the guest machine. If the source path is not absolute it is relative to the root directory.
Multiple methods like rsync, NFS and and SMB are used to support sharing of the directories between host and guest machines. The behavior of a shared folder will depend on which method has been used to set it up.
Multiple guest machines can be created on a host and that is limited only by the system resources locally available. Two guest machines, web and db, are defined in the following Vagrantfile snippet:
Vagrant.configure("2") do |config| config.vm.provision "shell", inline: "echo Hello" config.vm.define "web" do |web| web.vm.box = "apache" end config.vm.define "db" do |db| db.vm.box = "mysql" end end
Various configuration options that we had discussed above could be applied to each guest machine separately. These machines could share common configurations also as the inline shell provisioner applied to both machines in the last example.
The real power of Vagrant is unleashed by setting up multi-machine environments for development and testing on your laptop. As all the steps involved in configuring such complex environments are captured in Vagrantfile and related scripts, they could be shared between team members and put under revision control.
Taking it to Production
If CM provisioners like the Ansible option we had looked at earlier are used for building Vagrant based development environments, promoting those to production is very easy.
Also, such a process of setting up and maintaining of development environment, expressed in code, would shadow the application development process. Like the application code is managed, the Vagrant code could be shared between development team members and it could be version controlled.
If the company doesn’t maintain any permanent infrastructure in a data center or colo, and use only public cloud, Vagrant can be used to minimize the computational cost by setting up development environments locally on laptops which tend to be very powerful and they can run multiple guest machines.
The Vagrant documentation is simple and short and it must be read in full before if you plan to do anything extensive using Vagrant.