17 May 2017 · Software Engineering

    Getting Started with Vagrant

    12 min read
    Contents

    Vagrant is a development environment automation tool. It accomplishes this by leveraging virtual machines with VirtualBox, VMWare, or cloud providers like AWS. It’s primarily designed to standardize environments across platforms. However, it’s not limited to that. Vagrant can be especially useful for cross-platform automated tests. By the end of this tutorial, you’ll be ready to start using Vagrant. Let’s start with an overview. We’ll cover how Vagrant works and we’ll show some example use cases.

    Overview and Use Cases

    Vagrant is a tool for building and managing virtual machine environments in a single workflow. With an easy-to-use workflow and focus on automation, Vagrant lowers development environment setup time, increases production parity, and makes the “works on my machine” excuse a relic of the past.

    This quote comes straight from the Vagrant website. Let’s unpack this a bit and discuss what Vagrant is, how it works, and who it’s for.

    Vagrant makes it easy to create reproducible virtualised environments. Machines are provisioned on top of VirtualBox, VMWare, AWS, or any other provider. Vagrant includes support for configuration management tools like Chef or Ansible. Naturally you can use simple shell scripts to automatically install and configure software as well. All of this is defined in a Vagrantfile.

    The Vagrantfile defines the “box”, VM customizations (like memory and networking settings), and which provisioners to run. The “box” is a base image. This may be a VirtualBox image or an AMI (Amazon Machine Image) on AWS. Essentially, this is where you decide the operating system. Then, you use the various provisioners to set everything up. Once you have everything in the Vagrantfile, you’re ready to go. Now anyone can recreate the same environment with a few short commands.

    These features are useful in modern teams. Engineers can define a Vagrantfile that installs all the programming language tools and databases for their work. Then, they can be distribute it amongst them, so that everyone can work in the same environment of their OS. DevOps teams can use Vagrant to spin up multiple VMs running different Linux distributions to test configuration management in different systems. It also makes it easy to create disposable environments to experiment with different technologies. It’s also possible to package up your entire product in a Vagrantfile and give that to your designers or product owners, as a way to experiment with the product.

    Here are some real world examples. The open source Mastodon social network uses Vagrant to create a development environment for its contributors. The DCOS Vagrant project uses Vagrant to create a local test environment. All in all, Vagrant is generally designed to make it as easy as possible to create and distribute virtualised environments.

    This encapsulation makes Vagrant a useful tool when testing various infrastructure project. Here’s an example. Let’s say that you’re working with a configuration management system like Ansible to install and configure MySQL on different Linux distributions. Vagrant can be used in the test process to spin up VMs for each distribution and run the configuration management. Using vagrants gives you support for cloud instances as well local VMs in this scenario to create a kitchen.ci type solution.

    Vagrant supports building VirtualBox images. Packer is another tool from HashiCorp that supports creating Vagrant images from other platforms. Images may be used for creating a pre-packaged version of your application you can hand off to your design team, or building common development environments for different teams.

    Now, let’s get started working with Vagrant.

    Building Development Environments

    Creating development environments is the most common use case. This generally involves choosing a Linux distribution, installing various stack components, and starting the required databases. We’ll do just that in this example. We’ll start from the beginning by choosing a Linux distribution.

    Vagrant Cloud is a public box repository. You’ll find official and user contributed boxes there. Feel free to search around and see what’s on offer. We’ll one of the Fedora boxes for this exercise. The first step is to initialize a new Vagrantfile. This happens with vagrant init BOX. This command creates a skeleton Vagrantfile in the current directory.

    $ vagrant init fedora/25-cloud-base
    A Vagrantfile has been placed in this directory. You are now
    ready to vagrant up your first virtual environment! Please read
    the comments in the Vagrantfile as well as documentation on
    vagrantup.com for more information on using Vagrant.

    vagrant init also includes a helpful message on what to do next. We can start the VM by running vagrant up, then we can ssh into it, and we’ll have a functional Fedora environment.

    $ vagrant up

    The default Vagrantfile uses VirtualBox by default. This command downloads the box (if you don’t already have it), creates the VM, and starts it. The first time takes a few minutes.

    Let’s build a simple Node.js application to get a feel for how Vagrant works. We’ll need to install the nodejs packages at a minimum. This happens through provisioners. Vagrant supports multiple provisioners. Shell scripts are the easiest for this tutorial. Open up the generated Vagrantfile. Note that this file is written in Ruby. You’ll see a commented out section defining an inline:

    # config.vm.provision "shell", inline: <<-SHELL
    #   dnf update
    #   dnf install -y apache2
    # SHELL

    Let’s replace that script with the necessary commands to install Node.js on Fedora. Luckily Node.js is part of the default package repository. All we need to do is update the local index and install the package.

    config.vm.provision "shell", inline: <<-SHELL
    dnf update -y
    dnf install -y nodejs curl
    SHELL

    Now, run vagrant provision. This runs all the provisioner defines in the Vagrantfile.

    $ vagrant provision

    This step may take some time to sync the package index and install everything. But that’s all there is to it. Let’s write a small Hello World Node.js application. Save this file server.js in the same directory as the Vagrantfile:

        var http = require('http');
    
        // Configure our HTTP server to respond with Hello World to all requests.
        var server = http.createServer(function (request, response) {
          response.writeHead(200, {"Content-Type": "text/plain"});
          response.end("Hello World\n");
        });
    
        // Listen on port 8080, IP defaults to 127.0.0.1
        server.listen(8080);

    Vagrant also does something very important by default. It mounts the directory with the Vagrantfile at /vagrant in the virtual machine. This makes it very easy to share files between the guest and host. You can use your editor, IDE, or whatever tools you like on your system, and complete all development activities inside the guest. You can manually sync file system changes with:

    $ vagrant rsync

    Now ssh into the VM and start the server:

    $ vagrant ssh
    $ cd /vagrant
    $ nodejs server.js

    SSH in again in another terminal and curl localhost:

    $ vagrant ssh
    $ curl localhost:8080
    Hello World

    We can see the process is running correctly. It would be great if we could speed up this workflow by removing the need for vagrant rsync and exposing guest port 8080 to the host. With some setup, Vagrant can do this as well. This requires the VirtualBox guest editions. The process to configure a guest varies by distribution. Luckily, there is a Vagrant plugin to automate the process on the common platform. Install it via vagrant plugin:

    $ vagrant plugin install vagrant-vbguest

    Now we can fire off a command to automatically configure the guest:

    $ vagrant vbguest

    Now we need to override a setting in the debian/jessie Vagrant box. It uses an rsync synced folder out of the box. This works regardless of weather the guest additions are installed or not. This is a bit inconvenient because we need to run vagrant rsync after each change. Luckily, there is a special virtualbox synced folder that handles real-time bi-directional system changes. Open up the Vagrantfile and add the following:

    config.vm.synced_folder ".", "/vagrant", type: :virtualbox

    Then restart the VM:

    $ vagrant reload

    You’ll see output that looks similar to:

    ==> default: Machine booted and ready!
    [default] GuestAdditions 5.1.14 running --- OK.
    ==> default: Checking for guest additions in VM...
    ==> default: Mounting shared folders...
        default: /vagrant => ~/semaphore-community/code/getting-started-with-vagrant

    Let’s take our new and improved development environment for a spin. Code changes to the server.js on the host should automatically reflect in /vagrant in the guest. Make a change to the response.end line in the server.js on your host. Here’s an example:

    response.end("Hello World from Vagrant\n");

    Now, type vagrant ssh and cat /vagrant/server.js. You’ll see the changes reflected. You can repeat this exercise by updating server.js in the guest and reading in the host.

    Next let’s forward guest port 8080 to host port 8080. Vagrant supports port forwarding once the guest editions are installed. All we need to do is add the appropriate line to the Vagrantfile:

    config.vm.network "forwarded_port", guest: 8080, host: 8080

    Now, vagrant reload again to apply the changes. Start the server inside the VM:

    $ vagrant ssh -c 'nodejs /vagrant/server.js'

    Next, curl localhost from a separate terminal on host:

    $ curl localhost:8080
    Hello World from Vagrant

    We now have a fully functional and automated development environment we can interact with from the host. It’s easy to extend this simple setup with more Vagrant features. You should try to repeat this exercise from scratch after stopping and deleting the VM.

    # Shutdown the VM
    $ vagrant halt
    # Delete the VM
    $ vagrant destroy -f
    # Create and provision the vm
    $ vagrant up --provision

    The most useful next step is to learn about all the different provisioners. Shell scripts are the easiest to start out with, but they won’t help you with complex tasks. Here are some links:

    It’s surprisingly easy to get going with Vagrant. You should feel confident enough to start using Vagrant. Let’s switch into more advanced features.

    Multiple Machines

    Our sample Vagrantfile declares one implicit VM. It’s possible to declare multiple VMs for more complex setups. Here’s a skeleton example that creates 2 VM’s, one for a web server and another one for MySQL server.

    Vagrant.configure("2") do |config|
    	config.vm.define "web" do |web|
    		web.vm.box = "apache"
    	end
    
    	config.vm.define "db" do |db|
    		db.vm.box = "mysql"
    	end
    end
    

    This feature is especially useful when dealing with distributed systems. Here are some example use cases:

    • Test primary/replica configuration in MongoDB,
    • Create a multi node Kubernetes cluster,
    • Create independent VMs for a Service Oriented Archiecture or microservices system,
    • Experiment with different networking failure modes between hosts, and
    • Increase development/production parity.

    For complete information, you can check out multiple machines documentation.

    Cloud Infrastructure

    Vagrant uses VirtualBox by default, but it’s not limited to local VMs. There are cloud providers like AWS. This is useful when you’re dealing with setups that don’t run locally or you do not have enough local compute resources for the intended application. Note that when using the different providers, all functionality may not work the same. Here are some examples. We covered how Vagrant syncs filesystem changes between the host/guest. This is not possible on AWS because there is no shared file system. Instead, you’d need to vagrant rsync. Also forwarded ports are irrelevant because there is no local guest. Instead you’d use the public IP address and port to access network services.

    Here is an example from the vagrant-aws docs. Note that everything is still configured in the Vagrantfile, just with different syntax:

    Vagrant.configure("2") do |config|
    	config.vm.box = "dummy"
    
    	config.vm.provider :aws do |aws, override|
    		aws.access_key_id = "YOUR KEY"
    		aws.secret_access_key = "YOUR SECRET KEY"
    		aws.keypair_name = "KEYPAIR NAME"
    
    		aws.ami = "ami-7747d01e"
    
    		override.ssh.username = "ubuntu"
    	end
    end
    

    You can find out more about providers from the official documentation.

    Grab Bag

    There are a few more things we should cover together before wrapping up this introduction. Vagrant is a mature and stable development platform, thus it includes some features you may not expect. Let’s look at them now.

    • vagrant package: Package the Vagrant machine into a box. It’s trivial to build new images. This is only supported on VirtualBox. You can use this functionality to build complete environments (think as a topic environment) and push it out. Others can pull down the box and get going right away.
    • vagrant share and vagrant connect: These two commands expose your Vagrant environment to anyone in the world. Think of the remote pairing and debugging possibilities. You may also use this expose an internal web application to anyone with a globally accessible URL.
    • vagrant snapshot: Manage VM snapshots — great for playing around with quick rollbacks.
    • vagrant plugin: Manage plugins. We saw the vguest plugin earlier in the tutorial. The community provides many useful plugins for a variety of use cases.

    Wrap Up

    This concludes the tutorial. Let’s recap what we covered together:

    • Vagrant is a tool for automating and managing virtualised environments,
    • Everything happens through the vagrant command,
    • The Vagrantfile holds all configuration, don’t forget tocommit this to your source control,
    • Vagrant is a great tool for managing platform-independent development environments,
    • You can configure the VM with simple shell scripts, executable files, or more complex configuration management systems,
    • Remember to install the guest additions (vb-guest plugin) to get the most useful features (like port forwarding and bi-directional filesystem sync),
    • The Vagrantfile may declare multiple VMs,
    • Vagrant supports creating VMs on various cloud providers (find a plugin for your cloud),
    • vagrant package can create images for VirtualBox, and
    • vagrant share and vagrant connect allow remote collaboration through Vagrant environments.

    Hopefully you’ve learned enough to start using Vagrant or are ready to get going when you see it mentioned in a README. So, good luck out there, and happy shipping! If you have any questions or comments, feel free to leave them in the section below.

    Leave a Reply

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

    Adam Hawkins
    Writen by:
    Traveller, trance addict, automation, and continuous deployment advocate. I lead the SRE team at saltside and blog on DevOps for Slashdeploy. Tweet me @adman65.