Going Deeper into Ansible Playbooks

Update 2019: We’ve recently developed a Learning Path, Introduction to Ansible, which will help you to get you started using Ansible to automate common IT tasks, you will learn about Configuration Management and you’ll be able to practice your knowledge on Ansible through a series of hands-on labs. Guy Hummel, our expert cloud trainer, has recently written an introductory post on What is Ansible?.


In a previous blog post, we introduced some basic Ansible fundamentals, installation procedures, and a guide to ad-hoc mode.

Ansible can be used in either Ad-Hoc or Playbook mode. As covered in our previous post, the ad-hoc mode allows direct management of your hosts by executing single line commands and leveraging Ansible modules. Ad-hoc mode is useful when you plan to perform a quick and simple activity like shutting down your hosts or checking connectivity between your Ansible server and hosts using ping. But when you plan to manage host configurations and deployments, Ansible playbooks become more attractive.

According to Ansible documentation, “Playbooks are Ansible’s configuration, deployment, and orchestration language. They can describe a policy you want your remote systems to enforce or a set of steps in a general IT process.”

Playbooks are written in human-readable YAML format and can be created either by placing everything in a single file or by following a structure model. Each Ansible playbook contains one or more plays to help you to perform functions on different hosts. The goal of a play is to map roles to hosts and perform tasks under a role on different hosts. Tasks are nothing but modules called on hosts.

Ansible Playbook Structure

Here is sample Ansible playbook:

---
- hosts: webservers
vars:
   http_port: 80
   max_clients: 200
remote_user: root
tasks:
- name: ensure apache is at the latest version
   yum: pkg=httpd state=latest
- name: write the apache config file
   template: src=/srv/httpd.j2 dest=/etc/httpd.conf
   notify:
   - restart apache
- name: ensure apache is running
   service: name=httpd state=started
handlers:
   - name: restart apache
     service: name=httpd state=restarted in more than one way. You

In the above Ansible playbook, we created an entire configuration as one single file. This is fine if you’re writing a playbook for a simple deployment or configuration. However, once you decide to implement complex deployment scenarios, it is better to use a structured model, adding re-usability.
The structure of an Ansible playbook:

site.yml
hosts
group_vars/
      group1
      group2
host_vars/
      hostname1
      hostname2
roles/
      common/
            files/
            templates/
            tasks/
            handlers/
            vars/
            defaults/
            meta/
      webservers/
            …
            …
      applicationservers/
            …
            …
      databaseservers/
            …
            …

Let’s analyze this structure.

  • site.yml – site.yml is our master YAML playbook file. It contains information about the rest of the playbook.
  • Hosts

Ansible contains information about the hosts and groups of hosts to be managed in the hosts’ file. This is also called an inventory file. One can also divide hosts files into files with different environment names, i..e, instead of “hosts”, you can create two different files called “production” and “staging”. Apart from mentioning hosts and group of hosts information, you can also include host-specific information (ssh port, db parameters etc) in a hosts file.

Here is a sample hosts file:

[webservers]
prod-web01.example.com
prod-web02.example.com
[databaseservers]
prod-db01.example.com
prod-db02.example.com
  • group_vars and hosts_vars

Like the hosts’ inventory file, you can also include hosts and groups of hosts configuration variables in a separate configuration folder like group_vars and hosts_vars. These can include configuration parameters, whether on the application or operating system level, which may not be valid for all groups or hosts. This is where having multiple files can be useful: inside group_vars or hosts_vars, you can create a group or host in more than one way, allowing you to define specific configuration parameters.

A sample group_vars configuration:

---
# file: group_vars/webservers
apacheMaxRequestsPerChild: 3000
apacheMaxClients: 900

If you look at the above configuration, the apacheMaxRequestsPerChild or apacheMaxClients configuration parameters are only valid for the webservers hosts group. They don’t apply to the database or application hosts group.

If there is some configuration which you want to apply to all groups, this can be easily done:

---
# file: group_vars/all
ntp: ntp-boston.example.com
backup: backup-boston.example.com
  • Roles

As you add more and more functionality to your Ansible playbooks, it becomes difficult to manage it as a single file. Roles allow you to prepare a minimal Ansible playbook the defines how a server is supposed to perform rather than specifying the steps to get a server to act in a specific way.

According to Ansible documentation, “Roles in Ansible build on the idea of include files and combine them to form clean, reusable abstractions – they allow you to focus more on the big picture and only dive down into the details when needed.”

To correctly use roles with Ansible, you need to create a roles directory in your working Ansible directory, and then any necessary sub-directories.

The Ansible roles structure:

roles
|__ defaults
        |__ main.yml
|__ files
|__ templates
|__ tasks
        |__ main.yml
|__ meta
        |__ main.yml
|__ vars
        |__ main.yml
|__ handlers
        |__ main.yml

There are two ways to build the roles directory format, manually or by using Ansible-Galaxy. Ansible galaxy is a free site for finding, reusing and sharing community developed roles. To create a role using ansible galaxy, use the ansible-galaxy command :

# ansible-galaxy init webservers

 To understand more about roles, it is important to understand the roles directory structure.

  • Defaults – In the defaults directory, we need to have a file called main.yml which includes information about default variables used by this role. For example, this can be a default directory to deploy your configuration or to set up a default port for your application, etc.
---
webservers_dir: /var/www/html
webservers_port: 80
  • Files – the files directory contains files which need to be deployed to your hosts without any modification. These are simply copied over to your hosts. This could be your web application source code or some scripts.
  • Templates – templates are like files but allow modification. You can pass configuration variables to templates and those modified templates will be placed on your hosts. Ansible allows you to reference variables in your playbooks using the Jinja2 templating system.

 Sample template definition :

template: src=httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
  • Tasks – Each play can contain multiple tasks and each task can perform a variety of actions. Tasks basically execute modules with specific arguments. These arguments can be variables defined above. Along with modules, tasks also reference files and templates from other directories without providing a complete path. Tasks are executed in order against all hosts matching a particular host pattern. Tasks are written in main.yml or in other .yml files in the same directory that are referenced by main.yml.

 Sample task definition:

---
# These tasks install http and the php modules.
- name: Install http and php etc
yum: name={{ item }} state=present
with_items:
- httpd
- php
- php-mysql
- git
- libsemanage-python
- libselinux-python
  • Meta – meta contains files that describe environment (operating system, version etc), author, and licensing attributes, and establishes role dependencies. If a role written above depends upon some other role, those dependencies are resolved under meta section (main.yml).

 Sample meta definition:

---
dependencies:
- { role: apt }
  • Vars – vars is identical to defaults, i.e., it is used to store variables in a YAML file. However, variables defined under vars have higher priority than variables defined under defaults. These defined variables are found in configuration files for tasks, templates and others.
  • Handlers – If you refer to the above sample playbook, you’ll find a “notify” section under tasks. Notify definitions point to handlers. Handlers are also tasks which run only under particular events, and are executed only if notified by multiple tasks or state changes. For example, “if a new application code is deployed on hosts, then restart Apache. If not, don’t restart Apache.”

 Sample handlers definition:

---
# file: roles/common/handlers/main.yml
- name: restart ntpd
service: name=ntpd state=restarted

Executing Ansible Playbook

To execute an Ansible playbook, you use the ansible-playbook command.

# ansible-playbook -i hosts site.yml

If you have a different Ansible inventory file for production and staging, you can execute a playbook on a production environment by referencing the production inventory file.

# ansible-playbook –i production site.yml

Cloud Academy