Ansible with Vagrant on Windows

Since I’m converting all my builds and other things to use Ansible, the idea of using Ansible to customize a Vagrant box is very attractive.

I’ve chosen to use the ansible-local provisioner in this case, so that Ansible runs inside the Vagrant box.  I’ll do an example later where this isn’t the case.

Have a look at this gist for some info about how to do this.  Or read on.

Step 1 – the Vagrantfile

In a blank directory, edit a new Vagrantfile.  Make it look something like this;

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

Vagrant.configure("2") do |config|
 # The default ubuntu/xenial64 image has issues with vbguest additions
 config.vm.box = "bento/ubuntu-16.04"

 # Set memory for the default VM
 config.vm.provider "virtualbox" do |vb|
   vb.memory = "1024"
 end

 # Configure vbguest auto update options
 config.vbguest.auto_update = false
 config.vbguest.no_install = false
 config.vbguest.no_remote = true

 # Configure the hostname for the default machine
 config.vm.hostname = "ansible-example"

 # Mount this folder as RO in the guest, since it contains secure stuff
 config.vm.synced_folder "vagrant", "/vagrant", :mount_options => ["ro"]

 # And finally run the Ansible local provisioner
 config.vm.provision "ansible_local" do |ansible|
   ansible.provisioning_path = "/vagrant/provisioning"
   ansible.inventory_path = "inventory"
   ansible.playbook = "playbook.yml"
   ansible.limit = "all"
 end

end

There’s a few things going on here.  First up, we define the default box we’re going to use, the memory allocated to it, our auto-update options and the hostname.

Next up is we define a synced folder that will appear in the Vagrant box.  There is a default, which is for the folder the Vagrantfile is in to appear as /vagrant.  However, this is shared on VirtualBox with R/W access, which means that the box can modify your original files (including its own Vagrantfile).  Not necessarily bad, but I don’t like the idea of that very much.

Lastly, we define the Ansible provisioner.  This will simply run the playbook that’s in the vagrant/provisioning subfolder of the Vagrantfile against all hosts.

Step 2 – Create Playbook

Do the following to create the rest of the structure (from within the directory your Vagrantfile is in);

mkdir -p vagrant/provisioning

Now, you’ll need to create an ansible.cfg in that directory, like this;

[defaults]
host_key_checking = no

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes

The parameters are necessary to avoid Ansible having a freak-out about SSH keys and whatnot when deploying.  Of course, if you have just one host, you don’t need to worry about it.

Next, you need an inventory spec;

ansible-example ansible_connection=local

This forces deployments against the machine we’re deploying to use the local connection type.

And lastly, a really basic playbook to test it out;

---

- hosts: ansible-example
 tasks:
 - copy: content="IT WORKS!\n" dest=/home/vagrant/ansible_runs

...

Step 3 – Run it!

Now we’ve set up the most basic structure, bring it up!

$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'bento/ubuntu-16.04'...
==> default: Matching MAC address for NAT networking...
==> default: Checking if box 'bento/ubuntu-16.04' is up to date...
==> default: Setting the name of the VM: ansible-example_default_1472187535117_41803
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
 default: Adapter 1: nat
==> default: Forwarding ports...
 default: 22 (guest) => 2222 (host) (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:
 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 it's present...
 default: Key inserted! Disconnecting and reconnecting using new SSH key...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
 default: The guest additions on this VM do not match the installed version of
 default: VirtualBox! In most cases this is fine, but in rare cases it can
 default: prevent things such as shared folders from working properly. If you see
 default: shared folder errors, please make sure the guest additions within the
 default: virtual machine match the version of VirtualBox you have installed on
 default: your host and reload your VM.
 default:
 default: Guest Additions Version: 5.0.26
 default: VirtualBox Version: 5.1
==> default: Setting hostname...
==> default: Mounting shared folders...
 default: /vagrant => C:/cygwin64/home/username/ansible-example/vagrant
==> default: Running provisioner: ansible_local...
 default: Installing Ansible...
 default: Running ansible-playbook...

PLAY [ansible-example] ****************************************************************

TASK [setup] *******************************************************************
ok: [ansible-example]

TASK [copy] ********************************************************************
changed: [ansible-example]

PLAY RECAP *********************************************************************
ansible-example : ok=2 changed=1 unreachable=0 failed=0


$

And to prove that the playbook actually, really, did run;

$ vagrant ssh
Welcome to Ubuntu 16.04.1 LTS (GNU/Linux 4.4.0-31-generic x86_64)

 * Documentation: https://help.ubuntu.com
 * Management: https://landscape.canonical.com
 * Support: https://ubuntu.com/advantage
Last login: Fri Aug 26 05:01:58 2016 from 10.0.2.2
vagrant@ansible-example:~$ cat ansible_runs
IT WORKS!
vagrant@ansible-example:~$

You can then re-run the playbook any time you like with vagrant provision .

The main catch with running Ansible like this is that it actually installs Ansible on the Vagrant box.  You can get around this by running Ansible on your Vagrant host.  More on this later.