Ansible Part III: Using Playbooks

The Ansible cases we tested so far from Part I and Part II are what we call ad-hoc mode. If you are pretty comfortable on combining these ad-hoc commands and bash scripts, you can do a lot of work for a small amount of time. But Ansible can offer a lot more features. We’ll explore creating Ansible playbooks on this part.

The YAML file

Ansible uses easy-to-read configuration file for making “playbooks”, which are files full of separate Ansible “tasks”. A task is basically an ad-hoc command written out in a configuration file that makes it more organized and easy to expand. The configuration files use YAML (Yet Another Markup Language). It’s an easy-to-read markup language, but it does rely on whitespace so be careful with your spaces and tabs. A simple playbook looks something like this:

---
- hosts: webservers
 become: yes
 tasks:
   - name: this installs a package
     apt: name=apache2 update_cache=yes state=latest
    - name: this restarts the apache service
     service: name=apache2 enabled=yes state=restarted

You can basically summarize this down to two ad-hoc commands broken up into a YAML configuration file. For YAML files, it’s important to note that every filename ends with .yaml, and every YAML file must begin with three hyphen characters. And since whitespace matters, you should be careful when a hypen should precede a section or just be indented appropriately.

The above playbook would be executed by typing:

# ansible-playbook filename.yaml

And that is the equivalent of these two commands:

# ansible webservers -b -m apt -a "name=apache2 update_cache=yes state=latest"
# ansible webservers -b -m service -a "name=apache2 enabled=yes state=restarted"

Using Handlers

Aside from organization of tasks on the YAML files, there are also called “Handlers”. These are tasks that are executed only when “notified” that a task has made a change. As an example, we can rewrite the above YAML to make the second task a handler:

---
- hosts: webservers
 become: yes
 tasks:
   - name: this installs a package
     apt: name=apache2 update_cache=yes state=latest
     notify: enable apache
  handlers:
   - name: enable apache
     service: name=apache2 enabled=yes state=started

It may still look similar but the execution of the trigger to execute the second task is different. When the first task (installing Apache) executes and if a change is made, it notifies the “enable apache” handler, which makes sure Apache is enabled on boot and currently running. The significance is that if Apache is already installed, and no changes are made, the handler never is called. That makes the code much more efficient, but it also means no unnecessary interruption of the already running Apache process.

Note: Multiple tasks can call a handler and handlers are executed (notified) only when an Ansible task makes a change on the remote system.

Using Variables

Variable substitution can work inside a playbook. Here’s a simple example and syntax:

---
- hosts: webservers
 become: yes
 vars:
   package_name: apache2
 tasks:
   - name: this installs a package
     apt: "name={{ package_name }} update_cache=yes state=latest"
     notify: enable apache
  handlers:
   - name: enable apache
     service: "name={{ package_name }} enabled=yes state=started"

Note: sometimes Ansible can cause errors on unquoted variable substitutions, so always try to put things in quotes when variables are involved.

Getting Verbose

When moving from Ansible ad-hoc commands to playbooks, you’ll notice that Ansible tends a lesser output. With ad-hoc mode, you often can see what is going on, but with a playbook, you know only if it finished properly and if a change was made. There are two easy ways to change that. The first is just to add the -v flag when executing ansible-playbook. That adds verbosity and provides lots of feedback when things are executed.

If you’re creating a playbook and want to be notified of things along the way, the debug module comes in handy. For example:

---
- hosts: webservers
  tasks:
   - name: describe hosts
     debug: msg="Computer {{ ansible_hostname }} is running {{ ansible_os_family }} or equivalent"

The will show you something like the below output, which is incredibly useful when you’re trying to figure out the sort of systems you’re using. The good thing about the debug module is that it can display anything you want, so if a value changes, you can have it displayed on the screen so you can troubleshoot a playbook that isn’t working like you expect it to work.

It is important to note that the debug module doesn’t do anything other than display information on the screen for you. It’s not a logging system – it’s just a way to have information (customized information, unlike the verbose flag) displayed during execution.

#ansible-playbook os.yaml
 
PLAY ****************************************************************
 
TASK [setup] *********************************************************
ok: [ansible3]
ok: [ansible2]
 
TASK [describe hosts] ********************************************
ok: [ansible2] => {
    "msg": "Computer 8a04f124f46a is running Debian or equivalent"
}
ok: [ansible3] => {
    "msg": "Computer 8a851373ac3d is running Debian or equivalent"
}
 
PLAY RECAP *************************************************
ansible3      : ok=2    changed=0    unreachable=0    failed=0
ansible2      : ok=2    changed=0    unreachable=0    failed=0

Conditional statements

Conditionals are a part of pretty much every programming language. Ansible YAML files also can take advantage of conditional execution, but the format is a little different. Normally the condition comes first, and then if it evaluates as true, the following code executes. With Ansible, it’s a little backward. The task is completely spelled out, then a when statement is added at the end. This makes the YAML file readable but if you are used to using the usual “if, true, then, do this” conditional, if feels different. Here’s a slightly more complicated playbook.

 ---
- hosts: webservers
  become: yes
  tasks:
    - name: install apache for Ubuntu
      apt: name=apache2 update_cache=yes state=latest
      notify: start apache2
      when: ansible_os_family == "Debian"
 
    - name: install apache for Red Hat
      yum: name=httpd state=latest
      notify: start httpd
      when: ansible_os_family == "RedHat"
 
  handlers:
    - name: start apache2
      service: name=apache2 enabled=yes state=started
    - name: start httpd
      service: name=httpd enabled=yes state=starte

This playbook that will install Apache on hosts using either yum or apt based on which type of distro they have installed. Then handlers make sure the newly installed packages are enabled and running.

Using Loops

As with other configuration management systems, ansible includes most features of programming and scripting languages. For example, there are loops. Here’s an example YAML config that uses a simple list and loops it.

---
- hosts: webservers
become: yes
 tasks:
    - name: install multiple packages
      apt: "name={{ item }} state=latest update_cache=yes"
      with_items:
        - apache2
        - vim
        - htop
        - dnsutils
        - apt-utils

This playbook will install multiple packages using the apt module. Note the special variable named item, which is replaced with the items one at a time in the with_items section. Other loops work in similar ways, but they’re formatted differently. For a wide variety of ways Ansible can repeat similar tasks, you can check the Ansible docs here: https://docs.ansible.com/

Templates

Another module you may use fairly often is the template module. For template, you essentially create a text file and then use variable substitution to create a custom version on the fly. Ansible uses the Jinja2 templating language, which is similar to standard variable substitution in playbooks themselves. The example uses a custom HTML template file that will be copied on a remote batch of web servers. Here’s the playbook:

 ---
- hosts: webservers
  become: yes
  tasks:
   - name: install apache2
     apt: name=apache2 state=latest update_cache=yes
     when: ansible_os_family == "Debian"
   - name: install httpd
     yum: name=httpd state=latest
     when: ansible_os_family == "RedHat"
   - name: start apache2
     service: name=apache2 state=started enable=yes
     when: ansible_os_family == "Debian"
   - name: start httpd
     service: name=httpd state=started enable=yes
     when: ansible_os_family == "RedHat
   - name: install index
     template:
       src: index.html.j2
       dest: /var/www/html/index.html

Here’s the template file, which must end in .j2 (it’s the file referenced in the last task above):

 <html><center>
<h1>This computer is running {{ ansible_os_family }},
and its hostname is:</h1>
<h3>{{ ansible_hostname }}</h3>
{# this is a comment, which won't be copied to the index.html file #}
</center></html

The playbook takes a few different things it learned and installs Apache on the remote systems, regardless of whether they are Red Hat- or Debian-based. Then, it starts the web servers and makes sure the web server starts on system boot.

Finally, the playbook takes the template file, index.html.j2, and substitutes the variables while copying the file to the remote system. Note the {# #} format for making comments. Those comments are completely erased on the remote system and are visible only in the .j2 file on the Ansible machine.

More Possibilities!

Ansible is a very powerful tool that is surprisingly simple to understand and use. If you’ve been experimenting with ad-hoc commands, try to create playbooks that will allow you to do multiple tasks on a multitude of computers with minimal effort. You should at least play around with the “Facts” gathered by the ansible-playbook app, because those are things unavailable to the ad-hoc mode of Ansible.

On the next part, we’ll use playbooks with roles and take advantage of the Ansible community contributions available online.

See you on Part IV: Roles Overview

Ansible Part I: Installation and Setup
Ansible Part II: Using Modules
Ansible Part III: Using Playbooks
Ansible Part IV: Roles Overview

———————————

– masterkenneth

Leave a Reply

Your email address will not be published. Required fields are marked *