Configuring Vagrant and Ansible

They are powerful tools on their own, and even better when combined! In this post you’ll see how to set things up so Ansible and Vagrant can coexist in harmony. If you prefer to just grab the final file, scroll to the bottom and I’ll see you there.

Where We Are At Now

If you’re following along with the previous tutorial, Installing Vagrant and VirtualBox, you’ll have a new Vagrantfile that looks like this:

# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure(2) do |config|
  # The most common configuration options are documented and commented below.
  # For a complete reference, please see the online documentation at
  # https://docs.vagrantup.com.

  # Every Vagrant development environment requires a box. You can search for
  # boxes at https://atlas.hashicorp.com/search.
  config.vm.box = "ubuntu/trusty64"

  # Disable automatic box update checking. If you disable this, then
  # boxes will only be checked for updates when the user runs
  # `vagrant box outdated`. This is not recommended.
  # config.vm.box_check_update = false

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine. In the example below,
  # accessing "localhost:8080" will access port 80 on the guest machine.
  # config.vm.network "forwarded_port", guest: 80, host: 8080

  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  # config.vm.network "private_network", ip: "192.168.33.10"

  # Create a public network, which generally matched to bridged network.
  # Bridged networks make the machine appear as another physical device on
  # your network.
  # config.vm.network "public_network"

  # Share an additional folder to the guest VM. The first argument is
  # the path on the host to the actual folder. The second argument is
  # the path on the guest to mount the folder. And the optional third
  # argument is a set of non-required options.
  # config.vm.synced_folder "../data", "/vagrant_data"

  # Provider-specific configuration so you can fine-tune various
  # backing providers for Vagrant. These expose provider-specific options.
  # Example for VirtualBox:
  #
  # config.vm.provider "virtualbox" do |vb|
  #   # Display the VirtualBox GUI when booting the machine
  #   vb.gui = true
  #
  #   # Customize the amount of memory on the VM:
  #   vb.memory = "1024"
  # end
  #
  # View the documentation for the provider you are using for more
  # information on available options.

  # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies
  # such as FTP and Heroku are also available. See the documentation at
  # https://docs.vagrantup.com/v2/push/atlas.html for more information.
  # config.push.define "atlas" do |push|
  #   push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME"
  # end

  # Enable provisioning with a shell script. Additional provisioners such as
  # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
  # documentation for more information about their specific syntax and use.
  # config.vm.provision "shell", inline: <<-SHELL
  #   sudo apt-get update
  #   sudo apt-get install -y apache2
  # SHELL

Most of this is there for documentation purposes, so don’t be intimidated. Anything with a “#” character is a comment, and can be safely deleted if you like. I’ll remove the comments to make this post simpler while I explain it. Here’s the trimmed version:

# -*- mode: ruby -*-
# vi: set ft=ruby :

# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.

Vagrant.configure(2) do |config|

  config.vm.box = "ubuntu/trusty64"

end

Much cleaner than before :)

Allocating Resources

Now, let’s get into configuring the VM itself, which is more on the Vagrant and VirtualBox side of things. Since we’re using VirtualBox (which is by far the most common, maybe not the best), we need to configure the VirtualBox provider. We add the following after the “config.vm.box” line:

config.vm.provider "virtualbox" do |v|
    v.memory = 1024
    v.cpus   = 1
end

Here, I set my VM to use 1 GB of memory (1024 MB), and be sure to adjust it to reflect your local computer’s resources. I wouldn’t recommend anything less than 512 MB per VM and having at least 4 GB of memory on your physical system. Keeping the CPUs for all VMs set to 1 is just fine, since we’re not planning to do resource-intensive tasks.

Configuring SSH

The defaults Vagrant provides for SSH are great. So, you don’t need to do anything. I’ve listed the defaults below if you need to change something to work within your setup. Remember that Ansible is what we want to use to configure our systems, so keeping things as default and vanilla as possible is usually the way to go.

# SSH Default Configuration
# Commented because you shouldn't need to copy them.
# config.ssh.username         = "root"
# config.ssh.password         = "vagrant"
# config.ssh.port             = 22
# config.ssh.insert_key       = true
# config.ssh.private_key_path = "~/.ssh/id_rsa"
# config.ssh.forward_agent    = true

Setting Ansible as the Vagrant Provisioner

This part is where the Ansible journey will truly start. We’re telling Vagrant that we want to run a bunch of commands (called a “playbook” in Ansible), when we create the machine. Add this to your Vagrantfile:

# Provision with Ansible
config.vm.provision "ansible" do |ansible|
  ansible.playbook = "vagrant.yml"
end

That’s not a lot of information. You might have noticed we reference a file, vagrant.yml, and that’s where all the Ansible goodness is contained. You could name the vile whatever you like, just make sure you match what’s in the Vagrantfile with the actual filename.

Ansible Playbook

Create a new file in the same directory as your Vagrantfile for our first playbook. Ansible is based on a plays (sort of like tasks) that are run against a set of machines. A playbook is simply a group of plays (we only have one in this case).

---

- name: Provision Vagrant
  hosts: all
  tasks:

    - name: Say Hello
      debug: msg="Hello Ansible"

That’s it. You now have integrated Vagrant and Ansible and are ready to start it up. Boot the VM with the following command. Since we set up provisioning in the Vagrantfile, Ansible will jump in at the appropriate time and run during the creation process.

# Note: We're destroying the machine first if it exists
# Since Ansible is only run on creation by default (more on that later)
$ vagrant destroy && vagrant up

If you see output like the below, you’re ready to move on to the next lesson!

==> default: VM not created. Moving on...
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'ubuntu/trusty64'...
==> default: Matching MAC address for NAT networking...
==> default: Checking if box 'ubuntu/trusty64' is up to date...
==> default: Setting the name of the VM: vagrant_default_1436854358154_70178
==> default: Clearing any previously set forwarded ports...
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
==> default: Forwarding ports...
    default: 22 => 2222 (adapter 1)
==> default: Running 'pre-boot' VM customizations...
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: vagrant
    default: SSH auth method: private key
    default: Warning: Connection timeout. Retrying...
    default: 
    default: Vagrant insecure key detected. Vagrant will automatically replace
    default: this with a newly generated keypair for better security.
    default: 
    default: Inserting generated public key within guest...
    default: Removing insecure key from the guest if its present...
    default: Key inserted! Disconnecting and reconnecting using new SSH key...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
==> default: Mounting shared folders...
    default: /vagrant => /Users/ryan/Sites/vagrant
==> default: Running provisioner: ansible...
PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ANSIBLE_HOST_KEY_CHECKING=false ANSIBLE_SSH_ARGS='-o UserKnownHostsFile=/dev/null -o ControlMaster=auto -o ControlPersist=60s' ansible-playbook --private-key=/Users/ryan/Sites/vagrant/.vagrant/machines/default/virtualbox/private_key --user=vagrant --connection=ssh --limit='default' --inventory-file=/Users/ryan/Sites/vagrant/.vagrant/provisioners/ansible/inventory vagrant.yml

PLAY [Provision Vagrant] ****************************************************** 

GATHERING FACTS *************************************************************** 
ok: [default]

TASK: [Say Hello] ************************************************************* 
ok: [default] => {
    "msg": "Hello Ansible"
}

PLAY RECAP ******************************************************************** 
default                    : ok=2    changed=0    unreachable=0    failed=0   

Final Vagrantfile

Here’s the final Vagrantfile for your reference:

# -*- mode: ruby -*-
# vi: set ft=ruby :

# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.

Vagrant.configure(2) do |config|

  # VM Image - Ubuntu v14.04
  config.vm.box = "ubuntu/trusty64"

  # VM Resources
  config.vm.provider "virtualbox" do |v|
    v.memory = 1024
    v.cpus   = 1
  end

  # Provision with Ansible
  config.vm.provision "ansible" do |ansible|
    ansible.playbook = "vagrant.yml"
  end

end