In this example we are going to use Vagrant and Ansible to build one basic application server and one web (Nginx) server. They both will share common properties but web server will have Nginx installed on it.


System


I am using Ansible 2.4.3.0 and Vagrant 1.9.5 on MacOS.


Structure


$ tree
.
├── Vagrantfile
├── provisioning
│   ├── group_vars
│   │   └── all
│   ├── hosts.yml
│   ├── roles
│   │   ├── common
│   │   │   ├── handlers
│   │   │   │   └── main.yml
│   │   │   ├── tasks
│   │   │   │   └── main.yml
│   │   │   └── templates
│   │   │   └── ntp.conf.j2
│   │   └── web
│   │   ├── handlers
│   │   │   └── main.yml
│   │   └── tasks
│   │      └── main.yml
│   └── site.yml
└── vagrant_hosts.yml

Tasks



Files


vagrant_hosts.yml


# Reflect any "hostname" and "ip" changes in "provisioning/hosts.yml" file

- name: This is for App Server 1 box
label: App 1 - 192.168.99.31
hostname: app1
ip: 192.168.99.31

- name: This is for Web Server 1 box
label: Web 1 - 192.168.99.32
hostname: web1
ip: 192.168.99.32

Vagrantfile


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

require "yaml"

boxes = YAML.load_file("vagrant_hosts.yml")

Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/xenial64"

ANSIBLE_RAW_SSH_ARGS = []
i = 0

boxes.each do |box|
ANSIBLE_RAW_SSH_ARGS << "-o IdentityFile=.vagrant/machines/#{box["hostname"]}/virtualbox/private_key"

config.vm.define box["hostname"] do |machine|
machine.vm.hostname = box["hostname"]
machine.vm.network "private_network", ip: box["ip"]
machine.vm.synced_folder box["hostname"], "/srv/www", create: true, nfs: true, mount_options: ["actimeo=2"]
machine.vm.provider :virtualbox do |virtualbox|
virtualbox.name = box["label"]
end
i += 1

if i == boxes.count
machine.vm.provision :ansible do |ansible|
ansible.raw_ssh_args = ANSIBLE_RAW_SSH_ARGS
ansible.verbose = "-vvv"
ansible.limit = "all"
ansible.inventory_path = "provisioning/hosts.yml"
ansible.playbook = "provisioning/site.yml"
end
end
end
end
end

provisioning/hosts.yml


# Reflect any "hostname" and "ip" changes in "../vagrant_hosts.yml" file

all:
children:
app_servers:
hosts:
app1:
ansible_host: 192.168.99.31
web_servers:
hosts:
web1:
ansible_host: 192.168.99.32

provisioning/site.yml


---
# This playbook sets up whole stack.

- name: Apply common configuration to "all" hosts
hosts: all
remote_user: root
become: yes
roles:
- common

- name: Apply configuration to "web_servers" hosts
hosts: web_servers
remote_user: root
become: yes
roles:
- web

provisioning/group_vars/all


---
# Variables listed here are applicable to "all" host

ansible_python_interpreter: /usr/bin/python3
locale: en_GB.UTF-8
language: en_GB:en

provisioning/roles/common/handlers/main.yml


---
# This playbook contains common handlers that can be called in tasks.

# sudo service ntp restart (whether running or not)
- name: Restart ntp
service:
name: ntp
state: restarted
enabled: yes

# sudo update-locale
- name: Update locale
shell: update-locale

provisioning/roles/common/tasks/main.yml


---
# This playbook contains common actions that will be run on "all" hosts.

# sudo apt-get update
- name: Update apt packages
apt:
update_cache: yes
tags:
- system

# sudo locale-gen en_GB.UTF-8
- name: Install GB locale
locale_gen:
name: "{{ locale }}"
state: present
tags:
- locale

# sudo update-locale LANG=en_GB.UTF-8
# sudo update-locale LC_ALL=en_GB.UTF-8
# sudo update-locale LANGUAGE=en_GB:en
- name: Set locale
command: update-locale "{{ item }}"
with_items:
- LANG="{{ locale }}"
- LC_ALL="{{ locale }}"
- LANGUAGE="{{ language }}"
notify: Update locale
tags:
- locale

# sudo timedatectl set-timezone Europe/London
- name: Set time zone to Europe/London
timezone:
name: Europe/London
tags:
- system

# sudo apt-get install ntp
- name: Install ntp
apt:
name: ntp
state: present
update_cache: yes
tags:
- ntp

# sudo cp provisioning/common/templates/ntp.conf.j2 /etc/ntp.conf
- name: Configure ntp file and restart
template:
src: ntp.conf.j2
dest: /etc/ntp.conf
notify: Restart ntp
tags:
- ntp

# sudo apt-get install nano
- name: Install nano
apt:
name: nano
state: present
update_cache: yes
tags:
- nano

# sudo apt-get autoclean
- name: Remove useless apt packages from the cache
apt:
autoclean: yes
tags:
- system

# sudo apt-get autoremove
- name: Remove dependencies that are no longer required
apt:
autoremove: yes
tags:
- system

provisioning/roles/common/templates/ntp.conf.j2


driftfile /var/lib/ntp/drift

# Specify UK NTP servers.
server 0.uk.pool.ntp.org
server 1.uk.pool.ntp.org
server 2.uk.pool.ntp.org
server 3.uk.pool.ntp.org

# Use Ubuntu's NTP server as a fallback.
server ntp.ubuntu.com

# Local users may obtain data from NTP servers.
restrict 127.0.0.1
restrict ::1

provisioning/roles/web/handlers/main.yml


---
# This playbook contains common handlers that can be called in tasks.

# sudo service nginx restart (whether running or not)
- name: Restart nginx
service:
name: nginx
state: restarted
enabled: yes

provisioning/roles/web/tasks/main.yml


---
# This playbook contains common actions that will be run on "web_servers" hosts.

# sudo apt-get install nginx
- name: Install nginx
apt:
name: nginx
state: present
update_cache: yes
notify: Restart nginx
tags:
- nginx

Build


$ vagrant up
...
PLAYBOOK: site.yml *************************************************************
2 plays in provisioning/site.yml
PLAY [Apply common configuration to "all" hosts] *******************************
TASK [Gathering Facts] *********************************************************
TASK [common : Update apt packages] ********************************************
TASK [common : Install GB locale] **********************************************
TASK [common : Set locale] *****************************************************
TASK [common : Set time zone to Europe/London] *********************************
TASK [common : Install ntp] ****************************************************
TASK [common : Configure ntp file and restart] *********************************
TASK [common : Install nano] ***************************************************
TASK [common : Remove useless apt packages from the cache] *********************
TASK [common : Remove dependencies that are no longer required] ****************
RUNNING HANDLER [common : Restart ntp] *****************************************
RUNNING HANDLER [common : Update locale] ***************************************
PLAY [Apply configuration to "web_servers" hosts] ******************************
TASK [Gathering Facts] *********************************************************
TASK [web : Install nginx] *****************************************************
RUNNING HANDLER [web : Restart nginx] ******************************************
META: ran handlers
META: ran handlers
PLAY RECAP *********************************************************************
app1 : ok=12 changed=8 unreachable=0 failed=0
web1 : ok=15 changed=10 unreachable=0 failed=0

Results


app1


$ timedatectl
Local time: Sun 2018-03-11 10:51:48 GMT
Universal time: Sun 2018-03-11 10:51:48 UTC
RTC time: Sun 2018-03-11 10:51:46
Time zone: Europe/London (GMT, +0000) # This was "Etc/UTC (UTC, +0000)" before
Network time on: yes
NTP synchronized: yes # This was "no" before
RTC in local TZ: no

$ locale
LANG=en_GB.UTF-8 # This was "en_US.UTF-8" before
LANGUAGE=en_GB:en # This was empty before
LC_CTYPE="en_GB.UTF-8"
LC_NUMERIC="en_GB.UTF-8"
LC_TIME="en_GB.UTF-8"
LC_COLLATE="en_GB.UTF-8"
LC_MONETARY="en_GB.UTF-8"
LC_MESSAGES="en_GB.UTF-8"
LC_PAPER="en_GB.UTF-8"
LC_NAME="en_GB.UTF-8"
LC_ADDRESS="en_GB.UTF-8"
LC_TELEPHONE="en_GB.UTF-8"
LC_MEASUREMENT="en_GB.UTF-8"
LC_IDENTIFICATION="en_GB.UTF-8"
LC_ALL=en_GB.UTF-8 # This was empty before

web1


$ timedatectl
Local time: Sun 2018-03-11 10:51:48 GMT
Universal time: Sun 2018-03-11 10:51:48 UTC
RTC time: Sun 2018-03-11 10:51:46
Time zone: Europe/London (GMT, +0000) # This was "Etc/UTC (UTC, +0000)" before
Network time on: yes
NTP synchronized: yes # This was "no" before
RTC in local TZ: no

$ locale
LANG=en_GB.UTF-8 # This was "en_US.UTF-8" before
LANGUAGE=en_GB:en # This was empty before
LC_CTYPE="en_GB.UTF-8"
LC_NUMERIC="en_GB.UTF-8"
LC_TIME="en_GB.UTF-8"
LC_COLLATE="en_GB.UTF-8"
LC_MONETARY="en_GB.UTF-8"
LC_MESSAGES="en_GB.UTF-8"
LC_PAPER="en_GB.UTF-8"
LC_NAME="en_GB.UTF-8"
LC_ADDRESS="en_GB.UTF-8"
LC_TELEPHONE="en_GB.UTF-8"
LC_MEASUREMENT="en_GB.UTF-8"
LC_IDENTIFICATION="en_GB.UTF-8"
LC_ALL=en_GB.UTF-8 # This was empty before

$ nginx -v
nginx version: nginx/1.10.3 (Ubuntu)

If you go to http://192.168.99.32 in your browser, you should see "Welcome to nginx!" page.


References