- Environment Provisioning with AutoLab
- Environment Provisioning with Vagrant
- Creating Images with Packer
- Managing Source Code
- Summary
- References
Environment Provisioning with Vagrant
Vagrant is an environment provisioning system created by Mitchell Hashimoto and supported by his company, HashiCorp. Vagrant can help you quickly bring up VMs according to a pattern defined in a template file known as a Vagrantfile. Vagrant can run on Windows, Linux, and OS X (Mac) operating systems and supports popular desktop hypervisors such as VMware Workstation Professional, VMware Fusion Professional, and VirtualBox. Cloud providers such as Rackspace and Amazon Web Services can be used as well for your test environment. The Vagrant examples in this book are based on the VMware Fusion plugin, but the examples we provide can, with a few modifications, be used for other hypervisors and for cloud platforms. If you will be following along with the examples in this chapter, make sure to create a new directory for each Vagrantfile that you create.
After installing Vagrant and the VMware Fusion or Workstation plugin, you need to find a Vagrant box to use with the system. A Vagrant box can be thought of as a VM template: a preinstalled operating system instance that can be modified according to the settings that you specify in your Vagrantfile. Vagrant boxes are minimal installations of your desired operating system with some boxes not being more than 300 MB. The idea is to present a bare-bones operating system that is completely configured by your automation tool of choice.
Some box creators opt to include the binaries for popular Vagrant-supported provisioners like Puppet, Chef, and so on, but other box creators do not, and users need to use the shell provisioner to deploy their favorite configuration management solution before it can be used. The box creation process is beyond the scope of this book. However, if you would like to develop your own boxes, there are tools like veewee and Packer (discussed later in this chapter) available that you can try out.
In previous versions of Vagrant, you had to specify the URL of the box file that you want to use when you initialized your vagrant environment:
vagrant init http://files.vagrantup.com/precise64_vmware.box
If the box file is located on your computer, you can specify the full path to the file instead of a URL.
Starting with Vagrant version 1.5, HashiCorp introduced the Atlas system (formerly known as Vagrant Cloud), an online repository that Vagrant will search for boxes if you use the account and box name of an image stored on its site:
vagrant init hashicorp/precise64
It is good to know both types of syntax because it will be necessary to use the old method for any boxes not hosted on Atlas. The online site is a great place to search for boxes for various operating systems instead of building your own.
The vagrant init command will automatically create a simple Vagrantfile that will reference the box that you specify. Listing 3-1 shows the default Vagrantfile that is generated with the command listed above.
Listing 3-1 Default Vagrantfile
# -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "hashicorp/precise64" end
You’ll notice that to save space I remove the comments that automatically get generated when you initialize your Vagrantfile. However, if you look in the comments, you’ll see some helpful tips for using configuration management technology to make automated changes to your VM when it boots. As of today, the available options for provisioning your VM include basic shell scripts and configuration management tools such as Puppet, Chef, and Ansible. This is an immense value because your development and test environment can be stood up with the exact same settings that are used in your production deployments. This should cut down on the “well, it worked on my laptop” discussions that may go back and forth during a deployment mishap. Docker support was also added so that the provisioner could install the Docker daemon automatically and download the containers that you specify for use.
With your Vagrantfile in place, you can now boot your first test environment by using the following command:
vagrant up --provider=vmware_fusion
You can now log in to the VM using the following command:
vagrant ssh
Take a look at a folder in your VM called /vagrant. You’ll see that it contains your Vagrantfile! It’s a shared folder that is automatically created for the VM so that you can easily transfer files to and from your desktop without having to use SCP, FTP, and so on.
If you examine the operating system resources, you’ll notice that you have one vCPU and 512 MB of RAM. This may not be sufficient for the application that you want to run. So, we will take a look at how to modify the resources allocated to your Vagrant VM.
First, let’s destroy this VM so that we can move on with the other configuration options. You can do this exiting the VM and then using the following command:
vagrant destroy
Vagrant will ask you to confirm that you really want to destroy this VM. Alternatively, you can use the -f option to skip that confirmation.
Listing 3-2 shows that Vagrant can modify the VM’s VMX file to make the changes that we need. We use the config.vm.provider block of code to achieve this. By the way, the memsize attribute’s units are megabytes. Notice that we are creating an object named v enclosed in vertical lines to change settings just for this VM. This object name has local scope only to this config.vm.provider statement, and it can be used again when defining other VMs, as you’ll see in later examples. After executing vagrant up, the VM will be created with the desired attributes. At the time of this writing, the size and number of virtual disks cannot be controlled, but your Vagrant VMs will start with 40 GB of thin-provisioned storage.
Listing 3-2 Changing Default Vagrantfile
# -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "hashicorp/precise64" config.vm.provider :vmware_fusion do |v| v.vmx["memsize"] = 1024 v.vmx["numvcpus"] = 2 end end
It is great that we can modify the VM’s resources. What about a more complex setup, like multiple VMs? Vagrant supports such a topology as well. Of course, make sure that you have sufficient CPU cores and RAM to support the topology that you want to use! Multi-VM setups would be useful for testing realistic deployments with a separate database server and front-end server, for example. Listing 3-3 shows an example of a multi-VM Vagrantfile setup.
Listing 3-3 Multimachine Vagrantfile
# -*- mode: ruby -*- # vi: set ft=ruby : # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.define :first do |vm1| vm1.vm.box = "hashicorp/precise64" vm1.vm.hostname = "devops" vm1.vm.provider :vmware_fusion do |v| v.vmx["memsize"] = 1024 v.vmx["numvcpus"] = 2 end end config.vm.define :second do |vm2| vm2.vm.box = "hashicorp/precise64" vm2.vm.hostname = "vmware" vm2.vm.provider :vmware_fusion do |v| v.vmx["memsize"] = 1024 v.vmx["numvcpus"] = 2 end end end
The deployment utilizes multiple config.vm.define blocks of code: one for each VM that we are creating. :first and :second are labels that Vagrant will use to identify the two VMs when you run commands like vagrant status. These labels will also be used to connect to the VMs via Secure Shell (SSH)—for example, vagrant ssh first. If you’re familiar with Ruby, you’ll notice that these labels are Ruby symbols. The names in the enclosed pipe symbols (for example, |vm1|) denote the object whose information that vagrant is using to build and define your VM. The object name can be the same as the symbol (for example, first.vm.box...), but it doesn’t have to be.
Using this syntax can be a bit tedious when you want to deploy more than two VMs. Thankfully, because Vagrant is written in Ruby, you can use the language’s features such as lists, loops, and variables to optimize your Vagrantfile code. Listing 3-4 shows some optimization tips that I learned from Cody Bunch and Kevin Jackson in their OpenStack Cloud Computing book
Listing 3-4 Optimized Multimachine Vagrantfile
# -*- mode: ruby -*- # vi: set ft=ruby : servers = ['first','second'] Vagrant.configure("2") do |config| config.vm.box = "hashicorp/precise64" servers.each do |hostname| config.vm.define "#{hostname}" do |box| box.vm.hostname = "#{hostname}.devops.vmware.com" box.vm.provider :vmware_fusion do |v| v.vmx["memsize"] = 1024 v.vmx["numvcpus"] = 2 end end end end
At the top of the file, I create a Ruby list called servers whose elements are the names of the VMs that I want to create. Then I use the Ruby list iterator called each to loop the execution of the VM definition for each element in the servers list. If we ever want to increase the number of VMs that are deployed, we just add more entries to the list. Not every VM needs to have the same set of resources, and we can use if statements within the box.vm.provider code block to be selective:
if hostname == "first" v.vmx["memsize"] = 3128 v.vmx["numvcpus"] = 4 elsif hostname == "second" v.vmx["memsize"] = 1024 end
There are many more features in Vagrant that we will not be covering in this book, but with just these simple commands, you can build the test environment setups that we will be using in this book. If you’d like to learn more about Vagrant, be sure to check out the Vagrant website (http://www.vagrantup.com) and Mitchell’s book, Vagrant: Up and Running.