
Vagrant Provisioning with Ansible
Provisioning Virtual Systems with Ansible Local
Vagrant supports provisioning systems with Ansible, either through executing tasks remotely from the host, or through executing tasks locally on the target system.
This tutorial uses the Ansible local provisioner (ansible_local
), so that you do not need to install Ansible on the host. Vagrant will handle downloading and installing Ansible on the virtual guest, and then run a specified playbook locally on that virtual guest.
About Ansible
Ansible is a popular remote execution tool that can replace the classical ssh-for-loop patterns. Coupled with change configuration capability, Ansible is great web application deployments or for cloud environments where orchestration is required.
Ansible is an infrastructure as code solution for AWS, Google Cloud, Azure, and other cloud services, and can interact with web interfaces (RESTful and others), and can configure network equipment. Ansible positions itself as a competitor to the shell, driving ease of use and flexibility as an alternative for automation chores.
Ansible’s configuration scripts are called playbooks, that contain a collection of tasks to run on a remote host. Ansible can group together a collection of tasks into a component called a role. A role contains its own local files, templates, variables, and metadata.
Ansible includes an inventory system to classify configurable remote hosts into groups. Ansible supports self executing dynamic inventory, where remote hosts can be dynamically organized by any mechanism of your choosing, e.g. hostname, AWS EC2 tags, GCE, Azure, Serf, Consul, Nagios, Cobbler, etc.
Prerequisites
As this tutorial is about Vagrant, you need to install Vagrant, and along with Vagrant, you need the virtual systems with Virtualbox. It may be possible to use other virtual platforms, but this can be complex, and doesn’t work consistently across macOS, Windows, and Linux.
Additionally, the instructions are work with Curl and the Bash shell. If you do not have this, you need to either run Bash, or convert the commands something similar in your shell.

Optionally, you can run Ansible on the host, but this is not required.
I created some past how-to articles that can get these and other tools on macOS, Windows, and Linux platforms.
Windows 8.1
Using Chocolatey to install the requirements:
macOS High Sierra 10.3
Using Homebrew to install the requirements:
VirtualBox and Friends on macOS
VirtualBox, Vagrant, Test Kitchen, Docker Machine, Minikube
medium.com
Fedora 28
Using native package manager Dandified YUM, or in short DNF, to install requirements:
Part I: Ansible Role with Intelligent Defaults
We’ll start with an Ansible role called hello_web
that will install Apache web server on Ubuntu, and have Vagrant bring up the guest system and run the tasks in the role.
Initial Work Area Structure
Let’s great the staging area or work area for this project.
mkdir -p ~/vagrant-ansible/provision/roles
cd ~/vagrant-ansible
touch Vagrantfile provision/playbook.yml
If you have Ansible installed on the host system, you could run this command to create the role structure:
ansible-galaxy init provision/roles/hello_web
Otherwise, we can create a basic Ansible structure with Bash commands:
mkdir -p provision/roles/hello_web/{defaults,files,tasks}
touch provision/roles/hello_web/{defaults/main.yml,tasks/main.yml}
And we’ll place our HTML content for use later:
cat <<-'HTML' > provision/roles/hello_web/files/index.html
<html>
<body>
<h1>Hello World!</h1>
</body>
</html>
HTML
Overall this will create a structure like the following:
~/vagrant-ansible
├── Vagrantfile
└── provision
├── playbook.yml
└── roles
└── hello_web
├── defaults
│ └── main.yml
├── files
│ └── index.html
└── tasks
└── main.yml
Vagrant Configuration
Now we need to create our Vagrant configuration with this Vagrantfile
:
Vagrant.configure("2") do |config|
config.vm.box = "bento/ubuntu-16.04"
config.vm.network "forwarded_port", guest: 80, host: 8086 ####### Provision #######
config.vm.provision "ansible_local" do |ansible|
ansible.playbook = "provision/playbook.yml"
ansible.verbose = true
end
end
The ansible_local
provisioner defaults are adequate for our simple role hello_web
. The site playbook, or the playbook that runs our desire roles for this guest system is simply called playbook.yml
. This will kick off everything we need to make this work.
Create the Playbook
The provisioner picks a playbook to run for the provisioning process. This is where we can select the the hello_web
role. Update provision/playbook.yml
with this:
---
- hosts: all
gather_facts: yes
become: true
roles:
- hello_web
Create the Role Defaults
Now we can start digging into the role parts. First let’s set some default variables with some intelligent defaults that make sense for a Ubuntu 16.04 Xenial Xerus system.
From within the hello_web
role, update default/main.yml
with this:
---
hello_web:
docroot: /var/www/html
package: apache2
service: apache2
We’ll reference these variables in our main tasks file for the role.
Create the Role Tasks
From within the hello_web
role, update tasks/main.yml
with this:
---
- name: "Install Web Service"
package:
name: "{{ hello_web.package }}"
state: present- name: "Start Web Service"
service:
name: "{{ hello_web.service }}"
state: started
enabled: yes- name: "Copy Web Content"
copy:
src: "{{ role_path }}/files/index.html"
dest: "{{ hello_web.docroot }}/index.html"
These tasks use three modules (analogous as resources in Chef and Puppet) of package
, service
, and copy
to create our web server and content, and their purpose should be self explanatory based on the name
value for each task to describe what the task does.
Inside each task, you’ll notice embedded variables with this notation.{{ variable }}
. This from Jinja2 templating system that is heavily used by Ansible. Any value that uses this must be quoted. You can see we referenced the default variables specified earlier.
Testing the Solution
vagrant up # download, start guest, provision
curl -i http://127.0.0.1:8086 # access content
This will give us:
HTTP/1.1 200 OK
Date: Sun, 12 Aug 2018 01:06:50 GMT
Server: Apache/2.4.18 (Ubuntu)
Last-Modified: Sun, 12 Aug 2018 01:06:38 GMT
ETag: "3c-5733296c755b3"
Accept-Ranges: bytes
Content-Length: 60
Content-Type: text/html<html>
<body>
<h1>Hello World!</h1>
</body>
</html>
Part II: Ansible Role using Extra Variables
Now let’s swap out the guest system for CentOS virtual guest. We’ll have to send some different variables to the hello_web
as the role defaults are will not work for CentOS.
Update Vagrant Configuration
Update the Vagrantfile
with this content below, which use the CentOS box by the Bento project, and adds some new variables that will be used later by the hello_web
role.
Vagrant.configure("2") do |config|
config.vm.box = "bento/centos-7.5"
config.vm.network "forwarded_port", guest: 80, host: 8086 ####### Provision #######
config.vm.provision "ansible_local" do |ansible|
ansible.playbook = "provision/playbook.yml"
ansible.verbose = true
ansible.extra_vars = {
hello_web: {
package: "httpd",
service: "httpd",
docroot: "/var/www/html"
}
}
end
end
These variables quite readable in this format. If curious, this are in a ruby symbol hash format in the Vagrantfile
. This will be converted by Vagrant to an JSON in an escaped shell string used by the ansible-playbook
command that Vagrant calls:
\{\"hello_web\":\{\"package\":\"httpd\",\"service\":\"httpd\",\"docroot\":\"/var/www/html\"\}\}
Yuk!!! Anyhow, these extra variables take the highest precedence, and we can add them as needed in a more friendly format.
Testing the Updated Solution
Let’s try the new configuration:
vagrant destroy --force # purge old environment
vagrant up # download, create guest, provision
curl -i http://127.0.0.1:8086
This will show the results for CentOS:
HTTP/1.1 200 OK
Date: Sun, 12 Aug 2018 08:43:09 GMT
Server: Apache/2.4.6 (CentOS)
Last-Modified: Sun, 12 Aug 2018 01:30:35 GMT
ETag: "3c-57332ec6c9bce"
Accept-Ranges: bytes
Content-Length: 60
Content-Type: text/html; charset=UTF-8<html>
<body>
<h1>Hello World!</h1>
</body>
</html>
Final Thoughts
I hope this helps give a introduction to Ansible and how Vagrant can be used to easily develop and test Ansible playbook and roles.
This only touched the surface of what is capable with Ansible. Vagrant Ansible provisioners have other options, such as using Galaxy roles, Host and Group variables, and more.
Ansible itself can be used to configure and orchestrate more than just systems, but also networking equipment, web interfaces, and cloud platforms.