Ansible Roles

Simple and compatible Ansible roles for multi-distribution automation. Tested on Fedora, CentOS, Ubuntu, Debian, OpenSUSE, and more.

Home Blog My manifesto About Uptime View on GitHub

Learning Ansible

This is an as-short-as-possible walk through to learn Ansible. It should help you get going as quick as possible.

Concept

Ansible is a tool that can configure (remote) servers. It’s simple to read, has many modules and can run from a simple laptop.

With Ansible you describe what tasks should be executed on selected remote hosts in the inventory.

+--- command ------------------+
| ansible-playbook my_play.yml |
+------------------------------+
   |
   V
+--- ansible.cfg -----+   +--- my_play.yml ------------------+
| inventory=inventory |   | - name: configure my hosts       |
+---------------------+   |   hosts: all                     |
   |                      |   become: yes                    |
   V                      |   gather_facts: yes              |
+--- inventory ---+       |                                  | 
| my_host_1       |-----> |   tasks:                         |
| my_host_2       |       |     - name: show ntp_servers     |
+-----------------+       |       debug:                     |
                          |         msg: "" | 
                          +----------------------------------+
                             ^                           |
                             |                           V
+--- group_vars/all/ntp.yml ---+   +--- my_host_1 ----------+
| ntp_servers:                 |   | ntp_servers:           |
|   - name: pool.ntp.org       |   |   - name: pool.ntp.org |
+------------------------------+   +------------------------+

Inventories

An inventory is a file that describes what servers should be managed. For example:

my_server.example.com

[my_webservers]
my_web_1.example.com
my_web_2.example.com

[amsterdam]
my_web_1.example.com

[london]
my_web_2.example.com

[singapore]
my_server.example.com

[europe:children]
amsterdam
london

[asia:children]
singapore

These variables have a precedence, for the group_vars:

  1. group_vars/all
  2. group_vars/*

This means more specific variables overwrite all.

In playbooks you can target tasks and roles to run on selected hosts. In this case there are multiple groups, 2 are default

You can make variables (like the ntp_servers) available per group.

Host & Group variables

Once you have an inventory defined, you can make certain variables available per host or group.

group_vars/all/ntp.yml

ntp_servers:
  - name: pool.ntp.org

group_vars/amsterdam/ntp.yml

ntp_servers:
  - name: nl.pool.ntp.org

host_vars/my_web_2.example.com.yml

ntp_servers:
  - name: 1.uk.pool.ntp.org

With inventories and host & group variables you can define all data required by roles to configure a system.

Tasks

A task is a statement of what Ansible should to, it’s the smallest component in Ansible and you can use tasks in playbooks, roles and handlers.

This task copies a file from the Ansible Control node to the server the task is targeted to.

- name: Copy file with owner and permissions
  copy:
    src: /srv/myfiles/foo.conf
    dest: /etc/foo.conf

Roles

When you are writing more playbooks, you’ll see that duplicate code creeps in. You may have 2 playbooks that both configure and start ntp.

That’s a perfect opportunity to write a role. A role is basically a list of tasks that you can pull into a play:

---
- name: configure my servers
  hosts: all
  become: yes
  gather_facts: yes

  roles:
    - role: buluma.ntp

The role itself has a few files and directories. This is a simplified version of my ntp role.

.
├── defaults
│   └── main.yml      <- This contains user-overwritable settings, such as `ntp_servers`.
├── files
│   └── ntpd          <- Files that are copied to the remote system.
├── handlers
│   └── main.yml      <- Handlers are described further below.
├── meta
│   └── main.yml      <- Information for Ansible Galaxy, where Ansible roles can be published.
├── README.md         <- Documentation, give this the right amount of attention.
├── requirements.yml  <- Depending roles can be downloaded if specified here.
├── tasks
│   └── main.yml      <- The logic of the role, a list of tasks.
├── templates
│   └── ntp.conf.j2   <- Files that are rendered and placed on the remote system.
└── vars
    └── main.yml      <- Variables that are not supposed to be overwritten like `ntp_packages`.

defaults/main.yml

This file contains variables that a user/playbook can easily overwrite. Typically this contains settings for a program or role. You need to choose “sane defaults” so that a user that does not overwrite values still has a working application.

---
ntp_servers:
  - name: pool.ntp.org

You can use comments here to guide a user of your role on how to use these variables.

files/*

Files in here can be copied to a remote machine. The copying itself needs to be described in tasks/main.yml.

handers/main.yml

Handlers contain named tasks that are only started when they are called. I know, it’s a lot to take in. Here is an example:

tasks/main.yml

- name: configure ntp
  template:
    src: ntp.conf.j2
    dest: /etc/ntp.conf
  notify:
    - restart ntp

handlers/main.yml

---
- name: restart ntp
  service:
    name: ntp
    state: restarted

So, only if the task configure ntp is changed, the handler restart ntp is called. You can run roles over and over again, only the times where ntp.conf is changed, ntp is restarted. Quite logical right?

Handlers run at:

  1. The end of a play.
  2. When meta: flush_handlers runs.

The order of handers is as they are ordered in the handlers/main.yml, NOT how they are called.

meta/main.yml

This file is used to tell Ansible Galaxy how to show your role. Some examples of the contens:

galaxy_info:
  author: Michael Buluma
  role_name: ntp
  description: Install and configure ntp on your system.
  license: Apache-2.0
  company: ShadowNet
  min_ansible_version: 2.8

  platforms:
    - name: Fedora
      versions:
        - 33
        - 34

  galaxy_tags:
    - ntp

dependencies: []

README.md

requirements.yml

tasks/main.yml

templates/*.j2

vars/main.yml

Playbooks

A playbook is a list of tasks and roles and some details to understand how to apply them.

---
- name: configure my servers
  hosts: all
  become: yes
  gather_facts: yes

  tasks:
    - name: show something
      debug:
        msg: "Hello world"

  roles:
    - role: buluma.ntp

There are a few parameters that need explaining:

There order or tasks, roles and others is:

  1. pre_tasks
  2. roles
  3. tasks
  4. post_tasks

Facts

Ansible can get a lot of details from systems, called facts. A few examples of Ansible facts:

These facts can simply be used as variables in Ansible.

- name: show the distribution
  debug:
    msg: "You are running {{ ansible_distribution }} version {{ ansible_distribution_major_version }}

Handlers

Handlers are tasks that can be called when a parent tasks is changed. I know, it’s a lot to take in but an example may help:

- name: configure my servers
  hosts: all
  become: yes
  gather_facts: yes

  tasks:
    - name: configure ntp
      template:
        src: ntp.conf.j2
        dest: /etc/ntp.conf
      notify:
        - restart ntp

  handlers:
    - name: restart ntp
      service:
        name: ntpd
        state: restarted

Here you see an (incomplete) example of how handlers can be used. The advantage of a handler is that many tasks from the tasks list can call handlers, it will only be executed once, at the end of the play or when something calls the meta module with flush_handlers.

when

You can run jobs conditionally using when. It’s like the if of bash.

- name: install ntp on Fedora
  package:
    name: ntp
    state: present
  when:
    - ansible_distribution == "Fedora"

The when statement can handle a list of conditions, this is and “AND” list. To use “OR” simply write or:

- name: install ntp on RedHat and Debian machines
  package:
    name: ntp 
    state: present
  when:
    - ansible_os_family == "RedHat" or ansible_os_family == "Debian"

loop

You can repeat (most) tasks using a loop statement. This basically repeats the task and changed “{{ item }} every run.

- name: copy a file
  copy:
    src: "{{ item }}
    dest: "/tmp/{{ item }}
  loop:
    - my_file_1
    - my_file_2

Logic and data

This allows a maintainer of a role to focus on the logic and the implementor to focus on settings that are correct for her/his environment.