Ansible and Vagrant: setup your testing lab in minutes!

Introduction
Looking at the upcoming sunset of OS1 platform and moving around different customer's sites, with all the network troubles you may expect, having a fast and always reliable way for creating a test lab could be very helpful.
As you know Vagrant (from Hashicorp) established itself as one of the best tool for handling vm on development/testing environments.
In this document I'll describe the few necessary steps for enabling use of Ansible with a Vagrant box (also with multiple boxes).
Please note: I won't introduce you Ansible provisioning via VagrantFile, instead I'll show the usage of Ansible's Vagrant Dynamic Intentory.
Prerequisites: Choose a Vagrant box
The first step is to choose the right vagrant box, no matter what you're trying to setup/test/troubleshoot/configure.. You have to use a valid, small, reliable vagrant box.
Unfortunately we (as Red Hat) don't ship any RHEL as vagrant (apart from OSE all-in-one). For that matter you may use centos, freely available through Hashicorp cloud store:
Vagrant box centos/7 | Atlas by HashiCorp
By the way you may also use a RHEL boxes, many colleagues (me included) has written some articles to get a RHEL vagrant box set-up in minutes, please take a look at the following articles to gain more information:
How to create RHEL based Vagrant boxes (authored by me)
PLEASE NOTE:
*) In the next step I'll use a RHEL7 box created using my tutorial.
*) I'm supposing you'll use a Fedora workstation, but I can bet that this should work also on OSX if you managed getting to work Vagrant and Virtualbox.
Vagrant configuration and setup
First of all we need to create a VagrantFile for starting one or multiple instances.
I've just written an example VagrantFile for a multi-instance setup:
[alex@freddy test_vagrant]$ cat Vagrantfile
# -*- 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
# Every Vagrant development environment requires a box. You can search for
# boxes at https://atlas.hashicorp.com/search.
config.vm.define "webserver" do |webserver|
webserver.vm.box = "rhel72"
webserver.vm.provider :libvirt do |libvirt|
libvirt.memory = 1024
end
end
config.vm.define "database" do |database|
database.vm.box = "rhel72"
database.vm.provider :libvirt do |libvirt|
libvirt.memory = 1024
end
end
end
I'll not focus on details about the Vagrant keywords, anyway as you can see I've defined two instances of which one be two a webserver and the other a database.
I'm using as base box for the two instances the "rhel72" box [1], using the "libvirt" provider and 1GB of memory.
After defining the VagrantFile, we can check that is all ok by running:
[alex@freddy test_vagrant]$ vagrant status
Current machine states:
webserver not created (libvirt)
database not created (libvirt)
This environment represents multiple VMs. The VMs are all listed
above with their current state. For more information about a specific
VM, run `vagrant status NAME`.
And then try to starting the instances by running:
[alex@freddy test_vagrant]$ vagrant up
Bringing machine 'webserver' up with 'libvirt' provider...
Bringing machine 'database' up with 'libvirt' provider...
==> database: Creating image (snapshot of base box volume).
==> webserver: Creating image (snapshot of base box volume).
==> database: Creating domain with the following settings...
==> webserver: Creating domain with the following settings...
==> database: -- Name: test_vagrant_database
==> webserver: -- Name: test_vagrant_webserver
==> database: -- Domain type: kvm
==> webserver: -- Domain type: kvm
...
After running some "vagrant ssh" against the single instances to check if all it's working as expected, we can now focus on the Ansible integration.
First of all we need to download the latest (Ansible's Vagrant script):
[alex@freddy test_vagrant]$ wget https://raw.githubusercontent.com/ansible/ansible/devel/contrib/inventory/vagrant.py
Please note that what I'll introduce you is not the Ansible provisioning via VagrantFile [4], but the usage of Ansible's Vagrant Dynamic Intentory [3].
Using this Dynamic Inventory you can test your existing playbook without any modification and/or use with Ansible. You will use your vagrant instances like any other vm/baremetal hosts.
Give to the script the execute permission and try it! You should run it inside your current vagrant folder, the path where you stored your VagrantFile and where you run "vagrant *" commands.
[alex@freddy test_vagrant]$ chmod +x vagrant.py
[alex@freddy test_vagrant]$ vagrant.py --list all
{"vagrant": ["webserver", "database"], "_meta": {"hostvars": {"webserver": {"ansible_ssh_host": "192.168.121.171", "ansible_ssh_port": "22", "ansible_ssh_user": "vagrant", "ansible_ssh_private_key_file": "/home/alex/Projects/Ansible/test_vagrant/.vagrant/machines/webserver/libvirt/private_key"}, "database": {"ansible_ssh_host": "192.168.121.105", "ansible_ssh_port": "22", "ansible_ssh_user": "vagrant", "ansible_ssh_private_key_file": "/home/alex/Projects/Ansible/test_vagrant/.vagrant/machines/database/libvirt/private_key"}}}}
As you can see the two machines are automatically placed under "vagrant" group, remember that while we'll try to run an Ansible Playbook.
Pay attention that Ansible expect the Dynamic Inventory's output to be a JSON blob. So in case you're thinking of writing up a custom dynamic inventory, keep it in mind!
If your output looks like the one I showed you, then you can consider the script working.
For easily executing the dynamic inventory script you can place it in one of your PATH.
Let's try it with Ansible:
[alex@freddy test_vagrant]$ ansible all -i ./vagrant.py -m ping
database | SUCCESS => {
"changed": false,
"ping": "pong"
}
webserver | SUCCESS => {
"changed": false,
"ping": "pong"
}
As you can see, we've chosen to run Ansible's module "ping" against "all" hosts using a dynamic "inventory" called "vagrant.py".
Or maybe we can just run one command on a single host:
[alex@freddy test_vagrant]$ ansible webserver -i ./vagrant.py -m command -a "uptime"
webserver | SUCCESS | rc=0 >>
09:34:20 up 7 min, 1 user, load average: 0.00, 0.03, 0.05
Default inventory: vagrant.py
Setting the default dynamic inventory can be done by editing /etc/ansible/ansible.cfg
[alex@freddy test_vagrant]$ cat /etc/ansible/ansible.cfg |grep inventory
#inventory = /etc/ansible/hosts
inventory = /home/alex/bin/vagrant.py
# if inventory variables overlap, does the higher precedence one win
# These values may be set per host via the ansible_module_compression inventory
That's all, let's try an Ansible's playbook now!
Test: Download and execute a playbook
Just for testing the Dynamic Inventory script I wrote one sample playbook for:
- subscribing the systems to RHN
- enabling required repositories
- updating to latest packages to the latest versions
You'll find below the playbook that we'll use for registering the vagrant instances to RHN and for updating the systems.
[Note: Don't forget to change RHN_username, to your RHN user!]
[alex@freddy test_vagrant]$ cat init.yml
- hosts: vagrant
vars:
RHN_username: aarrichi@redhat.com
RHN_poolname: .*Employee.*
vars_prompt:
- name: "RHN_password"
prompt: Please input RHN password
tasks:
- name: Checking if it's already registered to RHN
become: yes
ignore_errors: True
shell: "subscription-manager status"
register: result
- name: Subscribe to RHN
become: yes
redhat_subscription: username= password= pool=
when: result.rc|int > 0
- name: Enable chosen repositories
become: yes
command: "subscription-manager repos --disable '*' --enable rhel-7-server-rpms --enable rhel-7-server-optional-rpms --enable rhel-7-server-extras-rpms"
- name: Update the system
become: yes
yum: state=latest name='*'
Let's execute the playbook for registering our vagrant instances:
[alex@freddy test_vagrant]$ ansible-playbook init.yml
Please input RHN password:
PLAY [vagrant] *****************************************************************
TASK [setup] *******************************************************************
ok: [database]
ok: [webserver]
TASK [Checking if it's already registered to RHN] ******************************
fatal: [database]: FAILED! => {"changed": true, "cmd": "subscription-manager status", "delta": "0:00:02.471373", "end": "2016-06-23 11:37:50.194596", "failed": true, "rc": 1, "start": "2016-06-23 11:37:47.723223", "stderr": "", "stdout": "+-------------------------------------------+\n System Status Details\n+-------------------------------------------+\nOverall Status: Unknown", "stdout_lines": ["+-------------------------------------------+", " System Status Details", "+-------------------------------------------+", "Overall Status: Unknown"], "warnings": []}
...ignoring
fatal: [webserver]: FAILED! => {"changed": true, "cmd": "subscription-manager status", "delta": "0:00:02.465741", "end": "2016-06-23 11:37:50.790684", "failed": true, "rc": 1, "start": "2016-06-23 11:37:48.324943", "stderr": "", "stdout": "+-------------------------------------------+\n System Status Details\n+-------------------------------------------+\nOverall Status: Unknown", "stdout_lines": ["+-------------------------------------------+", " System Status Details", "+-------------------------------------------+", "Overall Status: Unknown"], "warnings": []}
...ignoring
TASK [Subscribe to RHN] ********************************************************
changed: [webserver]
changed: [database]
TASK [Enable chosen repositories] **********************************************
changed: [webserver]
changed: [database]
TASK [Update the system] *******************************************************
changed: [webserver]
changed: [database]
PLAY RECAP *********************************************************************
database : ok=5 changed=3 unreachable=0 failed=0
webserver : ok=5 changed=3 unreachable=0 failed=0
If all is gone ok, we can proceed to the next step, download a sample ansible playbook and run it!
We can download "ansible-examples" github's project:
[alex@freddy test_vagrant]$ git clone https://github.com/ansible/ansible-examples
Cloning into 'ansible-examples'...
remote: Counting objects: 2235, done.
remote: Total 2235 (delta 0), reused 0 (delta 0), pack-reused 2235
Receiving objects: 100% (2235/2235), 3.81 MiB | 360.00 KiB/s, done.
Resolving deltas: 100% (641/641), done.
Checking connectivity... done.
[alex@freddy test_vagrant]$ ll ansible-examples/lamp_simple_rhel7/
total 24
drwxrwxr-x. 2 alex alex 4096 Jun 23 15:29 group_vars
-rw-rw-r--. 1 alex alex 59 Jun 23 15:29 hosts
-rw-rw-r--. 1 alex alex 237 Jun 23 15:29 LICENSE.md
-rw-rw-r--. 1 alex alex 1163 Jun 23 15:29 README.md
drwxrwxr-x. 5 alex alex 4096 Jun 23 15:29 roles
-rw-rw-r--. 1 alex alex 411 Jun 23 15:29 site.yml
This example playbook uses two groups of hosts: "webservers" and "dbservers".
So we need to setup a mixed dynamic/static inventory.
For doing it we can create a directory and place our "vagrant.py" dynamic inventory script inside with the hosts file provided by the ansible example playbook:
[alex@freddy test_vagrant]$ mkdir inventory
[alex@freddy test_vagrant]$ cp ansible-examples/lamp_simple_rhel7/hosts inventory/
[alex@freddy test_vagrant]$ cp vagrant.py inventory/
[alex@freddy test_vagrant]$ ll inventory/
total 8
-rw-rw-r--. 1 alex alex 59 Jun 23 16:01 hosts
-rwxrwxr-x. 1 alex alex 3960 Jun 23 16:01 vagrant.py
After that, you need to edit the hosts file like this:
[alex@freddy test_vagrant]$ cat inventory/hosts
[webservers]
webserver
[dbservers]
database
Finally we can test it!
As you can see by the command below we invoke the command specifying the "inventory" folder, the username for connection ("-u vagrant") and the option "-b" for letting the script become root with "sudo" command:
[alex@freddy test_vagrant]$ ansible-playbook -i inventory ansible-examples/lamp_simple_rhel7/site.yml -u vagrant -b
PLAY [apply common configuration to all nodes] *********************************
TASK [setup] *******************************************************************
ok: [database]
ok: [webserver]
TASK [common : Install ntp] ****************************************************
changed: [webserver]
changed: [database]
TASK [common : Configure ntp file] *********************************************
changed: [webserver]
changed: [database]
TASK [common : Start the ntp service] ******************************************
changed: [webserver]
changed: [database]
RUNNING HANDLER [common : restart ntp] *****************************************
changed: [database]
changed: [webserver]
...
PLAY RECAP *********************************************************************
database : ok=16 changed=14 unreachable=0 failed=0
webserver : ok=14 changed=10 unreachable=0 failed=0
If all it's gone ok, we can grab the ip address of our webserver:
[alex@freddy test_vagrant]$ vagrant.py --list all | json_reformat
{
"vagrant": [
"webserver",
"database"
],
"_meta": {
"hostvars": {
"webserver": {
"ansible_ssh_host": "192.168.121.171",
"ansible_ssh_port": "22",
"ansible_ssh_user": "vagrant",
"ansible_ssh_private_key_file": "/home/alex/Projects/Ansible/test_vagrant/.vagrant/machines/webserver/libvirt/private_key"
},
"database": {
"ansible_ssh_host": "192.168.121.105",
"ansible_ssh_port": "22",
"ansible_ssh_user": "vagrant",
"ansible_ssh_private_key_file": "/home/alex/Projects/Ansible/test_vagrant/.vagrant/machines/database/libvirt/private_key"
}
}
}
}
And then we can test if the webserver is running fine.
Quoting the ansible github page:
Once done, you can check the results by browsing to http://localhost/index.php.
You should see a simple test page and a list of databases retrieved from the database server.
[alex@freddy test_vagrant]$ curl http://192.168.121.171/index.php
<html>
<head>
<title>Ansible Application</title>
</head>
<body>
</br>
<a href=http://192.168.121.171/index.html>Homepage</a>
</br>
Hello, World! I am a web server configured using Ansible and I am : localhost.localdomain</BR>List of Databases: </BR>information_schema
foodb
mysql
performance_schema
test
</body>
</html>
That's all!
References
[1] How to create RHEL based Vagrant boxes
[2] ansible/vagrant.py at devel · ansible/ansible · GitHub