Vagrant and Ansible for Dev Machines
Start with the Vagrantfile
Vagrant is configured with a Vagrantfile
. Here I’m setting the simplest options – which box to use, a name for the box, any directories I want to mount, and then the instruction to use ansible for the rest. Here’s that basic config:
Vagrant.configure("2") do |config| config.vm.box = "ubuntu/xenial64" config.vm.network :private_network, ip: "192.168.121.8" config.vm.synced_folder '.', '/vagrant' config.vm.provider "virtualbox" do |vb| vb.name = "example-app" end # # Run Ansible from the Vagrant Host # config.vm.provision "ansible" do |ansible| ansible.playbook = "playbook.yml" ansible.host_vars = { "default" => { "ansible_python_interpreter" => "/usr/bin/python2.7" } } end end
This will bring up a basic Ubuntu 16.04 box (my preferred distro although it shipped with Python 3 rather than 2 which is why I need the ansible_python_interpreter
setting and why I need to set the box name as by default the early versions of this box had a hardcoded name which broke things when you used it as the basis of more than one VM) and then hand off to ansible to do everything else.
Ansible To Make Technology Dance
There are a few different tools that would fit here but I’ve picked ansible for my VMs and so far I have found it quite approachable. In particular its declarative approach makes it easy to iteratively build up a machine by adding to the ansible recipe and reprovisioning repeatedly rather than always having to destroy the machine and start completely from scratch to test changes.
In the Vagrantfile
example above, we set the provisioner to be ansible and give the playbook parameter as playbook.yml
. The playbook is the recipe for cooking up the virtual machine you desire: it can be used to install packages, restart services, run commands, or really anything else we need to do to get the platform we need.
Rather than using the commands we already know, and typing them all in turn though, ansible has neat wrappers for almost all the tasks we could want to do. You’ll probably want to refer to the excellent documentation for more information on the various modules that are in these examples and for others you want to use yourself.
Here’s a quick cheatsheet of some of the patterns from my playbook.yml
:
First-few-lines boilerplate
--- - hosts: all gather_facts: no tasks: - name: apt-get update raw: sudo apt-get update -qq - name: Install python 2.7 raw: sudo apt-get install -qq python2.7 - name: Fix hostname to allow sudo remote_user: root become: yes lineinfile: dest=/etc/hosts line='127.0.0.1 ubuntu-xenial' owner=root group=root mode=0644
Some of this is specific to the box that I had some issues with – since Python 2.7 isn’t installed by default, currently ansible requires this older version of Python and so I found I needed to disable the fact-gathering and then install the required python to get the process started. The second block teaches the vagrant box its name which prevents an annoying warning when sudoing things.
Install an apt package
- name: Install git remote_user: root become: yes apt: pkg=git
The syntax for setting which user to use changed between versions of ansible, this is the currently-correct syntax but you’ll see lots of examples using the older ones too so if you’re copying/pasting examples and having issues, check this. Also if my examples aren’t working, check the comments for errata and updates – and if you don’t find what you’re looking for, add a comment yourself!
When installing apt packages, you can also give a comma-separated list:
- name: Install Node + npm remote_user: root become: yes apt: pkg=nodejs,npm,nodejs-legacy
Running arbitrary shell commands
We can use the shell module to run commands:
- name: Install node worker dependencies remote_user: root become: yes shell: npm install args: chdir: /vagrant/src/worker/
For many tasks, there are ansible wrappers (check out the files modules for example) already available which are recommended and stop the command from being blindly repeated on every provision.
For example the following block is from a project where I’m compiling couchdb from scratch:
- name: Compile CouchDB remote_user: root become: yes shell: ./configure --disable-docs && make args: chdir: /vagrant/couchdb creates: bin/couchjs
By using the creates
property, I can tell ansible how to tell if this tool has already been compiled – it stops it from repeating the compile every time.
Technology-specific package managers
There are wrappers for all the dependency managers as well, here are two examples where I’m installing dependencies for npm and for pip (because polyglot is cool!)
- name: Install Grunt remote_user: root become: yes npm: name=grunt-cli global=yes - name: Install Requestbin dependencies pip: requirements=/vagrant/requestbin/requirements.txt
Working with services
If you change configuration and need to restart your webserver or any other service, there’s a command for that too
- name: Restart RabbitMQ remote_user: root become: yes service: name=rabbitmq-server state=restarted
These examples are my reference library for my next project – if they’re helpful to you too, then I’m delighted :) Comments are welcome!
You should try ansible galaxy rather than writing your own roles from scratch.
You can automatically run [code]apt-get update[/code] as part of the Node installation if you specify two extra parameters to the [code]apt[/code] module:
[code]
update_cache: yes
cache_valid_time: 86400
[/code]
If the cache is older than [code]cache_valid_time[/code] seconds, Ansible will automatically fetch the latest package information before installing the packages. It’s also a bit nicer than running [code]apt-get update[/code] as a raw command because if you run the playbook multiple times, the package cache will only be updated once. I think you will need to omit [code]gather_facts[/code] though in order for it to work.
Hi Lorna Jane,
Thanks for this excellent post. I use Ansible for managing host configuration in production. Today, I was just about to set out on some fresh development for which I wanted to use Vagrant to spin up an Ubuntu VM on my MacBookPro. But I was stuck because I did not want one set of box build config ( VagrantFile ) for dev and then another set ( Ansible ) for Prod. What you’ve shared here is a perfect match.
( when Google first showed my LornaJane website, I thought, “do those Yoga people code ??” :)
Thanks for sharing.
PS: I would suggest replacing ‘ansible’ provisioner with ‘ansible_local’ . Then there’s not dependency that ansible is installed on the Vagrant Host machine. Vagrant will automatically install it on the Guest you are creating.