Learning Ops Tooling, Part One - Vagrant

Learning Ops Tooling, Part One - Vagrant

I've been wanting to explore the world of "infrastructure as code", or IaC, for a while now but for one reason or another I just haven't had time (probably due to having a child to look after). Not wanting to let this go on any longer I've decided I need to make time to look into what tools are available and how to use them.

To clarify, the wiki definition for IaC is given as:

IaC is the process of managing and provisioning computer data centres through machine-readable definition files, rather than physical hardware configuration or interactive configuration tools.

When I set about learning something new I like to set myself a task to work on, to give some focus. At first I had grand plans to script the setup of my own kubernetes cluster. But after having conversations with people who know more about these things than me we decided it better to settle on something a simpler. So instead I figured why not script the setup of a server to host my blog.

This means I'll need to:

  1. Create a virtual machine on which to host the blog.
  2. Provision said server with all the dependencies needed to host my chosen blogging engine.
  3. Install and run my chosen blog engine and make sure its resilient to restarts etc.
  4. Secure my server so that it isn't vulnerable to attacks.
  5. Create back up strategy so that if something goes wrong I don't loose all my data.

And possibly some other things I haven't thought about yet.

Step one, I need a server in which to host my blog. Now I could host it on Azure or AWS. But because I'm going to be doing lots of testing, destroying and rebuilding it makes more sense to use a VM on my local machine. I can worry about pushing it to the cloud a later date. That leaves me with the choice of either creating a server using parallels or vmware fusion. Or I could, as is the aim of this little experiment, script the creation of the VM using a tool called Vagrant.

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

Installing Vagrant

Installing vagrant is a pretty simple affair. I'm on a mac so I can just use brew cask to install the vagrant package:

$ brew cask install vagrant
==> Satisfying dependencies
==> Downloading https://releases.hashicorp.com/vagrant/2.0.1/vagrant_2.0.1_x86_64.dmg
==> Verifying checksum for Cask vagrant
==> Installing Cask vagrant
==> Running installer for vagrant; your password may be necessary.
==> Package installers may write to any location; options such as --appdir are ignored.
==> installer: Package name is Vagrant
==> installer: Installing at base path /
==> installer: The install was successful.
🍺  vagrant was successfully installed!

But if you are using Windows or Linux you can follow the download instructions provided by Vagrant.

Once installed you can check it's working correctly by calling vagrant version:

$ vagrant version
Installed Version: 2.0.1
Latest Version: 2.0.1

You're running an up-to-date version of Vagrant!

There you go installed and ready to go.

Creating your first VM

Vagrant has a number of commands but the first one you want to use is vagrant init. This will create a file called VagrantFile in your working directory where you can define your VMs and how you want them setup:

$ vagrant init
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 will populate this file with a lot of information that you don't need right now so you can just go ahead and delete it. Leaving the following:

Vagrant.configure("2") do |config|

This is ruby syntax and it's within this block of code where you will define your VM.

The "2" parameter being passed into the configuration method is just telling vagrant that you want to use version 2 of the configuration file definition.

Next add the following code to define a VM

Vagrant.configure("2") do |config|
    config.vm.define "ghost" do |ghost|
        ghost.vm.box = "ubuntu/xenial64"
        ghost.vm.hostname = "ghost"
        ghost.vm.network "private_network", ip: ""

The first line tells vagrant we want to configure a new VM. We then tell it that we want to us the "ubuntu/xenial64" image and give it a host name of "ghost". Then last we tell it is to give the VM an IP address of "".

When this script runs the Ubuntu Xenial image will be pulled down from the Vagrant Cloud. This is a repository or images available for you to use and if you search for Ubuntu 16.04 you'll see the first result is ubuntu/xenial64. Vagrant caches these images locally so it doesn't have to download a copy each time the script runs.

Creating and booting the VM

To create and start the VM call the the command vagrant up in the same directory as your VagrantFile:

$ vagrant up
Bringing machine 'ghost' up with 'virtualbox' provider...
==> ghost: Importing base box 'ubuntu/xenial64'...
==> ghost: Matching MAC address for NAT networking...
==> ghost: Mounting shared folders...
    ghost: /vagrant => /Users/mat-mcloughlin/temp

This may take a few seconds, depending on if it needs to download a new image. But when it's completed you can then ssh onto the machine using vagrant ssh specifying your host name:

$ vagrant ssh ghost
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-112-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  Get cloud support with Ubuntu Advantage Cloud Guest:

0 packages can be updated.
0 updates are security updates.

WARNING! Your environment specifies an invalid locale.
 The unknown environment variables are:
 This can affect your user experience significantly, including the
 ability to manage packages. You may install the locales by running:

   sudo apt-get install language-pack-en
   sudo locale-gen en_GB.UTF-8

To see all available language packs, run:
   apt-cache search "^language-pack-[a-z][a-z]$"
To disable this message for all users, run:
   sudo touch /var/lib/cloud/instance/locale-check.skip


Now you have a VM up and running there's a couple of other commands that are useful to know about:

  • vagrant halt will stop shut down your VM.
  • vagrant destroy will destroy your VM, which can then be recreated by running vagrant up again. This means its nice and easy to recreate the VM if you break something.
  • vagrant global-status outputs status of all the Vagrant environments for this user.

And thats is, We've got a newly created VM that we can use to test our blog on before eventually pushing it to the cloud.

Bonus material

During the course of experimenting with Vagrant I came across a couple of other interesting tips.

Creating multiple machines

In your VagrantFile you can define as many machines as you like. Just keep adding code blocks:

Vagrant.configure("2") do |config|

  config.vm.define "acs" do |acs|
    acs.vm.box = "ubuntu/trusty64"
    acs.vm.hostname = "acs"
    acs.vm.network "private_network", ip: ""

  config.vm.define "web" do |web|
    web.vm.box = "nrel/CentOS-6.5-x86_64"
    web.vm.hostname = "web"
    web.vm.network "private_network", ip: ""

Running with GUI

By default vagrant runs headless, but if you need to diagnose a problem you can tell it to launch the gui by adding the following to your VagrantFile:

config.vm.provider "virtualbox" do |v|
    v.gui = true