You are on page 1of 37

Marcin T.

Ślęczek

THE WORLD OF AUTOMATION WITH

Red Hat Ansible

vol.2

2022
TABLE OF CONTENT:

01 | About the author: Marcin T. Ślęczek


02 | Red Hat Ansible Automation Platform 2 – What’s new?
03 | Ansible, part IV – Variables and Jinja2 Templates
04 | Ansible, part V – Loops, Conditional Statements and Handlers
05 | Ansible, part VI – Network Automation and some optimizations

2 | The world of automation with Red Hat Ansible


01
ABOUT THE AUTHOR:
Marcin T. Ślęczek

Marcin works as CEO, Network Engineer and System


Administrator at networkers.pl, which designs and
implements IT Systems, Data Centers and DevOps
environments. networkers.pl also sells software and
hardware for building such environments and is
a partner of well-known manufacturers such as
Red Hat, Cisco Systems, IBM, Storware and VMware.

Marcin is Red Hat Certified Architect and also Cisco


Certified Network Professional in the area of Data
Center, Security and Enterprise Networks.

In 2006-2014 he conducted trainings in the area


of GNU/Linux systems and Cisco Systems network
solutions. He is also Cisco Certified Academy
Instructor.

He has been working in the industry since 2000.


Until 2013, he works with GNU/Linux systems,
Enterprise Networks and Service Provider Networks.
Between 2013 and 2017, he dealt mainly with Network
Security, IP Telephony (VoIP), videoconferencing
and Enterprise Networks. Since 2017, he works
mainly with everything related to Data Centers and
DevOps environments.

Open Virtualization Pro


Professional community focused on sharing knowledge
about open-source technologies.

Portal Coordination and Editing: Leszek Warzecha

Portal Partners:

3 | The world of automation with Red Hat Ansible


02
RED HAT ANSIBLE AUTOMATION
PLATFORM 2 – What’s new?

The initial architecture of Red Hat Ansible Automation Platform had a closely related
Control Plane and Execution Plane, which were available as part of the Red Hat
Ansible Tower. While it was possible to use different Python virtual environments,
with different versions and components of Ansible, they were managed manually.
As a result, the process was complex, tedious and error-prone, and required the
involvement of the platform administrators.

Red Hat Ansible Automation Platform 2 can natively support many different
execution environments (runtime environments), adapted to automate specific
scenarios, devices or systems. The new architecture of Red Hat Ansible Automation
Platform 2 provides a control layer using the Automation Controller, which replaced
the Ansible Tower. The introduction of separation between the control layer and
the executive layer ensures much greater flexibility, stability and predictability of
the entire automation. The Execution Environment is a standalone RHEL (Red Hat
Enterprise Linux) UBI (Universal Base Image) 8 image that performs automation
tasks in the form of a running container. Such an image contains the appropriate
version of Python and Ansible, along with all the necessary collections (Ansible
Content Collections) or additional packages, frameworks and related dependencies.

In this way, each team can easily create automation tasks that are tailored to
a specific and optimized runtime or execution environment, being sure that it will
perform as well as the one used during the development and test.

4 | The world of automation with Red Hat Ansible


The predefined and accepted execution environment ensures portability and
predictable, stable and reliable automation, even in distributed environments.
Thanks to the bi-directional Automation Mesh network, the Automation Controller
can conveniently and securely, using overlay network technology and TLS-based
encryption, communicate with distributed nodes supporting the execution layer,
which may include physical and virtual servers running RHEL system or Red Hat
OpenShift platforms.

As part of the subscription for Red Hat Ansible Automation Platform, we get access
to several execution environments, which can be downloaded as special images.
Red Hat also provides the Ansible Builder tool, with which such environments can be
modified or created, and the Ansible Navigator tool, which provides an interface for
editing, analyzing and inspecting execution environments and launching previously
prepared playbooks in them.

In new version Ansible Core was also separated from Ansible Content Collections,
which were previously shipped as a single entity. Ansible Core currently includes
only the basic modules and command line tools of Ansible and its components,
that allow YAML code to be interpreted and executed or the use of conditional
expressions and loops. Previously, Ansible Core delivered a lot of additional
modules that extended the available automation functions of Ansible. The number
of these modules and its functionalities grew so quickly, that it became necessary
to isolate them into separate packages, called Ansible Content Collections. A single
collection is a set of modules, roles and additional plug-ins that together are used
for a specific purpose, such as the automation of a selected device or system.
This approach has many advantages. The execution environment can now only
have collections that are required for specific automation tasks. This applies not
only to the selection of specific collections, but also their exact versions. Thanks to
the new, each of the collections can be developed, maintained and updated more
independently of Ansible Core.

The Automation Hub was also expanded. Previously, it gave access to both
community-created collections as part of Ansible Galaxy, as well as commercial
ones created, maintained and supported directly by Red Hat and certified Red Hat

5 | The world of automation with Red Hat Ansible


technology partners, which include vendors of various commercial solutions, such
as in Cisco Systems, F5 and VMware. Automation Hub has been equipped with
built-in functions to limit available collections, including those of not always certain
quality, which are widely shared by the community as part of Ansible Galaxy, what
is good for security and stability reasons. Collections provided by Red Hat and Red
Hat certified partners are supported in the Red Hat Ansible Automation Platform
subscription. With the second version of the platform, the Private Automation Hub
also appeared, which allows you to conveniently manage and share collections and
images of execution environments within the same organization.

Finally, we cannot forget about additional cloud services available as part of


the Red Hat Ansible Automation Platform subscription, which include among
others Automation Services Catalog and Red Hat Insights for Red Hat Ansible
Automation Platform.

Automation Services Catalog provides a self-service interface for non-technical


or less technical personnel, based on ITSM (IT Service Management Solution)
idea. With its help, they can in a controlled manner (without or with approval),
independently create new services or systems within the local infrastructure and
cloud environments.

Red Hat Insights for Red Hat Ansible Automation Platform provides additional
analytical tools, whose goal is to deliver enterprise-wide information about the
automation and impact of the automation on the whole enterprise.

Despite all these changes, the method of creating automation code or running
it by Ansible can be carried out in exactly the same way, as it was done before.
The changes introduced as part of Red Hat Ansible Automation Platform 2 better
address the issues of automation in many big enterprises and organizations, hence
they are a key element of larger environments or places where automation should
be implemented in a consistent manner.

6 | The world of automation with Red Hat Ansible


03
ANSIBLE, PART IV: – Variables and
Jinja2 Templates

Variables are used to store various types of data. Their values usually depend on
the location or context in which they are located. Variables can be completely
independent entities or grouped together with other variables, to describe or
parameterize a given object. For this purpose, we can use the structure of
a dictionary, which groups variables called keys and allows you to assign values
to them.

An example of such a dictionary structure in YAML can be seen below:


device:

device:
name: R1
ipv4 _ address: 10.8.104.1
ipv6 _ address: 2001:db8:c1sc0::a

Variables must start with a letter, and may additionally contain numbers and the
underscore character ”_”. YAML allows you to reference variables within a dictionary
by using the period ”.” or square brackets ”[ ]”:

device.ipv6 _ address
device[’ipv6 _ address’]

Above, in both cases we referenced the value ”2001: db8: c1sc0 :: a”. While the
second is safer, the first is more often used for convenience. If we decide to use the
dot ”.”, it is worth checking the limitations described in the Ansible documentation.

Variables are most often used to parameterize hosts and inventory groups. Thus,
they can be handled by Ansible based on the values assigned to the various
variables. As the static inventory is most often created in the INI file format, we
are only referring to it at the moment. When we want to assign variables directly
to a host, they are given in the format ”key=value”, on the same line as the
host definition.

7 | The world of automation with Red Hat Ansible


Here is an example of an inventory with variables assigned directly to hosts:

[web _ servers]
web1 ansible _ host=10.8.232.121 ansible _ port=2222
web2:2222 ansible _ host=10.8.232.122
10.8.232.123:2222
10.8.232.124 ansible _ port=2222

In our example, we used the names ”web1” and ”web2”. If the given hostnames are
not FQDN (Fully Qualified Domain Name) or IP resolvable names, then we should use
the ”ansible_host” variable. It indicates the IP address to which the connection will
be established. By default, an attempt will be made to establish an SSH connection
to port 22. If you want to change the port number, you must append it after a colon
”:” to the hostname or IP address, or use the ”ansible_port” variable.

In practice, the variables are most often defined within groups. They are then located
in a special section called by the name of the group with the suffix ”:vars”. In the
variable section, one variable per line is provided. Using group level variables does
not exclude the possibility of using other variables or even overwriting the same
variables at the host level. This is shown below:

[web _ servers:vars]
ansible _ port=2222

[web _ servers]
web1 ansible _ host=10.8.232.121
web2 ansible _ host=10.8.232.122
10.8.232.123
10.8.232.124:22

In our example you can see the definition of the ”ansible_port” variable for all hosts
in the ”web_servers” group. However, for host ”10.8.232.124” a different port will be
used as this has been overridden at the host level.

Variable definitions can span multiple scopes, that is all hosts, parent or child
groups, as well as a single host. The definition of a variable within a more specific
scope has higher priority. Only when it is absent, the available definitions in wider
ranges are checked. In other words, as long as child groups inherit the values of
the variables of the parent groups, a more precise definition always applies. Hence,
at the child group level, you can override the parent group variables. If the
same host belongs to many groups of the same level, the groups are arranged
alphabetically and each subsequent group on the list overwrites the variables of the
previous ones. It is also possible to manually set the priority for each group.

Please also note, that we may have multiple inventories. There too, the definitions
of hosts and groups as well as their variables can duplicate. Both the ”ansible”
and ”ansible-playbook” commands allow you to specify several ”-i” or ”--inventory”
options, and as arguments you can also specify a directory with multiple inventory
files (static and dynamic simultaneously). In the event that we use several inventories,

8 | The world of automation with Red Hat Ansible


each subsequent inventory on the list overwrites the variables of the previous one.
When a directory is given, its files are in alphabetical order as for groups. Hence,
it is then recommended to start the inventory file names with numbers, which
makes the whole thing clearer.

In the case of complex environments and a large number of managed nodes,


it is recommended to keep the variables out of the inventory in separate files per
group and per host. The directory ”group_vars/” (group variables) and the directory
”host_vars/” (host variables) are used for this purpose, respectively. Ansible
tries to find these directories based on the location of the inventory file (”ansible”
command) or the current directory (”ansible-playbook” command). The names of
these files correspond to the names of groups or hosts. The contents of the files
must conform to the YAML syntax. These files do not have to have any extension
or have to have the extension: ”.yml”, ”.yaml” or ”.json”.

In our example, the ”group_vars/web_servers” file should contain:

---
ansible _ port: 2222

Above you can see three dashes ”---” and the typical YAML mapping ”key: value”.
Variables inside the playbook can also be created in the same way. It is also possible
to point to entire files and directories with variables inside it. Also, additional variables
may be passed as arguments to the ”-e” or ”--extra-vars” options of the ”ansible”
and ”ansible-playbook” commands. There are many possibilities, but fortunately
we do not need to know them all. The full list of variable sources and their priorities
is available in the Ansible documentation.

The variables are referenced through the Jinja2 template system, known to many
from the Python language. It is very flexible. It supports both conditional expressions
and loops. We reference the value of a variable in Jinja2 by placing its name inside
double curly braces ”{{ }}”.

In the previous article, we mentioned the possibility of using the collected


information from managed nodes to build more universal playbooks. This
information is called facts. They are obtained during the ”Gathering Facts” quest.
It is always launched by default at the beginning of every game. During this, Ansible
creates the appropriate variables for each of the nodes that can be referenced in
the playbook and Jinja2 templates. The number of these variables is very large. The
easiest way to view them all is to use the ad-hoc command with the ”setup” module
mentioned in the previous article in this series.

In a playbook, we usually refer to selected facts in order to condition the way of


performing a given task on the value that is contained in them. It also happens
that the collected facts are used as configuration values for various services or for

9 | The world of automation with Red Hat Ansible


building files. Facts can also be used to generate notifications both on the playbook
exit, as well as those sent by e-mail or to the Cisco Webex Teams room.

While the playbook is running, the ”debug” module is used to display the values
of variables, including facts.

[msleczek@vm0-net projekt _ A]$ cat playbook.yaml


---
- hosts: all
tasks:
- name: ”Informacje o hostach”
debug: ”msg=’Host: {{ansible _ hostname}}, OS: {{ansible _ os _ family}}, IPv4: {{ansible _
default _ ipv4.address}}.’”
...

[msleczek@vm0-net projekt _ A]$

We can use one of the two options of the ”debug” module to display the information:
”var” or ”msg”. They are mutually exclusive, so you have to choose:

• ”var” only displays the value of the specified variable and does not require the
explicit use of curly braces around the variable name ”{{ }}”.
• ”msg” displays a message prepared by us, in which if variables are used, they
should be explicitly placed inside double curly braces ”{{ }}” - according to the
Jinja2 format.

The ”debug” module also provides the ”verbosity” parameter, which determines the
minimum debug level at which this information will be displayed for us. By default,
”verbosity” is set to 0, which causes the information to be displayed every time the
playbook is called.

[msleczek@vm0-net projekt _ A]$ ansible-playbook playbook.yaml

PLAY [all] *******************************************************************************

TASK [Gathering Facts] *******************************************************************


ok: [10.8.232.123]
ok: [10.8.232.124]
ok: [10.8.232.121]
ok: [10.8.232.122]

TASK [Informacje o hostach] **************************************************************


ok: [10.8.232.121] => {
”msg”: ”Host: vm1-net, OS: RedHat, IPv4: 10.8.232.121.”
}
ok: [10.8.232.122] => {
”msg”: ”Host: vm2-net, OS: RedHat, IPv4: 10.8.232.122.”
}
ok: [10.8.232.123] => {
”msg”: ”Host: vm3-net, OS: RedHat, IPv4: 10.8.232.123.”
}
ok: [10.8.232.124] => {
”msg”: ”Host: vm4-net, OS: RedHat, IPv4: 10.8.232.124.”
}

PLAY RECAP *******************************************************************************


10.8.232.121 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

10 | The world of automation with Red Hat Ansible


10.8.232.122 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.8.232.123 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.8.232.124 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

[msleczek@vm0-net projekt _ A]$

Above you can see the result of making a very simple and short playbook, the
content of which is slightly above it. It uses the “debug” module discussed above to
display variables while the playbook is running.

Next, we will follow a more complex case of using variables and the Jinja2 template.
We will be working on a very simple inventory that only defines 4 hosts.

[msleczek@vm0-net projekt _ A]$ cat inventory


10.8.232.121
10.8.232.122
10.8.232.123
10.8.232.124
[msleczek@vm0-net projekt _ A]$

None of these 4 hosts have an HTTP service running:

[msleczek@vm0-net projekt _ A]$ for IP in `cat inventory`; do curl http://$IP; echo; done;
curl: (7) Failed to connect to 10.8.232.121 port 80: No route to host

curl: (7) Failed to connect to 10.8.232.122 port 80: No route to host

curl: (7) Failed to connect to 10.8.232.123 port 80: No route to host

curl: (7) Failed to connect to 10.8.232.124 port 80: No route to host

[msleczek@vm0-net projekt _ A]$

The task of our playbook will be to configure these 4 hosts to work as web servers.
To do this, they must have the ”httpd” software, an appropriately parameterized
”index.html” file, an active ”httpd” service and an open port for this service in the
system firewall. The ”index.html” file is parameterized as each of the web servers
is to provide slightly different content. For this purpose, we used the Jinja2 template.
Jinja2 template files should have the ”.j2” extension.

[msleczek@vm0-net projekt _ A]$ cat index.html.j2


Strona www na serwerze {{ ansible _ default _ ipv4.address }} z systemem {{ ansible _ os _
family }}.
Serwer ten nazywa się {{ ansible _ hostname }}.
[msleczek@vm0-net projekt _ A]$

In place of the Jinja2 template ”index.html.j2” variables, there should be appropriate


values that will be collected during the ”Gathering Facts” task. They can be different
for each host. The ”template” module is used to handle Jinja2 templates, the ”yum”
module to install the necessary software, the ”service” module to start the service,
and the ”firewalld” module to open the appropriate port in the firewall. At this stage,
I encourage you to read the output of the ”ansible-doc” command for these modules.

11 | The world of automation with Red Hat Ansible


[msleczek@vm0-net projekt _ A]$ cat web _ servers.yaml
---
- name: START WEB SERVERS
hosts: all
tasks:
- name: PACKAGE INSTALLATION
yum:
name: httpd
state: present
- name: ADD index.html file
template:
src: index.html.j2
dest: /var/www/html/index.html
- name: START HTTPD SERVICE
service:
name: httpd
state: started
enabled: true
- name: OPEN HTTP TRAFFIC
firewalld:
service: http
permanent: true
immediate: true
state: enabled

- name: TEST WEB SERVERS


hosts: localhost
become: false
gather _ facts: false
tasks:
- name: GET WEB SERVERS LIST
command: ”/usr/bin/cat ./inventory”
register: web _ servers
- name: CONNECT TO WEBPAGE
uri:
url: http://{{item}}
return _ content: true
status _ code: 200
loop: ”{{web _ servers.stdout _ lines}}”

[msleczek@vm0-net projekt _ A]$

The above playbook consists of two sets of tasks (”plays”). The first deals with
the configuration of web servers, and the second with the verification of what has
been done. The second set is executed on ”localhost” and has the ”Gathering
Facts” task disabled. In the first task, we assigned the contents of our inventory file
”./inventory” to the variable ”web_servers”. For this purpose, we have recorded the
result of the appropriate “command” under this variable. The ”register” parameter
is used for this.

In the second task of the second set of tasks, we use the ”uri” module to establish
an HTTP connection to each of our web servers in turn. The success of this task
depends on the received HTTP code. By default, the code 200 means success.
Performing these tests from the ”localhost” management station ensures that the
”httpd” service is working and the appropriate port on the firewall has been opened.

The newly registered ”web_servers” variable consists of many lines. Each of them
is accessed via the ”web_servers.stdout_lines” list. When we give this list as an
argument to the ”loop” expression, the module ”uri” will be executed as many times

12 | The world of automation with Red Hat Ansible


as there are values in the list. In each subsequent invocation of the module ”uri”, the
variable ”item” will take the value of the next value in the list, which is an argument
of the expression ”loop”. This is the first time we’ve used a loop in a playbook. We’ll
cover loops more in the next article.

[msleczek@vm0-net projekt _ A]$ ansible-playbook web _ servers.yaml

PLAY [START WEB SERVERS] ******************************************************************

TASK [Gathering Facts] ********************************************************************


ok: [10.8.232.122]
ok: [10.8.232.123]
ok: [10.8.232.124]
ok: [10.8.232.121]

TASK [PACKAGE INSTALLATION] ***************************************************************


changed: [10.8.232.122]
changed: [10.8.232.123]
changed: [10.8.232.124]
changed: [10.8.232.121]

TASK [ADD index.html file] *****************************************************************


changed: [10.8.232.122]
changed: [10.8.232.124]
changed: [10.8.232.121]
changed: [10.8.232.123]

TASK [START HTTPD SERVICE] ****************************************************************


changed: [10.8.232.122]
changed: [10.8.232.124]
changed: [10.8.232.121]
changed: [10.8.232.123]

TASK [OPEN HTTP TRAFFIC] ******************************************************************


changed: [10.8.232.122]
changed: [10.8.232.123]
changed: [10.8.232.124]
changed: [10.8.232.121]

PLAY [TEST WEB SERVERS] *******************************************************************

TASK [GET WEB SERVERS LIST] ***************************************************************


changed: [localhost]

TASK [CONNECT TO WEBPAGE] *****************************************************************


ok: [localhost] => (item=10.8.232.121)
ok: [localhost] => (item=10.8.232.122)
ok: [localhost] => (item=10.8.232.123)
ok: [localhost] => (item=10.8.232.124)

PLAY RECAP ********************************************************************************


10.8.232.121 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.8.232.122 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.8.232.123 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.8.232.124 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
localhost : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

[msleczek@vm0-net projekt _ A]$

You can see in ”PLAY RECAP” that all the tasks were successful.

13 | The world of automation with Red Hat Ansible


Now we can repeat our test from the beginning, and check the content returned by
the web servers.

[msleczek@vm0-net projekt _ A]$ for IP in `cat inventory`; do curl http://$IP; echo; done;
Strona www na serwerze 10.8.232.121 z systemem RedHat.
Serwer ten nazywa się vm1-net.

Strona www na serwerze 10.8.232.122 z systemem RedHat.


Serwer ten nazywa się vm2-net.

Strona www na serwerze 10.8.232.123 z systemem RedHat.


Serwer ten nazywa się vm3-net.

Strona www na serwerze 10.8.232.124 z systemem RedHat.


Serwer ten nazywa się vm4-net.

[msleczek@vm0-net projekt _ A]$

Here we just covered the basics of Jinja2 variables and templates. They are a key
element of playbooks and we will come back to them often in future articles.

14 | The world of automation with Red Hat Ansible


04
ANSIBLE, PART V – Loops, Conditional
Statements and Handlers

In an earlier article, we talked about collecting facts from managed nodes and
about how they can be used in our playbooks. We also showed how you can refer
to them in the pla ybook.

Going one step further, we’ll look at how they can be used to create conditional
tasks. The ”when” clause is used for this purpose. It can also be used to condition
subsequent tasks on the basis of previous results.

The ”when” clause uses Jinja2’s raw syntax, thus enabling the use of extensive
expressions using Jinja2’s tests (comparisons) and filters (data manipulation) as
well as logical expressions such as ”OR” or ”AND”. It also means that we have direct
access to the variables, without the need for double curly braces ”{{}}”.

[msleczek@vm0-net projekt _ A]$ cat playbook.yaml


---
- hosts: all
tasks:
- name: ”Informacje o hostach”
debug:
msg:
- ”Host: {{ansible _ hostname}}, Family: {{ansible _ os _ family}}”
- ”IPv4: {{ansible _ default _ ipv4.address}}”
- name: ”Dodatkowa informacje o {{ HOST }}”
shell: /usr/bin/uptime
register: uptime _ result
when: ansible _ default _ ipv4.address == HOST
- debug:
var: uptime _ result
when: ansible _ default _ ipv4.address == HOST or ansible _ default _ ipv4.address ==
’10.8.232.124’
...

[msleczek@vm0-net projekt _ A]$

This time, we have defined a list for the ”msg” option of the ”debug” module. It is
worth comparing its result with what it looked like in the previous article. A bit
lower, we also used the ”var” option of the ”debug” module, as well as the ”register”
parameter. It is worth revisiting this information in the previous article, if the
application of these parameters is not clear to you.

15 | The world of automation with Red Hat Ansible


There is no HOST variable declared anywhere in our playbook, as well as
in the inventory.
[msleczek@vm0-net projekt _ A]$ cat inventory
10.8.232.121
10.8.232.122
10.8.232.123
10.8.232.124
[msleczek@vm0-net projekt _ A]$

The HOST variable is defined during our playbook call with the ”--extra-vars” option.

[msleczek@vm0-net projekt _ A]$ ansible-playbook playbook.yaml --extra vars=”HOST=10.8.232.122”

PLAY [all] **************************************************************************************

TASK [Gathering Facts] *************************************************************************


ok: [10.8.232.121]
ok: [10.8.232.124]
ok: [10.8.232.122]
ok: [10.8.232.123]

TASK [Informacje o hostach] *******************************************************************


ok: [10.8.232.121] => {
”msg”: [
”Host: vm1-net, Family: RedHat”,
”IPv4: 10.8.232.121”
]
}
ok: [10.8.232.122] => {
”msg”: [
”Host: vm2-net, Family: RedHat”,
”IPv4: 10.8.232.122”
]
}
ok: [10.8.232.123] => {
”msg”: [
”Host: ms3-net, Family: RedHat”,
”IPv4: 10.8.232.123”
]
}
ok: [10.8.232.124] => {
”msg”: [
”Host: vm4-net, Family: RedHat”,
”IPv4: 10.8.232.124”
]
}

TASK [Dodatkowa informacje o 10.8.232.122] *****************************************************


skipping: [10.8.232.121]
skipping: [10.8.232.123]
skipping: [10.8.232.124]
changed: [10.8.232.122]

TASK [debug] ***********************************************************************************


skipping: [10.8.232.121]
ok: [10.8.232.122] => {
”uptime _ result”: {
”changed”: true,
”cmd”: ”/usr/bin/uptime”,
”delta”: ”0:00:00.006720”,
”end”: ”2020-05-11 12:16:21.952928”,
”failed”: false,
”rc”: 0,
”start”: ”2020-05-11 12:16:21.946208”,

16 | The world of automation with Red Hat Ansible


”stderr”: ””,
”stderr _ lines”: [],
”stdout”: ” 12:16:21 up 2 days, 3:17, 1 user, load average: 0.22, 0.05, 0.02”,
”stdout _ lines”: [
” 12:16:21 up 2 days, 3:17, 1 user, load average: 0.22, 0.05, 0.02”
]
}
}
skipping: [10.8.232.123]
ok: [10.8.232.124] => {
”uptime _ result”: {
”changed”: false,
”skip _ reason”: ”Conditional result was False”,
”skipped”: true
}
}
PLAY RECAP *************************************************************************************
10.8.232.121 : ok=2 changed=0 unreachable=0 failed=0 skipped=2 rescued=0
ignored=0
10.8.232.122 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0
ignored=0
10.8.232.123 : ok=2 changed=0 unreachable=0 failed=0 skipped=2 rescued=0
ignored=0
10.8.232.124 : ok=3 changed=0 unreachable=0 failed=0 skipped=1 rescued=0
ignored=0

[msleczek@vm0-net projekt _ A]$

The result of the last two tasks was additionally conditioned in our playbook with
the ”when” clause. The penultimate task was completed only for ”10.8.232.122”
and the last for ”10.8.232.122” and ”10.8.232.124”. It is also worth noting, that the
last task gives a different result for the two hosts as it depends on what happened
in the penultimate task.

The ”when” clause is useful in creating universal playbooks, that condition individual
tasks on the type or state of the node on which they will work. For example, if the
node’s operating system belongs to the ”Debian” family, we will use the ”apt” module
to install the software, and if it is ”RedHat”, then the ”yum” module. In our example,
we will additionally use the ”AND” logical condition to restrict the installation to only
one node with a specific IP address.

[msleczek@vm0-net projekt _ A]$ cat condicion.yaml


---
- name: START WEB SERVERS
hosts: all
tasks:
- name: Debian Family
apt:
name: apache2
state: present
become: true
when: ansible _ os _ family == ’Debian’ and ansible _ default _ ipv4.address == ’10.8.232.123’

- name: RedHat Family


yum:
name: httpd
state: present
become: true
when: ansible _ os _ family == ’RedHat’ and ansible _ default _ ipv4.address == ’10.8.232.123’

[msleczek@vm0-net projekt _ A]$

17 | The world of automation with Red Hat Ansible


We are working on the same inventory that contains four hosts. We will now use the
”--limit” option of the ”ansible-playbook” command to restrict playbook execution
to only two of them.

[msleczek@vm0-net projekt _ A]$ ansible-playbook condicion.yaml --limit 10.8.232.122,10.8.232.123

PLAY [START WEB SERVERS] ***********************************************************************

TASK [Gathering Facts] *************************************************************************


ok: [10.8.232.122]
ok: [10.8.232.123]

TASK [Debian Family] ***************************************************************************


skipping: [10.8.232.122]
skipping: [10.8.232.123]

TASK [RedHat Family] ***************************************************************************


skipping: [10.8.232.122]
changed: [10.8.232.123]

PLAY RECAP *************************************************************************************


10.8.232.122 : ok=1 changed=0 unreachable=0 failed=0 skipped=2 rescued=0
ignored=0
10.8.232.123 : ok=2 changed=1 unreachable=0 failed=0 skipped=1 rescued=0
ignored=0

[msleczek@vm0-net projekt _ A]$

As a result of our playbook, the software was installed only on 1 host ”10.8.232.123”.

More about conditional and boolean expressions can be found in the


Ansible documentation.

----

We already know from the previous article, that in Jinja2 templates we can refer to
variable values by using double curly braces ”{{ }}”. Comments can also be added to
Jinja2 templates. They are placed inside curly braces, between the hash characters
”{# #}”. Moreover, conditional expressions can also be used in Jinja2 templates.
They are placed inside curly braces with percent signs - ”{% %}”. These expressions
can be quite complex, and in addition to comparisons, they can include logical AND
and OR operators.

[msleczek@vm0-net projekt _ A]$ cat index.html.j2


Połączyłeś się do adresu IP: {{ ansible _ default _ ipv4.address }}.
{# To jest komentarz #}
{% if ansible _ os _ family == ’Debian’ %}
Działa tu system z rodziny debianowatych.
{% elif ansible _ os _ family == ’RedHat’ %}
Działa tu system z rodziny redhatowatych.
{% else %}
Działa tu system z rodziny nietypowej.
{% endif %}
Zapraszamy do przeglądnięcia strony https://networkers.pl.

[msleczek@vm0-net projekt _ A]$

18 | The world of automation with Red Hat Ansible


The ”{% elif %}” and ”{% else %}” sections are optional. However, the ”{% if %}”
and ”{% endif %}” sections are mandatory. As a result of the playbook execution,
only the lines that meet the conditions will be inserted into the target file from
our Jinja2 template.

[msleczek@vm0-net projekt _ A]$ cat jinja2-cond.yaml


---
- name: FILE CUSTOMIZATION
hosts: all
tasks:
- name: CUSTOMIZE index.html FILE
template:
src: ./index.html.j2
dest: /var/www/html/index.html

msleczek@vm0-net projekt _ A]$ ansible-playbook jinja2-cond.yaml --limit


10.8.232.121,10.8.232.122

PLAY [FILE CUSTOMIZATION] **********************************************************************

TASK [Gathering Facts] *************************************************************************


ok: [10.8.232.122]
ok: [10.8.232.121]

TASK [CUSTOMIZE index.html FILE] **************************************************************


changed: [10.8.232.121]
changed: [10.8.232.122]

PLAY RECAP *************************************************************************************


10.8.232.121 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0
ignored=0
10.8.232.122 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0
ignored=0

[msleczek@vm0-net projekt _ A]$ curl http://10.8.232.122


Połączyłeś się do adresu IP: 10.8.232.122.

Działa tu system z rodziny redhatowatych.

Zapraszamy do przeglądnięcia strony https://networkers.pl.

[msleczek@vm0-net projekt _ A]$

After establishing the HTTP connection, you can see the content of the
”index.html” file, which was created from our template ”index.html.j2”.

----

Loops can also be used at the level of YAML and Jinja2 templates. They enable
the cyclical repetition of the same tasks on different elements. Usually they stop
working after all items on the list have passed through or as a result of some event
or condition being met.

Below you can see the use of a ”for” loop inside the Jinja2 template. For this
purpose, the expression ”for .. in .. endfor” is used appropriately inside curly braces
and percent signs - ”{% %}”.

19 | The world of automation with Red Hat Ansible


[msleczek@vm0-net projekt _ A]$ cat index.html.j2
Połączyłeś sie do adresu IP: {{ ansible _ default _ ipv4.address }}.

{% for item in ansible _ dns.nameservers %}


Korzystam z serwera DNS: {{ item }}.
{% endfor %}

Zapraszamy do przeglądnięcia strony https://networkers.pl.

[msleczek@vm0-net projekt _ A]$

During fact gathering, a list ”ansible_dns.nameservers” was built for each node,
containing all DNS servers configured on it. In the Jinja2 template, we used the
expression ”for” to list all DNS servers on this list. While the Jinja2 template has
changed, our playbook looks exactly the same as in the previous example. The
result of his work can be seen below.

[msleczek@vm0-net projekt _ A]$ cat jinja2-cond.yaml


---
- name: FILE CUSTOMIZATION
hosts: all
tasks:
- name: CUSTOMIZE index.html FILE
template:
src: ./index.html.j2
dest: /var/www/html/index.html

[msleczek@vm0-net projekt _ A]$ ansible-playbook jinja2-cond.yaml --limit


10.8.232.123,10.8.232.124

PLAY [FILE CUSTOMIZATION] *********************************************************************

TASK [Gathering Facts] ************************************************************************


ok: [10.8.232.123]
ok: [10.8.232.124]

TASK [CUSTOMIZE index.html FILE] **************************************************************


changed: [10.8.232.123]
changed: [10.8.232.124]

PLAY RECAP *************************************************************************************


10.8.232.123 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0
ignored=0
10.8.232.124 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0
ignored=0

[msleczek@vm0-net projekt _ A]$ curl http://10.8.232.123


Połączyłeś sie do adresu IP: 10.8.232.123.

Korzystam z serwera DNS: 10.8.252.243.


Korzystam z serwera DNS: 10.8.252.244.
Korzystam z serwera DNS: 10.8.232.61.

Zapraszamy do przeglądnięcia strony https://networkers.pl.

[msleczek@vm0-net projekt _ A]$


[msleczek@vm0-net projekt _ A]$

After establishing the HTTP connection, you can see the content of the
”index.html” file, which was created from our template ”index.html.j2”.

20 | The world of automation with Red Hat Ansible


----

In the previous article, we showed the first use of loops inside a playbook. The
”loop” or ”until” statements are recommended for handling such loops. There
is also the expression ”with_ <lookup>”, which was more widely used in the past but
should be avoided now.

The use of loops inside playbooks is quite wide. We use them when we want to
install more than one package, unblock more than one port number, set up more
than one user account, create more than one VLAN or adjust the permissions
of more than one file. What is common to all these tasks is the ”more than one”
direction. Thus, we will use the loop when we want to perform a task more than
once, but not necessarily with the same variable values.

We use the expression ”until”, when the loop must run until a certain result
is obtained, and the expression ”loop”, when it is to run for each item in the specified
list. In practice, the ”loop” is much more used, so we’ll go through a few different
examples of how it is used.

We’ll start by using a ”loop” for simple lists. By default, the variable ”{{ item }}”
is a placeholder. It takes sequentially the values of the elements from the list, which
is a parameter of the ”loop”.

[msleczek@vm0-net projekt _ A]$ cat loop _ list.yaml


---
- name: ”Play with loops”
hosts: localhost
gather _ facts: false
vars:
devices:
- router
- switch
- hub
- firewall
tasks:
- name: ”Loop through list”
debug:
msg: ”{{ item }}”
loop: ”{{ devices }}”

- name: ”Loop through list”


debug:
msg: ”{{ item }}”
loop:
- Linux
- Red Hat
- Cisco Systems
- networkers.pl
[msleczek@vm0-net projekt _ A]$

When we want to refer to the list defined in the ”vars” section or in a special YAML
file with variables, we need to refer to it by double brace ”{{ }}”. It is also possible to
define the list directly in the ”loop” expression.

21 | The world of automation with Red Hat Ansible


[msleczek@vm0-net projekt _ A]$ ansible-playbook loop _ list.yaml

PLAY [Play with loops] *************************************************************************

TASK [Loop through list] ***********************************************************************


ok: [localhost] => (item=router) => {
”msg”: ”router”
}
ok: [localhost] => (item=switch) => {
”msg”: ”switch”
}
ok: [localhost] => (item=hub) => {
”msg”: ”hub”
}
ok: [localhost] => (item=firewall) => {
”msg”: ”firewall”
}

TASK [Loop through list] ***********************************************************************


ok: [localhost] => (item=Linux) => {
”msg”: ”Linux”
}
ok: [localhost] => (item=Red Hat) => {
”msg”: ”Red Hat”
}
ok: [localhost] => (item=Cisco Systems) => {
”msg”: ”Cisco Systems”
}
ok: [localhost] => (item=networkers.pl) => {
”msg”: ”networkers.pl”
}

PLAY RECAP *************************************************************************************


localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

[msleczek@vm0-net projekt _ A]$

Above you can see the result of our playbook run. It displays all the items in the
defined lists. For each of them, the ”loop” ran as many times as the ”msg” messages
can be seen.

----

There are times when we want to loop through a dictionary object. The dictionary
groups together several variables, called keys, with assigned values. Each of
these groups usually parameterizes one object or element. In our example it will
be a VLAN. Since the expression ”loop” expects a list argument, we will apply the
”dict2items” filter to manipulate the data. It will convert our dictionary into a list that
the ”loop” can handle.

[msleczek@vm0-net projekt _ A]$ cat loop _ dict.yaml


---
- name: ”Play with loops”
hosts: localhost
gather _ facts: false
vars:
vlans:
100:
name: ”MGMT”
ip4: ”10.8.100.1”
200:

22 | The world of automation with Red Hat Ansible


name: ”DATA”
ip4: ”10.8.200.1”
208:
name: ”VOICE”
ip4: ”10.8.208.1”
tasks:
- name: ”Loop through dictionary”
debug:
msg: >
VLAN={{vlan.key}}
NAME={{vlan.value.name}}
IPv4={{vlan.value.ip4}}
loop: ”{{ vlans|dict2items }}”
loop _ control:
loop _ var: vlan
[msleczek@vm0-net projekt _ A]$

By default, inside the loop, successive values are assigned to the ”item” variable.
In our example, we changed this name to ”vlan” using the ”loop_var” variable of
the ”loop_control” section. The ”loop_control” section is used to manipulate the
behavior of a loop, including the amount and type of information it provides. See the
Ansible documentation for more information on ”loop_control”.

In this example, the name ”vlan” seems more intuitive. However, in some cases we
may have to change this name due to overlapping loops and collisions in names.
An example would be to use a job in the “loop” that uses the loop itself. Then the
variable with the same name - ”item” - cannot be used in the outer and inner loops
at the same time.

[msleczek@vm0-net projekt _ A]$ ansible-playbook loop _ dict.yaml

PLAY [Play with loops] *************************************************************************

TASK [Loop through dictionary] ****************************************************************


ok: [localhost] => (item={’key’: 100, ’value’: {’name’: ’MGMT’, ’ip4’: ’10.8.100.1’}}) => {
”msg”: ”VLAN=100 NAME=MGMT IPv4=10.8.100.1\n”
}
ok: [localhost] => (item={’key’: 200, ’value’: {’name’: ’DATA’, ’ip4’: ’10.8.200.1’}}) => {
”msg”: ”VLAN=200 NAME=DATA IPv4=10.8.200.1\n”
}
ok: [localhost] => (item={’key’: 208, ’value’: {’name’: ’VOICE’, ’ip4’: ’10.8.208.1’}}) => {
”msg”: ”VLAN=208 NAME=VOICE IPv4=10.8.208.1\n”
}

PLAY RECAP *************************************************************************************


localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

[msleczek@vm0-net projekt _ A]$

Above you can see the effect of our playbook run and all elements of the defined
dictionary with VLAN configuration.

----

Another use of ”loop” is to perform operations on successive files of a given


directory.

23 | The world of automation with Red Hat Ansible


In our example, we will use a directory with the following content:

[msleczek@vm0-net projekt _ A]$ ls loop _ *.yaml


loop _ dict.yaml loop _ dir.yaml loop _ file.yaml loop _ list.yaml
[msleczek@vm0-net projekt _ A]$ pwd
/home/msleczek/projekt _ A
[msleczek@vm0-net projekt _ A]$

To pass a list of files to the input of the ”loop”, we will use the ”lookup” function.
Its first argument will be the ”fileglob” option, which lists the files in the specified
directory, that match the pattern specified in the second argument of this function
(files only, not directories). The search is carried out locally at the management
station, where the playbook was launched. By default, the output will be a list of
absolute file paths with the comma ”,” as the separator. If we want the output to get
a typical list, set the ”wantlist” variable inside this function to ”true”. This is shown
in the example below.

[msleczek@vm0-net projekt _ A]$ cat loop _ dir.yaml


---
- name: ”Play with loops”
hosts: localhost
gather _ facts: false
tasks:
- name: ”Loop through directory files”
debug:
msg: ”{{ item }}”
loop: ”{{ lookup(’fileglob’, ’loop _ *.yaml’, wantlist=true) }}”

[msleczek@vm0-net projekt _ A]$ ansible-playbook loop _ dir.yaml

PLAY [Play with loops] *************************************************************************

TASK [Loop through directory files] ************************************************************


ok: [localhost] => (item=/home/msleczek/projekt _ A/loop _ dir.yaml) => {
”msg”: ”/home/msleczek/projekt _ A/loop _ dir.yaml”
}
ok: [localhost] => (item=/home/msleczek/projekt _ A/loop _ list.yaml) => {
”msg”: ”/home/msleczek/projekt _ A/loop _ list.yaml”
}
ok: [localhost] => (item=/home/msleczek/projekt _ A/loop _ dict.yaml) => {
”msg”: ”/home/msleczek/projekt _ A/loop _ dict.yaml”
}
ok: [localhost] => (item=/home/msleczek/projekt _ A/loop _ file.yaml) => {
”msg”: ”/home/msleczek/projekt _ A/loop _ file.yaml”
}

PLAY RECAP *************************************************************************************


localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

[msleczek@vm0-net projekt _ A]$

You can also perform operations on files located on a managed node. For
this purpose, use the ”find” module. More about it can be found in the Ansible
documentation.

The loop ”loop” also allows you to perform specific operations for each line of the
specified file.

24 | The world of automation with Red Hat Ansible


[msleczek@vm0-net projekt _ A]$ cat ./inventory
10.8.232.121
10.8.232.122
10.8.232.123
10.8.232.124
[msleczek@vm0-net projekt _ A]$ cat loop _ file.yaml
---
- name: ”Play with loops”
hosts: localhost
gather _ facts: false
tasks:
- name: ”Loop through file content”
debug:
msg: ”IP _ ADDRESS={{ item }}”
loop: ”{{ lookup(’file’, ’./inventory’).splitlines() }}”
[msleczek@vm0-net projekt _ A]$

To pass the contents of a file to the input of the ”loop”, we will use the ”lookup”
function. Its first argument will be the ’file’ option, which lists the contents of the
file, and the second argument will be the location of this file. If you want to get
a list in which each line of the file will be a separate element, use the Python method
”splitlines ()”.

[msleczek@vm0-net projekt _ A]$ cat string _ methods.yaml


---
- name: ”Play with methods”
hosts: localhost
gather _ facts: false
vars:
TEKST: >
aAbB:cCdD:eEfF
aaaa:aaaa:aaaa
BBBB-BBBB-BBBB
AaBb CcDd EeFf
tasks:
- name: ”String whitout method”
debug:
msg: ”{{ TEKST }}”

- name: ”String method swapcase()”


debug:
msg: ”{{ TEKST.swapcase() }}”

- name: ”String method lower()”


debug:
msg: ”{{ TEKST.lower() }}”

- name: ”String method capitalize()”


debug:
msg: ”{{ TEKST.capitalize() }}”

- name: ”String method split(’:’)”


debug:
msg: ”{{ TEKST.split(’:’) }}”

- name: ”String method count(BBBB)”


debug:
msg: ”{{ TEKST.count(’BBBB’) }}”

[msleczek@vm0-net projekt _ A]$

25 | The world of automation with Red Hat Ansible


The list of available methods is very large. We used some useful operations on the
text:

• ”swapcase()” reverses the case of letters, so lowercase becomes uppercase,


and uppercase becomes lowercase.
• ”lower ()” makes all letters lowercase.
• ”capitalize()” makes only the first letter of the entire string uppercase.
• ”split()” splits the text based on the given separator.
• ”count()” counts the number of occurrences of a given pattern.

Below, we have displayed the original text first and then its variations
successively using various methods. Due to the use of the ”>” operator, this is 1 line
of continuous text.

[msleczek@vm0-net projekt _ A]$ ansible-playbook string _ methods.yaml

PLAY [Play with methods] ***********************************************************************

TASK [String whitout method] ******************************************************************


ok: [localhost] => {
”msg”: ”aAbB:cCdD:eEfF aaaa:aaaa:aaaa BBBB-BBBB-BBBB AaBb CcDd EeFf\n”
}

TASK [String method swapcase()] ****************************************************************


ok: [localhost] => {
”msg”: ”AaBb:CcDd:EeFf AAAA:AAAA:AAAA bbbb-bbbb-bbbb aAbB cCdD eEfF\n”
}

TASK [String method lower()] *******************************************************************


ok: [localhost] => {
”msg”: ”aabb:ccdd:eeff aaaa:aaaa:aaaa bbbb-bbbb-bbbb aabb ccdd eeff\n”
}

TASK [String method capitalize()] **************************************************************


ok: [localhost] => {
”msg”: ”Aabb:ccdd:eeff aaaa:aaaa:aaaa bbbb-bbbb-bbbb aabb ccdd eeff\n”
}

TASK [String method split(’:’)] *****************************************************************


ok: [localhost] => {
”msg”: [
”aAbB”,
”cCdD”,
”eEfF aaaa”,
”aaaa”,
”aaaa BBBB-BBBB-BBBB AaBb CcDd EeFf\n”
]
}

TASK [String method count(BBBB)] ***************************************************************


ok: [localhost] => {
”msg”: ”3”
}

PLAY RECAP *************************************************************************************


localhost : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0
ignored=0

[msleczek@vm0-net projekt _ A]$

26 | The world of automation with Red Hat Ansible


There are many more uses for loops, so see the Ansible documentation for more.

----

Handlers are common tasks, whose execution is triggered by state change


notifications from other tasks. Using them translates into better efficiency and
performance of the playbook. The task in the hander will only be executed once,
no matter how many times it is triggered by various other tasks. Additionally, if for
a given node the task in the hander does not receive any notification, it will not be
started at all.

[msleczek@vm0-net projekt _ A]$ cat playbook.yaml


---
- name: START WEB SERVERS
hosts: all
tasks:
- name: PACKAGE INSTALLATION
yum:
name: httpd
state: present
notify:
- START HTTPD SERVICE
- OPEN HTTP TRAFFIC

handlers:
- name: START HTTPD SERVICE
service:
name: httpd
state: started
enabled: true
- name: OPEN HTTP TRAFFIC
firewalld:
service: ”{{ item }}”
permanent: true
immediate: true
state: enabled
loop:
- http
- https

[msleczek@vm0-net projekt _ A]$

In our example, the handler list was defined with a job using the ”yum” module. The
”notify” section serves this purpose, where the names of the handlers to be notified,
when a change is made are indicated. Tasks that handle handles are defined in
a special ”handlers” section.

27 | The world of automation with Red Hat Ansible


[msleczek@vm0-net projekt _ A]$ ansible-playbook playbook.yaml

PLAY [START WEB SERVERS] ***********************************************************************

TASK [Gathering Facts] *************************************************************************


ok: [10.8.232.124]
ok: [10.8.232.123]
ok: [10.8.232.122]
ok: [10.8.232.121]

TASK [PACKAGE INSTALLATION] ********************************************************************


ok: [10.8.232.122]
ok: [10.8.232.121]
changed: [10.8.232.124]
changed: [10.8.232.123]

RUNNING HANDLER [START HTTPD SERVICE] *********************************************************


changed: [10.8.232.123]
changed: [10.8.232.124]

RUNNING HANDLER [OPEN HTTP TRAFFIC] ***********************************************************


changed: [10.8.232.124] => (item=http)
changed: [10.8.232.123] => (item=http)
changed: [10.8.232.124] => (item=https)
changed: [10.8.232.123] => (item=https)

PLAY RECAP *************************************************************************************


10.8.232.121 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.8.232.122 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.8.232.123 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.8.232.124 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
[msleczek@vm0-net projekt _ A]$

Thanks to the handlers in our playbook, if a node has already installed the ”httpd”
package, it will not try to do anything else. In our case, it will not try to open ports
for HTTP and HTTPS traffic and start ”httpd” services. However, if the node does
not have the ”httpd” package, the appropriate packages will be installed, which in
turn will trigger a notify and run handlers, whose task is to ensure that the ”httpd”
service is running and working after reboot, and the system firewall allowed HTTP
and HTTPS traffic to it.

In our example you can see that only 2 out of 4 servers did not have the ”httpd”
package installed and only for them the additional tasks from the ”handlers” section
were run.

28 | The world of automation with Red Hat Ansible


05
ANSIBLE, PART VI – Network
Automation and some optimizations

Ansible is great for managing network devices. This applies to both simple and
more advanced changes that can be made directly or through various controllers,
such as Cisco NSO (Network Services Orchestrator), Cisco ACI (Application Centric
Infrastructure), Cisco SD-WAN or Cisco DNA (Digital Network Architecture).
In this article, we will focus on direct automation of selected Cisco Systems network
devices.

When we use Ansible to manage GNU/Linux hosts, Ansible copies a module written
in Python to the managed host and runs it there. This has a great advantage, especially
when the execution of tasks is complex and time-consuming. Thanks to this, the
node used for management is almost not overloaded and with little resources it
can serve a very large number of hosts. In the case of network automation, it looks
a bit different, at least from the inside, because from the operational point of view,
nothing changes.

There are several reasons for this. A module that runs on a host usually runs
directly on its configuration files. In the case of network devices, you do not work
directly with files. Configuration is available through an appropriate interface, such
as CLI over SSH, XML over SSH or API over HTTP / HTTPS. The module running on
the host additionally creates various directories and files, which is not often either
possible or efficient on network devices due to the type of memory used in network
devices. The module also requires Python to work, which unfortunately most older
network devices do not have. This leads to the fact that it is most convenient to
execute the Ansible module locally and then connect via the shared interface.

Ansible establishes an SSH connection by default and tries to send its module
to the remote node. To change this, we need to use one of the three types of
connections available:

• network_cli which uses CLI over SSH (persistent connection).


• netconf which uses XML over SSH (persistent connection).
• httpapi which uses the API over HTTP / HTTPS (persistent connection).

Previously, the „local” connection type was used for this purpose, which indicated to
Ansible that the module must be started locally and the connection to the managed
node must be established in the manner specified in the „provider” dictionary.

29 | The world of automation with Red Hat Ansible


Unfortunately, this was not done efficiently as the connection was re-opened
and closed for each task. It is currently not recommended to use the „local”
connection type.

In the case of network automation, we must assume that the module will run
directly on the managing node, and not on network devices. Often, it will even send
some commands to network devices, wait for information received, do something
locally after receiving it, and then send something back to them. Therefore,
with large number of network devices, it may be required to use more Ansible nodes.
Such clusters can be managed from one place using the Red Hat Ansible
Automation Platform.

Playbook’s tasks for each node are performed sequentially in the order in which
they are arranged. However, they can be performed in parallel on many nodes at
the same time. How many at the same time depends on our configuration - by
default 5. This can be modified using the „-f” or „--fork” option of the „ansible” and
„ansible-playbook” commands or the „forks” parameter of the „ansible.cfg” file.
This parameter determines the number of parallel processes that will be available
to run the tasks. In this way, the main process splits or in other words, forks into
many smaller ones. By default, Ansible waits for all other nodes to finish executing
the previous task before starting a new task on the first node. Even when the rest
of the processes are not busy and do nothing. This default behavior is due to the
default setting of the „strategy” parameter to „linear”.

When a small group of devices is significantly slower than the rest, consider
changing this parameter. After setting the „strategy” parameter to „free”, Ansible
will execute the next tasks as quickly as possible. That is, as soon as there are
free-to-use processes. It will continue to execute the tasks in the correct order, but
when no more processes are needed for the current task, they will start handling
subsequent tasks on those nodes that have already completed the previous task.
This makes the use of available processes more efficient, which translates into
improved overall efficiency.

Additionally, if performance is important to us, we should avoid issuing the „show


running-config” command. It is one of the most resource-consuming commands,
that can be sent to a network device. Its processing takes so long that we advise
against executing any commands based on it, on the large number of network
devices and using it in playbooks.

Now let’s go through a few examples of using Ansible to automate Cisco Systems
devices. We will use two switches for this purpose:

• Cisco Nexus 3172TQ with NXOS version 7 (Ansible modules prefixed with
„nxos_”).
• Cisco Catalyst 9300 running IOS-XE version 16 (Ansible modules prefixed with
„ios_”).

30 | The world of automation with Red Hat Ansible


As we do our tasks only on two different switches, there might as well be many, many
more. Therefore, while these are simple activities, when we talk about performing
them on a larger number of devices or each time a service is created or deleted,
doing it manually is ineffective, time-consuming and prone to errors or omissions.

Our inventory file has been slightly modified. With „ansible_network_os” we have
indicated which operating systems are used by our network devices. We also
used the simplest way to log in to them, using a user and an explicit (clear text)
password. Ansible allows you to store sensitive information such as passwords
in special credential containers so that they are not available in clear text. Due to
the fact, that our goal here is to show you how to manage network devices in the
easiest way, and we have not shown this connection method yet, we will use it now.

[msleczek@vm0-net projekt_A]$ cat inventory


[all:vars]
ansible_user=admin
ansible_ssh_pass=networkers.pl
ansible_connection=network_cli
ansible_become=no

[catalyst]
10.8.100.65 ansible_network_os=ios

[nexus]
10.8.100.66 ansible_network_os=nxos

[msleczek@vm0-net projekt_A]$

We will use the „network_cli” method to establish connections. We’ll start with
something simple and useful, which is collecting configurations from all network
devices. For this purpose we will use the parameters „backup” of modules
„ios_config” and „nxos_config”.

[msleczek@vm0-net projekt_A]$ cat backup.yml


---
- hosts: catalyst
gather_facts: no
tasks:
- name: IOS/IOS-XE Backup Configuration
ios_config:
backup: yes

- hosts: nexus
gather_facts: no
tasks:
- name: NXOS Backup Configuration
nxos_config:
backup: yes

[msleczek@vm0-net projekt_A]$ ansible-playbook backup.yml

PLAY [catalyst] ***************************************************************************

TASK [IOS/IOS-XE Backup Configuration] *****************************************************


changed: [10.8.100.65]

PLAY [nexus] *********************************************************************

31 | The world of automation with Red Hat Ansible


TASK [NXOS Backup Configuration] *************************************************
changed: [10.8.100.66]

PLAY RECAP ***********************************************************************


10.8.100.65 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.8.100.66 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

[msleczek@vm0-net projekt_A]$

If we do not indicate explicitly where the backups must be stored and we do not
provide the names of their files, then in the current directory Ansible will create
a directory called „backup/” and place configurations from network devices there.

[msleczek@vm0-net projekt_A]$ ls -l backup


total 20
-rw-rw-r--. 1 msleczek msleczek 5535 May 14 13:05 10.8.100.65_config.2020-05-14@13:05:13
-rw-rw-r--. 1 msleczek msleczek 9962 May 14 13:05 10.8.100.66_config.2020-05-14@13:05:18
[msleczek@vm0-net projekt_A]$

Now let’s do something more demanding. On the Cisco Catalyst 9300 switch,
we will secure the VTY lines through which the switch is managed. This will only
be possible from two IP addresses and only via SSH. Additionally, everyone will be
automatically logged out after 15 minutes of inactivity. If there has been a change
in the configuration, it will be permanently saved.

CLI commands available in Cisco IOS-XE and Cisco NXOS systems are related to
the section or context in which they are issued. This means that the same command
can do quite different things depending on where it is issued. The commands we
want to issue are placed in the „lines” list. The „parents” parameter allows you
to set the appropriate context or section in which they will be published.

It also happens that we want to delete some previous commands before issuing
new ones. The „before” parameter is used for this purpose. In our example, we first
delete the entire ACL and then create a new one from scratch. The commands on
the „before” list will be issued only if the module has to make changes.

The „match” parameter is used to verify this, which compares the current
configuration with what we want to achieve within a given context. This parameter
can take the following values:

• „none” - we do not check anything in the current configuration, so the commands


will always be entered.
• „strict” - checks if the requested commands are in the correct order in the
current configuration.
• „exact” - similar to strict, but the given section cannot contain anything else.
• „line” - checks each command line by line with configuration, if any line match,
it does nothing.

32 | The world of automation with Red Hat Ansible


In our example, we chose „strict”, which means that the commands in the „lines”
list will be checked not only for their occurrence, but also for the position they
should be in, which is crucial with ACL lists.

Further, by using the „parents” parameter, the commands in the „lines” list that
aim to protect VTY lines 0 through 15 will be entered in the configuration mode
of these lines.

[msleczek@vm0-net projekt_A]$ cat configure_switches.yml


---
- hosts: catalyst
gather_facts: false
tasks:
- name: Create ACL
ios_config:
match: strict
before: no ip access-list extended MGMT-ACL
parents: ip access-list extended MGMT-ACL
lines:
- permit ip host 10.8.232.120 any
- permit ip host 10.8.100.100 any
- name: Harden VTY lines
ios_config:
parents:
- line vty 0 15
lines:
- exec-timeout 15 0
- transport input ssh
- access-class MGMT-ACL in
- name: Save Configuration
ios_config:
save_when: changed

- hosts: nexus
gather_facts: false
tasks:
- name: Configure hostname and domain name
nxos_system:
hostname: nx3172-lab
domain_name: networkers.pl
- name: Configure DNS Servers
nxos_system:
name_servers:
- 1.1.1.1
- 8.8.4.4
- name: Banner MOTD
nxos_banner:
banner: motd
text: „{{ lookup(‚file’, ‚banner.txt’) }}”
state: present
- name: VLAN creation
nxos_vlans:
config:
- vlan_id: 123
name: ANSIBLE-123
enabled: yes
state: active
- name: Save Configuration
nxos_config:
save_when: changed

[msleczek@vm0-net projekt_A]$

33 | The world of automation with Red Hat Ansible


On the Cisco Nexus 3172TQ switch, we will set the hostname, domain name, DNS
servers, MOTD (Message Of The Day) message that appears during login and
one VLAN. Additionally, if there has been a change in the configuration, it will be
permanently saved.

[msleczek@vm0-net projekt_A]$ ansible-playbook configure_switches.yml

PLAY [catalyst] ***************************************************************************

TASK [Create ACL] *************************************************************************


changed: [10.8.100.65]

TASK [Harden VTY lines] *******************************************************************


changed: [10.8.100.65]

TASK [Save Configuration] ******************************************************************


changed: [10.8.100.65]

PLAY [nexus] ******************************************************************************

TASK [Configure hostname and domain name] **************************************************


changed: [10.8.100.66]

TASK [Configure DNS Servers] ***************************************************************


changed: [10.8.100.66]

TASK [Banner MOTD] ************************************************************************


changed: [10.8.100.66]

TASK [VLAN creation] **********************************************************************


changed: [10.8.100.66]

TASK [Save Configuration] ******************************************************************


changed: [10.8.100.66]

PLAY RECAP ********************************************************************************


10.8.100.65 : ok=3 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
10.8.100.66 : ok=5 changed=5 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

[msleczek@vm0-net projekt_A]$

Network devices can also be managed using ad hoc commands. This applies
to both making changes and collecting certain data. The former may be useful
when changing the password on the large number of devices, and the latter for
the purpose of inventorying or verifying the status of certain elements or services
of the device.

Separate specialized modules are used to gather facts from network devices. For
devices with the IOS or IOS-XE system, the „ios_facts” module should be used for
this purpose, and for devices with the NXOS system, the „nxos_facts” module.

34 | The world of automation with Red Hat Ansible


[msleczek@vm0-net projekt_A]$ ansible -m ios_facts catalyst -a „gather_subset=hardware”
10.8.100.65 | SUCCESS => {
„ansible_facts”: {
„ansible_net_api”: „cliconf”,
„ansible_net_filesystems”: [
„flash:”
],
„ansible_net_filesystems_info”: {
„flash:”: {
„spacefree_kb”: 8897616.0,
„spacetotal_kb”: 11087104.0
}
},
„ansible_net_gather_network_resources”: [],
„ansible_net_gather_subset”: [
„default”,
„hardware”
],
„ansible_net_hostname”: „cat9300”,
„ansible_net_image”: „flash:packages.conf”,
„ansible_net_iostype”: „IOS”,
„ansible_net_memfree_mb”: 1105641.8515625,
„ansible_net_memtotal_mb”: 1392644.6328125,
„ansible_net_model”: „C9300-48P”,
„ansible_net_python_version”: „3.6.8”,
„ansible_net_serialnum”: „FCW2248E156”,
„ansible_net_stacked_models”: [
„C9300-48P”
],
„ansible_net_stacked_serialnums”: [
„FCW2248E156”
],
„ansible_net_system”: „ios”,
„ansible_net_version”: „16.06.05”,
„ansible_network_resources”: {},
„discovered_interpreter_python”: „/usr/libexec/platform-python”
},
„changed”: false
}
[msleczek@vm0-net projekt_A]$

Below you can see our MOTD banner, which we set up on the Cisco Nexus
3172TQ switch:

[msleczek@vm0-net projekt_A]$ ansible -m nxos_command nexus -a „commands=’show banner motd’”


10.8.100.66 | SUCCESS => {
„ansible_facts”: {
„discovered_interpreter_python”: „/usr/libexec/platform-python”
},
„changed”: false,
„stdout”: [
„W networkers.pl kupisz m.in. produkty:\n- Cisco Systems\n- Red Hat\n- LINBIT\n-
Storware\n- VMware\n- Veeam\n- NextCloud\n- Collabora Online\n- ONLYOFFICE\n- SuperMicro\n\
nZapraszamy do dzialu PRODUKTY i ROZWIAZANIA.”
],
„stdout_lines”: [
[
„W networkers.pl kupisz m.in. produkty:”,
„- Cisco Systems”,
„- Red Hat”,
„- LINBIT”,
„- Storware”,
„- VMware”,
„- Veeam”,
„- NextCloud”,
„- Collabora Online”,

35 | The world of automation with Red Hat Ansible


„- ONLYOFFICE”,
„- SuperMicro”,
„”,
„Zapraszamy do dzialu PRODUKTY i ROZWIAZANIA.”
]
]
}
[msleczek@vm0-net projekt_A]$

It is also possible to use Jinja2 templates to generate configurations that will then
be sent to network devices. What’s cool about them is that Jinja2 templates are
supported directly by network modules, so you don’t need to use the „template”
module indirectly.

Finally, it is worth adding that the Red Hat Automation Hub includes many ready-
made roles, plugins, modules and collections created by various manufacturers
of network solutions. An example is the well-developed Cisco ACI (Application
Centric Infrastructure) automation kit for network and policy management in data
centers.

If you are interested in automating and orchestrating your network and its services
on a larger scale, but not necessarily in data centers or enterprises, we encourage
you to familiarize yourself with Cisco NSO (Network Services Orchestrator), which
allows you to cooperate with Ansible and make any changes atomically across the
entire network infrastructure.

36 | The world of automation with Red Hat Ansible


THANK YOU FOR YOUR ATTENTION.
Did you miss the first part? You can download it from here: GET FOR FREE

Are you interested in third part of Ansible e-book or other publications?


Follow us on our social media:

OVP Storware Red Hat

2022

You might also like