Ansible playbooks
Here the Video Transcript
An Ansible playbook is a collection of plays or instructions defined against a host or a group of hosts. Let us start by writing a simple example for the master node:
---
- name: Enable/start some services for master node
hosts: master
tasks:
- name: enable/start httpd service
service:
name: httpd
state: started
enabled: yes
- name: enable/start named service
service:
name: named
state: started
enabled: yes
- name: enable/start cobblerd service
service:
name: cobblerd
state: started
enabled: yes
...
In this example, we have three different tasks (plays) to enable/start the system services httpd
, named
and cobblerd
on the master node. The playbook is written in YAML syntax, and therefore needs to follow the correct indentation in order to avoid errors. Let us now go through the different YAML tags used here:
name
: This tag specifies the name of the Ansible playbook or a task. A logical description in this tag will help to the reader to understand the purpose of the playbook as well as to debug it.hosts
: This tag specifies the lists of hosts or host group in which we want to run the tasks. Thehosts
tag is mandatory.tasks
: This is the list of actions to be performed. Each task includes a piece of code called module (in this example, the module service). If the module requires arguments, they must be provided within indentation after the module invocation (in our example, the argumentsname
,state
andenabled
).
To run this playbook, the ansible-playbook
command should be used instead of ansible
. If the inventory hosts file to be used is not located in /etc/ansible/hosts
, it must be specified with the -i
option. Using the inventory created in Ansible Basics, the playbook is executed in the following manner:
[root@master ~]# ansible-playbook -i inventory master.yml
PLAY [Enable/start some services for master node] **********************************************************************
TASK [Gathering Facts] *************************************************************************************************
ok: [localhost]
TASK [enable/start httpd service] **************************************************************************************
ok: [localhost]
TASK [enable/start named service] **************************************************************************************
ok: [localhost]
TASK [enable/start cobblerd service] ***********************************************************************************
ok: [localhost]
PLAY RECAP *************************************************************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0
Working with variables
The vars tag lets you to define the variables which you can use in your playbooks. Usage is similar to variables in any programming language. For instance, in the following task
- name: ensure a list of packages installed
yum:
name: "{{ packages }}"
vars:
packages:
- httpd
- httpd-tools
the vars
tag defines the variable packages
as a list. In general, Ansible supports YAML variables, including dictionaries, lists, single-value attributes, etc. Once you have defined variables, you can use them in your playbooks using the Jinja2 templating system, look at the usage of the package
variable in the previous example:
name: "{{ packages }}"
Variables are defined in the tag: value
syntax. In a more advance use of ansible, variables can be stored in files within a vars
folder inside a role
(see Introduction to roles).
Another special kind of variables are the Ansible facts. These facts are variables discovered by Ansible as a result of the interaction with a system and not set by the user. For instance, the OS distribution, the IP address, among others. To see what facts are available on a particular system, you can add the following task in a playbook:
- debug: var=ansible_facts
or run the following command:
[root@master ~]# ansible <hostname> -m setup
Note that in the execution of a playbook, the first step is Gathering Facts
unless it is explicitly suppressed. You can disable the fact gathering by adding
gather_facts: no
after the host
declaration. Depending on your needs, facts can also be declared by the user using the module set_fact.
Iterative items (loops)
Loops can be used to gather some common tasks. For instance, the previous example can be simplified to one task with the tag with_items
as follows:
---
- name: Configuration for the master node
hosts: master
tasks:
- name: enable/start services
service:
name: "{{ item }}"
state: started
enabled: yes
with_items:
- httpd
- named
- cobblerd
...
Here, the variable "{{ item }}"
will iterate over the list with_items
. The execution of this playbook is now:
[root@master ~]# ansible-playbook -i inventory master.yml
PLAY [Configuration for the master node] **********************************************************************
TASK [Gathering Facts] ****************************************************************************************
ok: [localhost]
TASK [enable/start services] **********************************************************************************
ok: [localhost] => (item=httpd)
ok: [localhost] => (item=named)
ok: [localhost] => (item=cobblerd)
PLAY RECAP ****************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
Ansible modules
Besides the service
module we have been using so far, Ansible contains a wide variety of modules. The most used Ansible modules for our purposes are:
Module |
Description |
Example |
---|---|---|
Execute commands in nodes. |
- name: Shell module example
shell: ping
args:
creates: /path/file # skip if this exists
removes: /path/file # skip if this is missing
chdir: /path # cd here before running
|
|
Runs a local script on a remote node after transferring it. |
- name: Script module example
script: /x/y/script.sh
args:
creates: /path/file # skip if this exists
removes: /path/file # skip if this is missing
chdir: /path
|
|
Manages packages with the yum package manager. |
- name: Install the latest version of Apache
yum:
name: httpd
state: latest
|
|
Sets attributes of files. |
- name: File module example
file:
path: /etc/dir
state: directory # file | link | hard | touch | absent
# Optional:
owner: bin
group: wheel
mode: 0644
recurse: yes # mkdir -p
force: yes # ln -nfs
|
|
Copies files to remote locations. |
- name: Copy example
copy:
src: /x/y/inventory
dest: /etc/ansible/hosts
# Optional:
owner: user
group: user
mode: 0644
backup: yes
|
|
Print statements during execution. |
- name: Debug module example
debug:
msg: "Hello {{ var }}"
|
For a list of all available modules, see Module Index, or run the following at a command prompt:
[root@master ~]# ansible-doc -l
Working with conditionals
conditionals can be used inside playbooks to regulate the execution of certain tasks depending on the outcome of previous tasks. For instance,
- name: Check if CentOS 7 Distro has been imported
command: cobbler profile report --name CentOS-7-2009-x86_64
register: distro_exists
ignore_errors: True
- name: Download CentOS 7 ISO
get_url:
url: http://mirror.cs.pitt.edu/centos/7.9.2009/isos/x86_64/CentOS-7-x86_64-DVD-2009.iso
dest: /provisioning/iso/CentOS-7-x86_64-DVD-2009.iso
checksum: sha256:e33d7b1ea7a9e2f38c8f693215dd85254c3a4fe446f93f563279715b68d07987
when: distro_exists is failed
In the previous example, the task get_url will be executed only if command
fails. The variable containing the information of the success of the command
task is distro_exists` in the form of register
. Note that the when
statement must contain a Boolean expression. This can use Ansible facts, variables, or results that were registered. For instance:
tasks:
- name: "shut down CentOS 6 systems"
command: /sbin/shutdown -t now
when:
- ansible_facts['distribution'] == "CentOS"
- ansible_facts['distribution_major_version'] == "6"
Example: set up NTP server and clients
Let’s configure a Network Time Protocol (NTP) server and clients using ansible playbooks. Please see the NTP configuration in Synchronize time across the cluster for a detailed explanation on the manual configuration.
NTP server installation in a playbook
This playbook has to target the master node since it will serve as NTP server for the rest of the cluster. The necessary steps to configure the NTP server are,
Disable and stop the
chronyd
service.Install of
ntpd
daemon, usingyum
.Allow the cluster’s subnets (IPMI and network) to query the NTP server.
Enable and start the
ntpd
service.
These steps can be translated as ansible tasks in the following manner:
---
- name: NTP server configuration
hosts: master
tasks:
- name: Disable and stop chronyd daemon
service:
name: chronyd
enabled: no
state: stopped
ignore_errors: true
- name: Remove chronyd package
yum:
name: chrony
state: absent
- name: Install ntpd daemon
yum:
name: ntp
state: latest
- name: Allow IPMI and node network to query ntp server
blockinfile:
dest: /etc/ntp.conf
insertafter: '^#restrict '
block: |
restrict 192.168.0.0 mask 255.255.240.0 nomodify notrap
restrict 192.168.16.0 mask 255.255.240.0 nomodify notrap
- name: Enable and Restart ntpd daemon
service:
name: ntpd
enabled: yes
state: restarted
...
Note
Within the task blockinfile the symbol |
in block: |
, is used to allow multi-line entries.
To run the playbook, save the playbook as ntp_server.yml
and execute the command:
[root@master ~]# ansible-playbook -i inventory ntp_server.yml
NTP client installation in a playbook
Once again, following the steps described for the NTP client configuration explained in Synchronize time across the cluster, our playbook looks like,
---
- name: NTP client configuration
hosts: compute
tasks:
- name: Disable and stop chronyd daemon
service:
name: chronyd
enabled: no
state: stopped
ignore_errors: true
- name: Remove chronyd package
yum:
name: chrony
state: absent
- name: Install ntpd daemon
yum:
name: ntp
state: latest
- name: Set up default default ntp server
lineinfile:
dest: /etc/ntp.conf
regexp: '^server'
insertafter: '^#server '
line: 'server ntp.hpc prefer'
- name: Enable and Restart ntpd daemon
service:
name: ntpd
enabled: yes
state: restarted
...
Note the use of the module lineinfile and regular expressions. Again, using the inventory file created previously, the playbook can be executed with the command:
[root@master ~]# ansible-playbook -i inventory ntp_client.yml