Managing and provisioning VMs

Touch and Go

© Lead Image © adchariya chanpipat, 123RF.com

© Lead Image © adchariya chanpipat, 123RF.com

Author(s):

With Vagrant, you can automate the creation and management of consistent virtual machines that work across platforms.

Automation is the key ingredient for efficiency in any system administration strategy. If your job involves spinning virtual machines (VMs) regularly, you should familiarize yourself with Vagrant [1], which helps you make consistent virtual environments available to your users with a few keystrokes. Vagrant provides a simple and easy to use command-line interface (CLI) that helps automate the creation, editing, running, and deletion of VMs. Moreover, it supports all major virtual platforms, such as VirtualBox and VMware, and plays nicely with all the well-known software configuration tools, such as Chef, Puppet, Ansible, Fabric, and more.

Since VM hypervisors like VirtualBox and VMware have their own CLIs that can be used to automate provisioning, why should you choose Vagrant? Vagrant offers consistency and interoperability. With Vagrant you can define your virtual environment in code and then use it to provision VMs on top of any hypervisor running on any operating system. Additionally, anytime you make changes to the virtual environment, instead of sharing the complete VM that can be worth several gigabytes, you can share a simple text file that can then be used to provision VMs with the changes.

A Rolling Start

While Vagrant can provision VMs with various virtualization software, by default it uses the free and open source VirtualBox.

To get started, the first thing you need to do is install Vagrant and VirtualBox. The Vagrant project recommends downloading the packages for Vagrant [2] and VirtualBox [3] directly from their respective websites, since package managers will probably have outdated versions.

Next, you'll need to create a Vagrant configuration file that defines all the VM's characteristics from a template. You can search for templates based on various operating systems and predefined purposes on the project's website [4]. For example, fire up a terminal and enter:

$ mkdir ~/demo
$ cd ~/demo
$ vagrant init centos/7

to create a VM based on the CentOS 7.6.181 release inside the ~/demo directory.

Under the ~/demo directory, this command creates a file called Vagrantfile, which is the main configuration file that defines all the VM's attributes. If you want to make changes to a VM, you'll need to edit this file. You'll need to be well-versed with the Vagrantfile's anatomy in order to modify or create one as per your needs.

For now, you can bring up the VM using the default settings, by typing:

$ vagrant up

This command will create the actual VM reading the configuration specified in the Vagrantfile. This tells Vagrant to create a new VirtualBox machine based on the base image specified in the Vagrantfile. It'll do so by copying the virtual hard disk files from the remote server (Figure 1).

Figure 1: The first time you bring up a VM, it'll take a lot longer, because it downloads the image from the Internet.

Once it's done, you'll have a fully featured CentOS 7 VM running headless in the background. If you get errors regarding missing guest additions, refer to the "Guest Additions Plugin" box to leverage Vagrant's extensive plugin infrastructure to solve the issue permanently.

Guest Additions Plugin

The VirtualBox guest additions is an essential component that is required to take full advantage of Vagrant. However, rolling them into every box and then making sure the boxes are running the latest version of the guest additions is a time consuming task and an unnecessary distraction. A Vagrant plugin can take care of installing and updating the guest additions automatically.

To install the plugin, head to the terminal and run the following Vagrant command:

$ vagrant plugin install vagrant-vbguest

Once Vagrant has been equipped with the plugin, every time you launch a box, Vagrant will check whether the VM is equipped with the guest additions. If the latest version is installed, it'll continue booting the machine. If an update or installation is required, Vagrant will then automatically download all dependent packages and then install the guest additions from the VirtualBox ISO (Figure 2).

Figure 2: The vbguest plugin will automatically download the latest guest additions if it isn't available in the VM.

Once the VM is up and running, no extra window will pop up on the screen; you'll be returned to the shell prompt, because Vagrant VMs run headless by default. Instead you can access the VM by typing:

$ vagrant ssh
[vagrant@localhost ~]$

As you can see, the command uses SSH to connect to the VM. The command will automatically authenticate the SSH sessions and drop you at the SSH shell in the machine. You can now interact with the VM like any other CentOS installation (Figure 3). You can also share files between the host and the VM (see the "Shared Filesystem" box). When you're done working inside the VM, type:

[vagrant@localhost ~]$ exit
logout
Connection to 127.0.0.1 closed.
$

which will drop you back to your host's CLI.

Figure 3: Vagrant doesn't support SSH using a normal password; instead it uses public key authentication.

Shared Filesystem

With Vagrant, you can easily share files between the host and the VM. The files placed inside the shared filesystem are kept in sync between the physical and the virtual machines. You can then easily roll the shared filesystem into your host's backup strategy. In addition, these files will always be available to the VM whenever you create a new VM instance.

By default, the current project folder (the root folder that contains the Vagrantfile) is shared between the host and guest machines. You can verify this by first SSHing into the VM and then listing the files in the directory:

$ vagrant ssh
[vagrant@localhost ~]$ ls /vagrant
Vagrantfile

As you can see, this is the same Vagrantfile that's kept under the ~/demo directory on the host. Any file or directory you place inside this folder will be available in /vagrant inside the guest.

You can easily share another folder from the host to the VM by tweaking the Vagrantfile. Open the file and add the following line:

config.vm.synced_folder "/home/bodhi/Documents/", "/docroot"

This line asks Vagrant to share the contents of the /home/bodhi/Documents/ folder on the host inside the /docroot directory in the VM. After saving the file, simply restart the VM with:

$ vagrant reload

During boot up, Vagrant will automatically create the /docroot directory inside the VM and mirror the contents of /home/bodhi/Documents/ inside it. You can again verify this by SSHing into the VM and then listing the directory's contents:

$ vagrant ssh
[vagrant@localhost ~]$ ls /docroot

Vagrantfile Anatomy

Before you can make changes to your VMs, you must familiarize yourself with Vagrantfiles. Vagrant is configured separately for each VM, each of which has its own isolated configuration environment. At the crux of each VM is the configuration file, Vagrantfile.

The Vagrantfile is a simple text file that Vagrant reads in order to create the virtual environment. The Vagrantfile describes the various parameters that are necessary to create the VM. Vagrant reads this file to configure and provision the VMs.

Because of this singular Vagrantfile, other users can use Vagrant to automatically and easily create their virtual environment with a single command. Vagrantfiles are portable, which means you can use them to create and provision VMs on every platform that Vagrant supports, including Linux, Windows, and Mac OS X.

As shown previously, the Vagrantfile is created automatically when you issue the vagrant init command, for example:

vagrant init centos/7

By default, the file is heavily commented out and only exposes a handful of parameters. However, these are the most essential ones, and you can use this Vagrantfile to create a VM with any additional modifications.

Also note that a Vagrantfile is written in Ruby. Even if you aren't well versed in Ruby, you'll be able to configure every aspect of Vagrant without ever learning the programming language.

Boxed In

One of the very first parameters in the Vagrantfile is config.vm.box. In our example, this parameter will be set to centos/7.

Because building a VM from scratch is a resource-intensive and time-consuming endeavor, Vagrant uses a base image and clones it to rapidly create a usable machine. In Vagrant terminology, this base image is called a box, and it is distributed in the form of .box files.

Boxes contain already-installed operating systems (CentOS in my example), so they're usually quite large, ranging from a few hundred megabytes to a few gigabytes. The boxes are downloaded when you issue vagrant up for the first time. Vagrant saves this box file for future usage, so it won't have to be downloaded again, even when you remove a Vagrant VM.

The required box needs to be specified for every VM using the config.vm.box parameter in the Vagrantfile. In my example, this is set to centos/7 since this was the box I requested with vagrant init earlier. Remember that multiple Vagrant environments can have the same config.vm.box value, which gives you the flexibility to create different VMs for different purposes with the same box image.

Port Forwarding

An important setting for my particular use of VMs is port forwarding, which allows me to access services running inside the VM from the host. Vagrant supports this type of networking as well. I can tweak the Vagrantfile to set up a port on the host machine to forward to a port in the VM. In effect, this allows me to access the services running inside the VM.

For instance, if I forward the standard port for HTTP content (port 80) in the VM to port 8081 on the host, then I can access the web server running in the VM by pointing the web browser on the host machine to http://localhost:8081. In the background, the traffic sent to port 8081 is actually forwarded to port 80 on the VM.

Fire up the Vagrantfile in a text editor and enter the following parameter:

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

With this parameter, I am instructing Vagrant to forward port 80 from inside the VM to port 8081 on the host. Save the file and restart the VM:

$ vagrant reload

To check the settings, you can use the SimpleHTTPServer module in Python to read the contents of the /vagrant directory. Once the VM is up and running, SSH into it and bring the web server online with:

$ vagrant ssh
[vagrant@localhost ~]$ cd /vagrant
[vagrant@localhost ~]$ sudo python -m SimpleHTTPServer 80

Now open a browser on the host and point it to http://localhost:8081, and you'll get a /vagrant directory listing served from inside the VM. You can now tweak the example to install any service inside the VM and modify the Vagrantfile to shuttle traffic between the host and the VM.

Provisioning VMs

At the moment, the CentOS VM is pretty basic. To harness Vagrant's power, you'll have to flesh out the installation. You can do this in a couple of ways. You can use the CentOS package manager to add packages manually. However, the better option is to automatically install the software as part of the VM's creation process, which is known as provisioning. Vagrant supports provisioning with shell scripts, Chef, or Puppet, and you can add more provisioners via plugins.

I personally prefer provisioning via shell scripts as opposed to Chef or Puppet, which are overkill for my simple requirements. Working with shell scripts is fairly straightforward. All you need to do is simply gather all the required operations that need to be handled automatically inside a script (see Listing 1).

Listing 1

Provisioning Shell Script

01 $ sudo nano provision.sh
02
03 # !/usr/bin/env bash
04
05 echo "Now installing the Apache web server quietly in the background"
06 yum update httpd > /dev/null 2>&1
07 yum install -y httpd > /dev/null 2>&1
08 systemctl start httpd

Line 3 of Listing 1 specifies which shell to use to execute the rest of the file (bash in this case). The script will then display that it's about to install the Apache web server without any further prompts or outputs, so as to not fill the screen with unnecessary output.

Vagrant will run the script as root, which is why there is no need to actually use sudo on lines 6, 7, and 8. The -y flag tells yum to automatically respond "yes" to any prompts. This is important since I am provisioning the VMs automatically. Because there is no human interaction, if yum were to ask for confirmation, the script would simply never finish. As previously mentioned, yum's output is sent to /dev/null instead of the terminal.

Once the shell script has been created, the next step is to configure Vagrant to use it to provision the VMs during boot. For this, you can point to the script from the Vagrantfile with the following parameter:

config.vm.provision "shell", path: "provision.sh"

This tells Vagrant to provision the machine with the shell provisioner and to use the shell script named provision.sh that's available in the project directory (~/demo on the host).

Now bring up or reload the VM. Thanks to this parameter, Vagrant will execute the provision.sh script after the VM is up and running (Figure 4).

Figure 4: Once a VM has been provisioned, the script will be ignored on subsequent reboots.

GUI Guest

While Vagrant is primarily used to create and provision headless VMs, you can also create a VM with a full blown graphical desktop. Open the Vagrantfile, scroll down, and uncomment the following lines or add them manually:

config.vm.provider "virtualbox" do |vb|
vb.gui = true
vb.memory = "1024"
end

These lines tell Vagrant to enable the VirtualBox GUI mode and allocate 1024MB of RAM to it. Once you've saved the changes, you can restart the VM. This time, it'll pop out a VirtualBox window and drop you at the shell login prompt. Even though Vagrant uses key-based authentication by default, it is a general convention to set the password for the "vagrant" user to vagrant. You can use these login credentials to log into the VM manually.

Since the VM I specified earlier is a CentOS server distribution, you'll have to pull in packages for a full-blown CentOS graphical desktop, which is fairly well-documented [5], after logging into this VM.

Cleanup

Once you've worked with your Vagrant VMs, the last order of business is to clean up the environment. Depending on your needs, you can use Vagrant to take snapshots, suspend, halt, or destroy the VMs.

If you need to verify your VM's current state before running any commands, you can get some useful output with:

$ vagrant status

Suspending the guest machine will save the machine's current running state and stop it:

$ vagrant suspend

A suspended machine can be resumed with the vagrant up command.

Halting the guest machine will shut it down pretty much like a physical computer. To turn off the machine, type:

$ vagrant halt

When you enter this command, Vagrant will first attempt to gracefully halt the machine by executing the proper commands to initiate a shutdown from within the guest machine. However, if it is unable to communicate with the machine and the shutdown sequence times out, Vagrant will forcefully shut it down.

It's also wise to take regular snapshots of the VMs, which you can then roll back to if you run into problems. To do this, use:

$ vagrant snapshot save <name>
$ vagrant snapshot restore <name>

Here <name> is the unique string to identify the snapshot. The first command creates the snapshot, while the second command restores from it (Figure 5).

Figure 5: It's a good practice to take a snapshot of the VM after making any significant change.

Finally, when you are done with a VM you can zap it from your host machine and remove all traces of it by deleting hard disks, state files, and so on with:

$ vagrant destroy

Remember that destroying a VM will cause you to lose all changes, as well as any files or folders created outside of the shared filesystem. When you now issue a vagrant up command, Vagrant will create the VM from scratch, which means it'll provision it again as well.

Conclusions

Now that you have a basic understanding of Vagrant, I hope you can see its potential. If you choose to deploy Vagrant in your production environment, you should first read Vagrant's extensive documentation section [6].

The Author

Mayank Sharma has been writing and reporting on open source software from all over the globe for almost two decades.