Ansible Zoran
Ansible Zoran
Ansible Zoran
org
Ansible Succinctly
By
Zoran Maksimovic
Foreword by Daniel Jebaraj
Copyright © 2021 by Syncfusion, Inc.
If you obtained this book from any other source, please register and download a free copy from
www.syncfusion.com.
The authors and copyright holders provide absolutely no warranty for any information provided.
The authors and copyright holders shall not be liable for any claim, damages, or any other
liability arising from, out of, or in connection with the information in this book.
Please do not use this book if the listed terms are unacceptable.
www.dbooks.org
Table of Contents
Introduction .............................................................................................................................11
Resources .......................................................................................................................13
Orchestration ...................................................................................................................16
Users ...............................................................................................................................17
Modules ...........................................................................................................................17
Plugins .............................................................................................................................18
Inventories .......................................................................................................................18
4
Ansible playbooks ............................................................................................................18
Vagrant ................................................................................................................................20
VirtualBox .............................................................................................................................21
www.dbooks.org
Ansible Manager Server configuration ..................................................................................46
Command modules..........................................................................................................53
Basic structure......................................................................................................................56
Dry run.............................................................................................................................59
Variables ..............................................................................................................................59
6
Chapter 12 Ansible Vault: Data Encryption ..........................................................................76
Encrypt ............................................................................................................................76
Decrypt ............................................................................................................................77
View.................................................................................................................................77
Edit ..................................................................................................................................77
Rekey ..............................................................................................................................77
www.dbooks.org
The Story Behind the Succinctly Series
of Books
Daniel Jebaraj, CEO
Syncfusion, Inc.
taying on the cutting edge
S As many of you may know, Syncfusion is a provider of software components for the
Microsoft platform. This puts us in the exciting but challenging position of always
being on the cutting edge.
Whenever platforms or tools are shipping out of Microsoft, which seems to be about
every other week these days, we have to educate ourselves, quickly.
While more information is becoming available on the Internet and more and more books are
being published, even on topics that are relatively new, one aspect that continues to inhibit us is
the inability to find concise technology overview books.
We are usually faced with two options: read several 500+ page books or scour the web for
relevant blog posts and other articles. Just as everyone else who has a job to do and customers
to serve, we find this quite frustrating.
We firmly believe, given the background knowledge such developers have, that most topics can
be translated into books that are between 50 and 100 pages.
This is exactly what we resolved to accomplish with the Succinctly series. Isn’t everything
wonderful born out of a deep desire to change things for the better?
Free forever
Syncfusion will be working to produce books on several topics. The books will always be free.
Any updates we publish will also be free.
8
Free? What is the catch?
There is no catch here. Syncfusion has a vested interest in this effort.
As a component vendor, our unique claim has always been that we offer deeper and broader
frameworks than anyone else on the market. Developer education greatly helps us market and
sell against competing vendors who promise to “enable AJAX support with one click,” or “turn
the moon to cheese!”
We sincerely hope you enjoy reading this book and that it helps you better understand the topic
of study. Thank you for reading.
www.dbooks.org
About the Author
Zoran Maksimovic is a solution architect and software developer with more than 20 years of
professional experience. He is passionate about programming and technology.
Zoran is also the author of ServiceStack Succinctly, MongoDB 3 Succinctly, and Akka.NET
Succinctly.
Zoran enjoys guitar, baroque music, good food, and Italian wine. He is a proud father of three:
Alexei, Xenia, and Sofia.
10
Introduction
I wrote this book primarily to make you aware of infrastructure automation made in Ansible, and
to help you start working with this technology in the fastest possible way. My hope is that after
reading it, you will have enough knowledge to start coding and using Ansible effectively. While
this book is not fully comprehensive of the technology, it should give a good grounding in
performing the most useful operations.
Target audience
This book is intended for DevOps practitioners, or in general, software developers or readers
with a notion of scripting or programming. It would be useful for any kind of application
developer involved with the following areas:
You should already be familiar with Linux OS commands and Bash shell, and have some idea
of any programming language, as those concepts are mostly left unexplained.
Git, Apache HTTP Server (httpd), Firewall, Linux in general, MongoDB, and others won’t be
covered in this book, so the expectation is that these topics are to be understood separately.
Where appropriate, I’ve provided links (usually in the footer) for more information.
I’ve taken some shortcuts in this book—the intention is not to go too deep into the details, but
rather to show the various concepts, options, and possibilities.
Additional resources
You can find a lot of additional information about Ansible directly on the Ansible website.
If you want to know more about some of the technologies mentioned in this book, take a look at
the following resources:
• Linux Succinctly
• Bash (Unix shell)
• MongoDB Succinctly
• Firewalld
11
www.dbooks.org
• Apache Server
• PHP
Software requirements
To get the most out of this book and the included examples, you will need to have a version of
the Microsoft Visual Studio Code IDE installed (or any other file editor you prefer).
All of the examples in this book have been written and tested on Microsoft Windows 10, Linux
CentOS 8, and Microsoft Visual Studio Code.
Note: This icon will identify things to note throughout the book.
Tip: This icon will identify tips and tricks throughout the book.
$ command
12
Most of the coding examples are written in YAML, and the following formatting style is used when
working with it.
---
- name: Web Server Playbook
hosts: webservers
become: yes
tasks:
- name: Pinging web server
ansible.builtin.ping:
data: pong
Most of the results of executing commands are shown as a windows terminal image.
Resources
You can check out the code mentioned in this book here.
Ansible version
All the examples and explanations apply to Ansible v2.10, which is the latest stable version at
the time of writing.
13
www.dbooks.org
Chapter 1 Introduction
Ansible is an open-source software, automation engine, and automation language mainly used
in software configuration management, infrastructure provisioning, configuration management,
application deployment, and orchestration. The Ansible automation engine executes Ansible
playbooks.
• Simple: Playbooks are readable and easy to understand. Playbooks will contain some tasks
that will be executed in the order in which they are written. No special coding skills are
required.
• Powerful: Ansible can manage infrastructure, networks, operating systems, and other
resources, straight out of the box. It enables us to orchestrate the entire infrastructure and
environment lifecycle (cloud and on premises).
• Agentless: Uses Open SSH and Windows Remote Management. No additional firewall
ports need to be open.
System administrators at one point in time managed servers by hand (and this is sometimes still
the case). This obviously included installing the operating system and keeping it up to date,
installing the software needed for the application to run, changing configuration for application
deployment, and a myriad of other tasks.
Given the fact that we live in an information age, and that the usage of the typical applications,
now internet-facing, has grown to an unthinkable size, manual system management simply
doesn’t work anymore.
Application development, now being very agile, has become quicker, as the time to market is
one of the key factors (as it has always been!). Software releases have become more frequent,
and scalability and elasticity of the applications are requiring an effort that can no longer be
managed manually.
This is why configuration management tools such as Ansible, Puppet, Chef, and SaltStack
came to thrive as solutions to the problems I just mentioned.
14
What can Ansible be used for?
Ansible has a wide range of usages, covered in the following sections.
Infrastructure provisioning
Infrastructure provisioning is the process of setting up the IT infrastructures, which refers to the
components, hardware, and software needed to operate an application service or system. Bear
in mind that provisioning is not the same as configuration, but both are steps in the deployment
process.
Infrastructure as code (IaC) is a term that describes the ability to script and code the
infrastructure, as we would do for other kinds of software. This obviously has the great benefit of
being traceable, versioned, and rolled back if necessary.
Infrastructure has never stopped evolving, and Ansible offers great support for automation of the
following aspects:
The chance you are working in the cloud and using virtualization or container-based
deployments is very high! Ansible, in that sense, has great support for all of the major cloud
providers and supports industry-leading virtualization platforms such as VMware, Vagrant, and
Red Hat Virtualization.
Support that Ansible gives is available for all of the major operating systems: Microsoft
Windows, Linux (Ubuntu, CentOS, RHEL, Fedora, and others), Unix, and OS X.
An example of infrastructure provisioning might include all of the operations needed to create a
new machine (server) and bring it to a working state, including defining the desired state of the
system.
Configuration management
Configuration management is the process of maintaining infrastructure and software in a
desired and consistent state. It’s a way to make sure that a system performs as expected as
changes are made over time.
This is particularly important when it comes to applying changes to the resources. The goal,
especially with automation, is to keep the configuration changes transparent and documented.
Without it, we couldn’t simply know what changes have been applied to which resource, which
again, makes it hard to maintain.
One example is that we would like the production system to have exactly the same settings as a
development system (or vice-versa). In that way, we can ensure the consistency of the state of
the environment.
15
www.dbooks.org
With configuration management, you can accurately replicate an environment with the correct
configurations and software, as it is documented!
The great benefits of automating and having the configuration management processes in place
is that it makes the deployments faster, removes the possibility of the human errors, and
manages the system in a predictable and stable state.
Application deployment
With Ansible, teams are able to manage the entire application lifecycle effectively from
development to production. Ansible offers a simple way to deploy your multi-tier application in a
reliable and consistent way.
Although Ansible does not directly perform source and version control, it has great support for
application source control systems like Git and Subversion.
You can configure needed services as well as push application artifacts from one common
place. Ansible doesn’t require agents on remote systems, and it offers the possibility to execute
a playbook that contains a list of tasks that will be executed in order. That order will always be
consistent.
Orchestration
Ansible provides orchestration in the sense of aligning the business request with the
applications, data, and infrastructure.
It obviously helps define the policies and service levels through automated workflows,
provisioning, and change management. This creates an application-aligned infrastructure that
can be scaled up or down based on the needs of each application. This is especially useful
when working in an enterprise environment.
16
Chapter 2 High-Level View
Ansible works by connecting via ssh to the hosts (without the need for a special agent to be
installed on the host itself), and by pushing modules to the hosts itself. The modules are then
executed locally on the host, and the output is pushed back to the Ansible server.
Since it uses ssh, it can very easily connect to clients using SSH-Keys authentication, which
simplifies the whole process.
INVENTORY API
USERS
HOSTS
MODULES PLUGINS
Users
Users are simply the developers, operators, and DevOps practitioners who are writing or
executing the automation scripts. This automation and orchestration script is called the Ansible
playbook. A user can execute those scripts by using the Ansible orchestration engine.
Modules
Modules are executed directly on remote hosts through playbooks or by running them
individually from the command line. Modules, therefore, are the units of code Ansible executes.
Each module has a particular use, from administering users on a specific type of database, to
managing VLAN interfaces on a specific type of network device.
17
www.dbooks.org
Ansible allows users to write their own modules and provides out-of-the-box core (maintained by
the Ansible team) or extras modules (maintained by community).
To get an idea of the scope of the available Ansible modules, take a look at the list of all
modules.
Plugins
Plugins should not be confused with Ansible modules. While modules are executed on the
managed hosts, plugins are extensions to the Ansible runtime. Operations such as data
transformation, logging of the output, and inventory handling are plugins.
Inventories
Ansible works against multiple managed nodes or hosts that are part of the infrastructure, and
the list of those items is also known as the inventory.
Inventory is a file, defined in a YAML or INI format, that contains a list of hosts (nodes) along
with their IP addresses, servers, and databases, which need to be managed. Ansible then takes
action via a transport to connect to them: ssh for UNIX, Linux, or networking devices; and WinRM
for Windows system.
Ansible playbooks
Playbooks are files (scripts) that combine configuration, deployment, and orchestration
functions. Playbooks are executed to provide a way of automating the remote systems in a
consistent and repeatable manner.
Playbooks will execute predefined tasks, such as installing a new package on a remote system,
and tasks on their own will use modules to provide such a functionality. In that sense, playbooks
can be seen as the ultimate place where all the automation code converges.
Playbooks are human-readable and use the YAML format, which is easy to write and understand.
18
Chapter 3 Environment Setup
Before starting to work with Ansible, we need to set up the environment against which we will be
performing the examples and exercises.
For simplicity, this book assumes you are using Microsoft Windows 10 on a desktop computer.
Therefore, we will be using Microsoft Windows 10 to perform the basic setup. Windows 10 will
be purely used to host the VM. The rest of the examples will be run on CentOS 8 Linux.
Figure 3 depicts the set of virtual machines we are going to create, which will be installed locally
(on your desktop computer). The virtual machines are all based on CentOS 8 and will be
running on VirtualBox. To have them up and running, you will need to have about 6GB of RAM
available. However, it’s not necessary that all the servers run at the same time, if that’s the
limitation you have in your environment.
The environment presented is a possible setup for a web application that is load balanced, that
can be possibly scaled out (by attaching more web servers), and that has a backend system
serving the data (database).
Developer s
Desktop Web Server 1
Ansible Manager
Server
Web Server n...
19
www.dbooks.org
Server Purpose Hostname Description (Static) IP Address
either web server 1 or web
server 2.
Web Server web160 Server hosting the web 192.168.3.160
application.
web161 192.168.3.161
DB Server db Server hosting the database. 192.168.3.199
In order to have the recommended setup, you need to install the following tools on your desktop
machine:
• Vagrant
• VirtualBox
• Git
• Visual Studio Code
Let’s just briefly explain the tools, their purpose, and the installation procedure.
Vagrant
“Vagrant is a tool for building and managing virtual machine environments in a single workflow.
With an easy-to-use workflow and focus on automation…” (Vagrant website).
You can install the Vagrant software by downloading it here. Just follow the installation wizard,
which is quite straightforward. If there is already a version of Vagrant installed, it will be
upgraded. You might need to restart your computer.
After the installation, please verify that the software has been installed correctly by opening the
command prompt (PowerShell) and typing the following command.
PS C:\>vagrant -v
You should receive no errors, and the result should be something like Vagrant 2.2.15, which
is the version used at the time of writing this book.
20
We need also to install a Vagrant plugin called vagrant-hostmanager, which will help us in
setting up the connectivity between hosts by manipulating the /etc/hosts file at the time of
provisioning of the machines.
The version used at the time of writing is 1.8.9. To check that the plugin got installed (and the
version), we can run the following command.
VirtualBox
According to its website, VirtualBox is a powerful virtualization product for enterprise as well as
home use. We will be using VirtualBox in order to run the aforementioned virtual machines.
On Windows, you can install VirtualBox by visiting this site. After downloading the application,
install it by following the wizard. You can simply keep the default options.
Tip: Keep in mind that only one hypervisor can be active at the same time, so if
your Windows 10 has the Hyper-V (or other) installed and active, you might need to
deactivate it in order to run VirtualBox.
After installing VirtualBox, please also install the VirtualBox Extension Pack. The link to the
extension pack is typically in the link provided.
At the time of writing, the version of the Virtual Box application is 6.1.18.
After installing the tool, please install some plugins that will help you with the scripting, such as
the ones listed in the following table.
21
www.dbooks.org
Table 2: Visual Studio Code Plugins
Ansible
haaaad.ansible
Ansible language support.
By using the following commands and running them from the PowerShell command line, you
can automate the installation of the plugins mentioned in the previous table.
22
For example, I have created the following two folders:
You can execute in the command line (PowerShell) the following script.
PS C:\>mkdir AnsibleSuccinctly\Vagrant
PS C:\>New-Item C:\AnsibleSuccinctly\Vagrant\Vagrantfile
PS C:\>code C:\AnsibleSuccinctly\
You should now have the basic folders and a file called Vagrantfile created and opened in
Visual Studio Code.
After running the commands, you should also see Visual Studio Code opening the folder
AnsibleSuccinctly. You should get something similar to the following figure.
23
www.dbooks.org
Figure 5: Visual Studio Code view of the folder
Open the file called Vagrantfile, paste the following content, and save the file, which contains
the script that will be executed to create the VMs.
BOX_IMAGE = "Dougs71/CentOS-8.1.1911"
BOX_VERSION = "1.0.0"
Vagrant.configure("2") do |config|
config.hostmanager.enabled = true
config.hostmanager.manage_host = false
24
end
#Database definition
config.vm.define "db" do |db|
db.vm.box = BOX_IMAGE
db.vm.box_version = BOX_VERSION
db.vm.hostname = 'db'
db.vm.network :private_network, ip: "192.168.3.199"
db.vm.provider :virtualbox do |v|
v.memory = 1024
v.name = "db"
end
end
25
www.dbooks.org
Finally, let’s create the VirtualBox images by running the following command in the Vagrant
folder.
Note: Vagrant needs to download the CentOS 8 image from the Vagrant online
repository. The speed will depend on speed of your internet connection. As an
indication, you might experience 3–5 minutes of waiting time for the creation of all
five machines.
PS C:\>AnsibleSuccinctly\Vagrant>vagrant up
You might open VirtualBox at the same time and see in real time how the VMs are getting
created. The result should look similar to what is shown in the following figure.
When you run the status command, it should give you the view on the status of the VMs.
PS C:\>AnsibleSuccinctly\Vagrant>vagrant status
Please note that, in the result, we can also see that we can ping the newly created server, as
shown in the following figure.
26
Figure 7: Status of VMs
Tip: At every restart of your PC/workstation, you might need to run the vagrant up
command to start the VMs. Unless you destroy and recreate a VM, the state of the VM
will remain intact.
Examples:
27
www.dbooks.org
• vagrant ssh web160
• vagrant ssh db
username = vagrant
password = vagrant
Examples:
• ssh [email protected]
• ssh [email protected]
vagrant destroy It can only be executed in the folder where the Vagrantfile is located. It
destroys the VMs created, by literally deleting all of the files and
configuration.
vagrant reload Reloads the virtual machine by applying new settings from the
Vagrantfile. Useful when you need to change network or synced folder
settings.
28
Tip: Anytime you think you’ve done something wrong within the VM, you can
always destroy it by running vagrant destroy <machine_name> and vagrant up
<machine_name>. It’s a very convenient way to experiment with machines and reset
the environment at any time—and all of this within minutes.
VS Code gives us a very easy way of doing so. After starting up the VM with the vagrant up
command, we can connect remotely to the amgr node by executing the following steps:
1. Open Visual Studio Code and click the Remote Explorer icon on the left-hand side of the
editor.
2. Choose SSH Targets from the drop-down menu and click the + icon, as shown in Figure
9.
When you are prompted with Select SSH configuration file to update, choose the folder
location as in the Code Listing.
C:\ProgramData\ssh\ssh_config
29
www.dbooks.org
Figure 9: Add new remote connection
At the end of the process, you should see something like the following.
If not already present, make sure there is a path selected for the config file in the remote
connection settings. To do so in VS Code, press Ctrl+Shift+P and type Remote-SSH: Settings.
When the settings are open, specify the full path to the config file in the RemoveSSH: Config
File entry, as shown in the following figure.
30
Figure 11: Remote.SSH: config file setting
On the Remote Explorer, right-click the IP address and choose Connect to Host in Current
Window. The process of connecting to the remote server starts.
You will be prompted for the password twice, so please enter vagrant as the password.
At the end of the process, you should have the result shown in Figure 12. You can open any
folder on the server by choosing File >Open Folder.
Now you can create your own folders, edit files, and execute commands directly from Visual
Studio Code.
Note: All the figures in this book are showing examples using the command line.
Use what you will find more convenient vagrant ssh, ssh [email protected], or
VS Code remote connections.
31
www.dbooks.org
Chapter 4 Installing Ansible
We will be installing the Ansible software on our Ansible Manager server amgr, which we started
up previously. From there, we will be orchestrating the execution of the code. There are two
main ways to install and use Ansible on the host:
• Using the operating system package manager (apt-get, yum), depending on the
operating system in use.
• Install Ansible by using pip, which is the Python package manager.
Ansible creates new releases a couple of times per year. Due to this short release cycle, minor
bugs will generally be fixed in the next release. Major bugs will still have maintenance releases
when needed, though these are not so frequent.
Note: Although you can try both installation options, in this book we will be using
the installation through pip.
Let’s log into the amgr server by running the following command.
PS C:\>ssh [email protected]
Once logged into the amgr host, we’ll use the Ansible installation command.
32
To check that Ansible has been properly installed, let’s run the following command.
The result returned should be similar to what is shown in Figure 13. You can see that the result
returned shows a lot of information about the installed software, such as the version (in our case
Ansible 2.9.18) and the configuration settings, such as configuration file location and Ansible
location.
If pip is not already installed (as in our case currently), we should install it first.
Note: If you have followed the previous installation by using the package
manager, you can simply destroy the amgr machine and create a new one to have a
clean environment [vagrant destroy amgr, vagrant up amgr].
Note: Throughout this book, we will be using Ansible installed by using pip and
within a virtual environment.
33
www.dbooks.org
Code Listing 18: Installation of Python 3.6
The output of the command should look like the output shown in Figure 14. We can also see
that together with Python 3.6, this command also installs the pip and setuptools packages.
Once it’s installed, we will be working in a virtual environment, which we need to create first.
This is very useful, as we can play around with packages installed by Python.
We need to create a folder called ansible, where we will keep the code, and afterward we will
create and activate the virtual environment called avenv (which stands for ansible virtual
environment, a random name chosen for convenience).
34
(avenv) [vagrant@amgr ansible]$
Activating the virtual environment will change your shell’s prompt to show what virtual
environment you’re using, as you can see the (avenv) in front of the command prompt.
Note: Make sure to activate the environment every time before using Ansible by
running source avenv/bin/activate in the ansible folder. To deactivate the virtual
environment, just use the deactivate command.
Before installing Ansible, it is good to take the latest version of pip and setuptools by running
the following command. We will also install a package called wheel, which will help us install
Ansible.
Installing Ansible
After pip and its dependencies have been properly installed and updated, we can install
Ansible. Ansible is installed by the following command (inside the virtual environment).
Note: Installing Ansible also installs the dependencies such as jinja2, PyYAML,
cryptography, and other packages.
35
www.dbooks.org
Figure 15: Installation procedure of Ansible using pip
When you wish to update Ansible, we can first check whether there is a newer version by using
the following command.
36
Chapter 5 Ansible Configuration
Certain settings in Ansible are adjustable via a configuration file (ansible.cfg). This file contains
all of the Ansible configuration.
Installing Ansible via the pip tool doesn’t create such a file. To verify this, we can run the
version command as follows.
You will get a similar output to the one shown in Figure 16, and you might notice that the config
file setting is set to None. This means that when we install Ansible via the pip command, the
ansible.cfg file is not set up by default. In the next chapter, I will provide more information about
how to deal with the ansible.cfg file.
Ansible will look at the configuration file in a variety of locations, with the following priority, and
use the first file found (all others are ignored).
Table 5
export ANSIBLE_CONFIG=/path_to_file/ansible.cfg
~/.ansible.cfg ~ is a shortcut for the user’s home directory. In our case, this
would be the vagrant user under its home directory.
37
www.dbooks.org
/home/vagrant.
Note: Throughout this book, the ansible.cfg file will be always placed in the
“current folder” ./ansible.cfg. In this way, we can have different folders with different
ansible.cfg files.
To find out all of the currently configured options, the ansible-config utility can help us.
The output of this command is going to be a long list of configuration items with explanations.
In case the ansible.cfg file is present, we can see its content by running the following command.
An error will be returned if the file is not present at any of the aforementioned locations.
An example of a complete ansible.cfg file can be found on the Ansible GitHub account.
This file can be copied in the current folder, and its default parameters could be changed.
Alternatively, we can have an empty ansible.cfg file and simply set the values we want to
override.
Another very useful command option is --only-changed, which will show only the values that
we have potentially overwritten, and which are different from the default ones.
There are quite few sections in the ansible.cfg file with subconfiguration keys.
38
[paramiko_connection] Paramiko is the default SSH connection
implementation on Enterprise Linux 6 or
earlier and is not used by default on other
platforms.
39
www.dbooks.org
Chapter 6 Ansible Inventory
The inventory or host configuration file is a file that defines the hosts or groups of hosts upon
which commands, modules, and tasks in an Ansible Playbook will operate. In other words, it
defines a list of systems that we wish to manage with Ansible.
Typically, this file is located in the /etc/ansible directory if Ansible is installed with the default
Linux package manager. This file is not provided by the pip installation, so it has to be created
manually.
• The inventory file defines a collection of hosts that are target systems of the automation.
• The inventory file contains a list of hosts that can be grouped together into groups. One
host can be part of multiple groups. For example, we could group hosts into webservers,
databases, load balancers, and so on.
• Groups can be grouped together and managed collectively.
• The inventory file contains variables that could apply to either hosts or groups.
• The inventory file is a file that can be written in INI-style or YAML-style formats.
• It is possible to create an inventory file in a dynamic way, but this is outside of the scope
of this book.
Inventory location
As mentioned previously, the file has a default location; however, we can create our own local
version, just to be used by our project. Wherever this location is, it is controlled by the
ansible.cfg file, which specifies the location of the inventory file that can be either local
(relative), as in the following example, or absolute.
Code Listing 28: Inventory file location in ansible.cfg
[defaults]
Inventory = ./inventory
mail.example.com
192.168.3.100
web.mydomain.local
40
Host groups
We can organize the list of hosts in an intelligent way, so that whenever we want to apply some
changes, those changes get applied in one go to several hosts belonging to the group.
The following example shows how we can define three arbitrary groups: webservers,
databases, and production. Under each of them we can see that one server
(web1.domain.com) can make part of two different groups.
[webservers]
web1.domain.com
web2.domain.com
192.168.3.1
[databases]
db.domain.com
[production]
web1.domain.com
This implies that every host will belong to at least one of the two groups.
Nested groups
It’s also possible to “group the groups.” This is achieved by appending :children to the group
definition. We can define the list of hosts in Europe by putting together the list of Italian and
Swiss hosts.
[italy]
web1.domain.it
web2.domain.it
[switzerland]
web1.domain.ch
[europe:children]
italy
switzerland
41
www.dbooks.org
Host ranges
It is also possible to define host ranges when defining hosts, in case we have a repetitive and
large list of servers that otherwise would be cumbersome to handle manually.
1. web[1:2].domain.com
2. [a:c].domain.com
3. 192.168.3.[1:200]
4. 192.168.[2:3].[1:200]
• The first case defines two web servers starting with web1.domain.com and
web2.domain.com.
• The second case defines the list, such as: a.domain.com, b.domain.com,
c.domain.com.
• The third case defines the servers in a range from 192.168.3.1, 192.168.3.2 until
192.168.3.200.
• The fourth example defines the range of:
• 192.168.2.1, 192.168.2.2, … until 192.168.2.200
• 192.168.3.1, 192.168.3.2, … until 192.168.3.200
There is also a possibility to create an alias (such as WS1), in case we have only the IP
addresses. This is quite useful when displaying the information about the machine while
executing commands, as the IP addresses might not give us enough information, especially if
we have a lot of machines to manage.
Why it is it important to group the hosts together? This is mainly because it’s more convenient to
launch a command against a group (or all) of servers rather than doing it one by one, which
would defeat the reason for having the inventory file altogether.
Host verification
Ansible offers the ansible-inventory command line tool, which is used to display or dump the
configured inventory files as Ansible sees it. By default, it produces an output in JSON format,
but it can also produce a YAML file, which is useful if we like to convert the format from INI-style
to YAML.
Let’s quickly check the command by creating a folder on the amgr node and naming it
chapter_6. Let’s also create two files: the inventory file with the content (Code Listing 34), and
the ansible.cfg (Code Listing 35).
42
Code Listing 34: Inventory file
[webservers]
192.168.3.160
192.168.3.161
[load_balancers]
192.168.3.200
[databases]
192.168.3.199
Code Listing 35: ansible.cfg with inventory file specified
[defaults]
Inventory = ./inventory
We can get a list of hosts converted into YAML format by supplying the -y argument and –list.
43
www.dbooks.org
Dynamic inventories
It’s outside the scope of this book to discuss dynamic inventories, but it’s worth mentioning that
there is such a possibility.
Dynamic inventories are particularly important in cases where the infrastructure is not
predefined, or it might change overtime.
Ansible supports this scenario either through inventory plugins, which would then integrate with
the data providers (cloud, LDAP), or by predefined, custom scripts that are custom built.
44
Chapter 7 Connecting to Remote
Environments
In order to execute any code, Ansible has to first connect to the host that is targeted for
changes. That host could be of various types, such as Linux, Windows, or Kubernetes.
The beauty of Ansible is the pluggable architecture and its support for various environments. As
shown in the following picture, there are various methods that Ansible supports given the
targeted host type.
When it comes to connecting to the Linux hosts, the typical way is to use the OpenSSH (ssh).
For connecting to Microsoft Windows hosts, the default is the winrm technology supported by
Microsoft Windows, and so on.
Ansible supports other plugins, as well; you can find a full list here.
You can find the list of available and installed plugins by running the following command.
The result shows the list of plugins currently available, as seen in Figure 21.
We won’t go into the details of those connection types other than ssh, as this is the default
mechanism used in Linux, and since all of our machines are Linux-based, this would be the
context of this book.
45
www.dbooks.org
Figure 21: Ansible connection plugins result
To run the commands, Ansible should be using the unprivileged account that can use sudo to
become root without supplying a password. Requiring a password during the command
running can be cumbersome, as the operator needs manual interaction.
[privilege_escalation]
#become=True
#become_method=sudo
#become_user=root
#become_ask_pass=False
However, these settings can be also placed in the playbook to override the default settings, as
we are going to see later.
Note: By default, Ansible will connect to the other host by using the user
executing commands.
46
Generation of the ssh key
Before using Ansible without providing the username and password, we have to create the ssh
key (as a file) and push this file to all the hosts we’d like to manage.
First, log in to the amgr server, and at the shell prompt, type the following command.
You will be prompted for the location of the key file, and you can just keep the default values as
supplied in the command prompt. Please do override the id_rsa file.
Once the ssh key is generated, we need to copy it to all the hosts. We can accomplish this by
running the following command. You will be asked to provide a password for each server.
Hopefully this is just a one-time operation.
You can see that now we can log into the servers without supplying any username and
password, as shown in the following figure.
47
www.dbooks.org
Figure 23: No password required
48
Chapter 8 Running Ad-Hoc Commands
One of the easiest ways to interact with the hosts defined in the inventory is to run ad-hoc
commands. An ad-hoc command is a one-time, quick, and easy operation—but in general, not
reusable.
There are several tasks that are rarely repeated, so in that case, it’s not that efficient to create
playbooks. These can be whatever comes to mind, from rebooting servers or simply checking
that the servers are up by using the ping command.
To run Ansible ad-hoc commands, we will directly use the ansible command line tool. The
command looks like the following.
• Pattern defines the specific host or group of hosts as defined in the inventory against
which we’d like to run the command.
• Module defines the command to be executed. This can be a simple ping command or
something way more complex. Executing modules is idempotent (they only make
changes if the change is needed). We are going to see a few examples of commands to
understand this better.
You can retrieve the full list of available commands by running the following command.
$ ansible-doc -l
There is a large amount of information retrieved; therefore, you can simply filter out the
commands and retrieve what you need by using the grep command.
Documentation for a particular command is also available, and we can look for it by using the
following command.
$ ansible-doc ping
49
www.dbooks.org
Figure 24: Ping documentation
Some modules need some arguments to be passed to them, and for this we can supply them by
specifying the -a option. For instance, Ansible has a module called command, which executes
whatever command we want directly on the remote host.
This will log in to the remote host, execute the ping www.microsoft.com -c 2 command from
the remote host, and return the result.
[webservers]
192.168.3.160
192.168.3.161
[defaults]
Inventory = ./inventory
Now, in the directory where we placed the two files, we can run the following command to ping
the web servers.
50
We can see that two results are returned, one per web server (as we have specified two web
servers as the target of the module).
Let’s see how this is different from running the ping module.
Here we can also see two results, but this time the amgr is pinging the webservers (web160,
web161) rather than the webservers themselves pinging www.microsoft.com.
51
www.dbooks.org
Figure 27: Service module documentation
The documentation says that it controls services on remote hosts. This means that we are able
to control the status of services on a given host, such as by stopping or (re)starting the service.
In the next example, we are issuing a service command to restart sshd service running on the
web servers.
If there is need for elevated rights, the -b option can be specified directly in the command line.
This option is the same as when specifying become=True in the ansible.cfg file (which we have
encountered previously).
The following is the content of the ansible.cfg if we’re not supplying the -b option.
[defaults]
Inventory = ./inventory
deprecation_warnings = False
[privilege_escalation]
become=True
Common modules
In the previous examples, we have seen how to use ad-hoc commands. Ansible offers many
modules that could be used. The following sections cover some of the most common ones.
52
Ansible built-in modules
The following list is just an extract of the most-used modules. These modules are idempotent,
which means that the operation, action, or request can be applied multiple times without
changing the result (state of the system).
Table 7
apt Manages apt-packages; useful for managing Linux distributions using apt.
yum Manages yum packages; useful for managing Linux distributions using yum.
dnf Manages dnf packages; useful for managing Linux distributions using dnf.
Command modules
In addition to using the built-in Ansible modules, you can run commands directly on the
managed hosts. Those are very handy if there are no specific modules built for Ansible, but in
general, the advice is to use the Ansible ones if possible.
These modules are not idempotent! In other words, issuing multiple identical commands may
not have the same effect as issuing a command once.
53
www.dbooks.org
Table 8
raw Runs a command directly using the remote shell and bypasses the module
subsystem, which is useful when the remote system cannot have Python
installed.
Idempotent modules
We have already mentioned that Ansible runs the ad-hoc commands in an idempotent way. As
is stated in the Ansible documentation:
“An operation is idempotent if the result of performing it once is exactly the same as the result of
performing it repeatedly without any intervening actions.”
Let’s demonstrate this concept by using the group module, which creates a user group on the
managed host, (in our case, web servers).
We have learned that we can investigate the documentation and check the options (arguments)
the group command supports by running the ansible-doc group command-line command.
• name: A mandatory argument that specifies the name of the group to be added or
removed.
• state: An optional argument that can be either absent or present. If absent is
specified, we will instruct Ansible that the final state of the group on the managed host
should be, indeed, absent (removed). Otherwise, it will be present, which is the default
value.
Code Listing 48: Create app_users group on webservers
The result of running the command is shown in the following figure. We can see that the result
of the command was CHANGED. This means that a change has been applied to the managed
host. The changed property is also set to true, which tells us that the change has been applied,
so the state of the system has changed.
54
Figure 28: Result of create app_users group on webservers
However, if we rerun the same command, the message we see is another one. You can see
that the command was successful (SUCCESS), but the changed property is false. This means
that the command executed didn’t apply any change on the managed host.
Because this command is idempotent, it didn’t change the state of the managed machine by
running the same command twice.
55
www.dbooks.org
Chapter 9 Ansible Playbook
An Ansible playbook is a file written in the Ansible automation language, and it’s based on the
YAML format. The playbook is Ansible’s means to perform configuration, deployment, and
orchestration.
One can think of a playbook as an entry point for all of the operations that we would like to
execute in a given order against one or a set of managed hosts. In that sense, playbooks are
meant to be kept in the source control (such as Git) and should be treated as any other
application code.
Basic structure
The first thing to know about an Ansible playbook is that it’s written in YAML. There are just a
few rules to pay attention to when writing the code:
---
- name: Web Server Playbook
hosts: webservers
become: yes
tasks:
- name: Pinging web server
ansible.builtin.ping:
data: pong
We have specified the name of the play, the hosts to which this code is going to be applied
(group in the inventory file), and the become:yes option (to enable privilege escalation). These
settings are global to the playbook.
We can see that there is a tasks section defined and aligned by two spaces. The tasks section
is a list of individual tasks identified by the name, the module, and other possible arguments. It’s
very similar to what we have seen previously with the ad-hoc commands.
56
Although we see only one task in the previous example, we can specify more of them, and as
we are going to see in the following chapters, combine them with variables, handlers, or roles to
obtain a very powerful orchestration.
Playbook
Play 1
Hosts
Variables
Task 1: Module
Task 2: Handler
Task 3: Notify
Task 4: Module
Play 2
$ ansible-playbook webserver.yml
57
www.dbooks.org
PLAY RECAP
******************************************************************************************
193.168.3.161 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0
ignored=0
193.168.3.160 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0
ignored=0
We can clearly see that the output contains the output grouped under the names of the sections
as defined in the webserver.yml:
• Gathering Facts: This is the phase of retrieving information about all of the hosts listed
in the inventory file, and that are in the context of the playbook. We can disable it by
specifying the option gather_facts: false in the default section of the playbook.
• TASK [task_name]: For every task defined in the playbook and executed, there will be
one section in the result. In our case, we have only one task. Pinging web server is
executing the module ping, and it is displaying that the operation has been completed
successfully, specifying the name of the host, as well.
• PLAY RECAP: After each run, there is a summary of the playbook execution stating if
there were failures during the execution, and it constitutes a very nice report of the
playbook execution itself. We can clearly see that there are a few pieces of information
available:
Limit option
There are a few options when using ansible-playbook commands, such as limiting the hosts
against which we would like to run the command. This is done by using the --limit argument.
This is useful if we would like to target just one server, for instance, without changing the
playbook itself.
58
The result will show that the command was executed only on one server, as specified in the
command.
Dry run
To run the “dry run,” which is like a test mode, there is the -C option. Dry run mode will show the
output of the specified change, but without changing the managed hosts. This is extremely
useful when testing, as we can see which changes would occur if we execute this command.
$ ansible-playbook -C webserver.yml
Variables
We have seen the most basic playbook content and how to execute it. Now we have to look into
how to parametrize and make the playbooks more useful by specifying variables.
Variables provide a very convenient way to handle dynamic values. Variables could be about
anything, such as a list of users, a list of software packages to install or uninstall, and services
to start or stop.
59
www.dbooks.org
It’s obvious that having everything statically defined in the playbook would work, but it would
also be a bit more cumbersome to handle, as this would typically result in a larger code base
with some repetition, which would potentially increase the possibility of errors in code.
Naming convention
The variables have to start with a letter, and they can include underscores and numbers.
account_nr_1 account-nr1
accountnr#1
account$1
1_account
Variables scope
The scope of the variable is the context within which it is defined. In other words, this defines in
which parts of the program variables will be seen, applied, or used.
Scope Description
Global This is set by configuration, environment variables, and the command line. It is
set to all hosts.
Host Directly associated to a specific host or host groups (as defined in the
inventory file). Those are variables defined in the inventory or in the host_vars
directory.
Play Scope applies to the play in which variables are declared. It applies to all hosts
in the context of the current play.
The vars directive in the playbook is where the variables are declared.
Additionally, they can be defined by the include_vars task.
If a variable is defined in several scope levels, the value of the level that has the precedence
would be taken as the variable value. The narrower in scope we go, the more precedence it
has.
60
Play scope overrides the host variables, which override the global variables, which have more
precedence over the variables defined in the inventory file. However, if the variable value is
defined in the command line directly while executing the command, it has the highest
precedence. By providing the -e option in the ansible-playbook command, we can override
any value.
---
- name: Example with vars
hosts: all
vars:
user_name: john
user_description: "standard user"
We can then reference those variables in the playbook by placing the variable name between
double curly braces: {{ name_of_variable }}.
---
- name: Example with vars
hosts: all
vars:
user_name: john
user_description: "standard user"
tasks:
- name: Show user name
debug:
msg: "{{ user_name }} - {{ user_description }}"
61
www.dbooks.org
In this snippet, you can see that we are using the debug module. This module is useful when we
want to display some information to the console, in this case the variables defined. The two
variables previously defined are stored in the msg argument of the module. An important thing to
notice is that we placed the variables between quotes.
Let’s execute this code and see what the output will be, as shown in the following figure. We
can clearly see the “msg”: “john – standard user” is displayed in the output.
This is the same output we would have when using the the var_files directive. In the
users.yml file, we would place the same content as in the vars section.
user_name: john
user_description: "standard user"
By looking at the folder structure, we can see there is a users.yml file in the vars folder.
It’s worth noting that we can also define a list or dictionary of parameters.
62
Code Listing 58: Playbook with a list
---
- name: Example with list
hosts: all
vars:
users:
- john
- mark
- bob
tasks:
- name: Show user name
debug:
msg: "{{ users }}"
---
- name: Example with list
hosts: all
vars:
users:
john:
name: john
default_password: john1234
mark:
name: mark
default_password: mark1234
63
www.dbooks.org
bob:
name: bob
default_password: bob1234
tasks:
- name: Show user name
debug:
msg: "{{ users['john']['name'] }} -
{{ users['john']['default_password']}}"
The naming convention for files is driven by using the same host names or group names as
defined in the inventory file.
[webservers]
web160
web161
[load_balancers]
lb
[databases]
db
The following is the folder structure with files containing group or host files with the same names
as defined in the inventory file.
64
Code Listing 61: Directory structure of group and host vars
\
|-- group_vars
| |-- all
| |-- webservers
| |-- load_balancers
| |-- databases
|-- host_vars
| |-- web160
| |-- web180
|-- playbook.yml
|-- ansible.cfg
|-- inventory
These variables will be loaded by default, depending on what is declared in the playbook hosts
section.
---
- name: Looping through a list of variables
hosts: all
vars:
packages:
- httpd
- python
- mysql
tasks:
- name: List packages
debug:
msg: "{{ item }}}"
loop: "{{ packages }}"
When we execute the playbook, in the output we can see three distinct msgs being returned to
us. In fact, this is executing the task as many times as there are items in the list.
65
www.dbooks.org
Figure 36: Result of looping through variables
Conditional statements
Ansible supports conditional statements. Similar to an if statement in programming languages
such as Python or C#, Ansible uses the keyword when to check whether a condition is being
satisfied or not.
---
- name: Conditional check for true
hosts: all
vars:
preinstall_package: false
tasks:
- name: List packages
debug:
msg: "executed"
when: preinstall_package
In this case, the debug task will not be executed, as the preinstall_package is set to false.
We can clearly see in the output that the task is skipped.
66
Checking for true or false values is just one of the possibilities. There are other predefined
keywords, such as is defined, where the variable is checked for its existence. The following
table defines other possibilities.
Operation Example
Equal “some string” package_name == "httpd"
Equal some number port_number == 80
Conditions can be multiple, and we can separate them by using the or and and keywords.
when:
- username = "john"
- groupname = "admin"
67
www.dbooks.org
Combining loops and conditional statements
With the knowledge of how to write loops and conditional statements, we can certainly combine
the two, making the playbook execution even more powerful.
---
- name: Conditional check for true
hosts: all
vars:
users:
- john
- mark
tasks:
- name: List packages
debug:
msg: "executed"
loop: "{{ users }}"
when: item == "john"
As you may already expect, this task will be executed only if the name of the user is john.
68
Chapter 10 Ansible Playbook Handlers
There are situations where we want a task to run only when a change is made on a managed
host. For example, we may want to restart a particular service if a task updates the configuration
of a service, or we might reboot the machine after some package installation.
Ansible uses handlers to address this use case. We may ask, why not simply create a task at
the end of the playbook that would reboot the server, or something similar? This task would
need to depend on the execution of other tasks and to check the status of each of them to
decide if something has to happen or not.
Ansible has solved this issue elegantly by introducing handlers. Handlers are tasks that only run
when notified by other tasks, and only when the change happens on the managed host. If a task
doesn’t notify the handler, it won’t run.
A nice thing about handlers is that they run only once, even if notified by multiple tasks. This fits
perfectly, for instance, with the reboot use-case where it doesn’t make sense to reboot the server
after each task requiring it, but only once when all the tasks have been executed.
Handlers have a unique name globally and typically get placed at the end of the playbook. All of
the modules used by tasks can also be used in the handler, so technically everything we can do
in a task, we can do also in a handler itself.
---
- name: Handler restarting httpd service after installation
hosts: webservers
become: yes
gather_facts: false
tasks:
- name: Install apache package
yum:
name: httpd
state: present
notify: Restart apache
handlers:
- name: Restart apache
service:
name: httpd
state: restarted
69
www.dbooks.org
In the previous code snippet, we can see a playbook containing one task and one handler.
The task’s responsibility is to install the httpd (Apache HTTP Server) by using the yum package
manager. The state present in the task means that we want the httpd service to be present on
the system as a result of running the task.
We can also see that there is a notify keyword after the task (aligned to the name and
module), and the handler’s name to which the link is made (notify: Restart apache).
On the other hand, at the bottom of the playbook, we define a handler in the same way we
would define a task: by specifying a name, module, and eventual arguments. The state
restarted simply means that we want the service to be restarted.
We can see that the service got installed on the managed host 192.168.3.160 (web160) and
that the httpd service got restarted.
• We can declare more than one handler (in the example, we only had one).
• The order in which the handlers are executed depends on the order in which they are called
(not declared) by the task.
• The handler will be executed only after all tasks are executed!
• If the task doesn’t change the system, the handler won’t run.
• In handlers, we can use variables in the same way we use them normally in tasks.
70
Chapter 11 Templating
In this chapter, we are going to discuss the Jinja2 templates. Jinja2 is a templating language for
Python. Ansible uses Jinja2 templating to enable the dynamic creation of the content. The
dynamic content is going to be driven by the variables used within a playbook. This is useful
when we need to apply changes to the static content and adapt it to the managed hosts.
For instance, a load balancer configuration file might need to be updated with the list of
available web servers. Rather than hard-coding this information in the load balancer’s config file,
and in fact duplicating this information that we need later on to maintain, we can simply utilize
the list of hosts we already have predefined in our inventory file, change the content of the
config file, and ship it to the load balancer.
When playbooks are executed, these variables get replaced by actual values defined in the
playbooks. This way, templating offers an efficient and flexible solution to create or alter content
with ease.
• {{ }}: Used for embedding variables and using their value during code execution. For
example, a simple syntax using the double curly braces is as shown: The {{ webserver
}} is running on {{ nginx-version }}.
• {% %}: Used for control statements such as loops and if-else statements.
• {# #}: These denote comments that describe a task.
We can perform conditional statements such as loops and if-else statements, and transform
the data using filters and more.
Jinja2 module
To invoke the transformation of the template, we have to integrate it within a playbook. Ansible
offers a module called template.
71
www.dbooks.org
Code Listing 69: Basic template module with arguments
tasks:
- name: Jinja2 template
template:
src: haproxy.cfg
dest: /etc/haproxy/haproxy.cfg
owner: root
group: root
mode: 0644
• src: The source file we would like to transform; in this case, haproxy.cfg.
• dest: Where we want to copy the content after the transformation.
• owner: Name of the user who should own the file/directory, as would be fed to chown.
• group: Name of the user who should own the file/directory, as would be fed to chown.
• mode: The permissions the resulting file or directory should have.
To debug the template, we could utilize the debug module with a special lookup function. This is
particularly useful as the content won’t be deployed to any host, but we could see the result of
the transformation.
The lookup plugin is an Ansible extension to the Jinja2 templating language. For more
information, you can run ansible-doc -t lookup -l to list all available lookup plugins. For
more information about the lookup template plugin, you can always consult the documentation
by running the ansible-doc -t lookup template command.
tasks:
- name: Show the template content
debug:
msg: "{{ lookup('template', './haproxy.cfg') }}"
The inventory file we are going to use is as follows, and we can see that we are defining a
webservers group with two servers.
[webservers]
web160
web161
[load_balancers]
lb
72
Let’s create a file named webservers.j2 and put it in the local folder, where we run the
playbook.yml. The content of the webservers.j2 is shown in Code Listing 72. This is actually
our template file. Typically the file can have the .j2 extension, but this is purely optional, as it
might be useful when using text editors with the Jinja2 syntax option.
group['webservers'] is something we haven’t yet seen. It’s a built-in collection that would
return the content of the hosts as defined in the inventory file.
---
- name: Using a jinja2 template
hosts: load_balancers
gather_facts: false
vars:
- message: "We love Ansible"
tasks:
- name: Show the template content
debug:
msg: "{{ lookup('template', './webservers.j2') }}"
73
www.dbooks.org
The result of executing the playbook follows.
To run the code against the load balancer, let’s change the playbook to look as follows.
---
- name: Using a jinja2 template
hosts: load_balancers
gather_facts: false
vars:
- message: "We love Ansible"
tasks:
- name: Jinja2 template
template:
src: webservers.j2
dest: /home/vagrant
We are using the template module rather than the one used for the debugging purpose.
With the newly defined task, we are going to transform the file locally, and then the file will be
copied to the load balancer at the destination folder /home/vagrant, as specified in the dest
argument.
The task went fine, but let’s check on the load balancer server and see the content of the file we
have transformed and copied across. By using the cat command, we can see the content of the
file.
74
Figure 42: Load balancer home directory
We love Ansible
web160
web161
75
www.dbooks.org
Chapter 12 Ansible Vault: Data Encryption
In this chapter, we will describe how Ansible can protect sensitive data.
As with any other code, we would use a versioning system such as Git or Subversion to keep
our code versioned. This automatically means that we would expose sensitive information to
people who are not intended to see it.
Rather than leaving this content (playbooks, variable files, etc.) visible in plaintext, Ansible offers
a tool called Ansible Vault that allows data encryption and decryption. Only after encrypting
the sensitive content should we feel safe about putting it into a source control.
To use Ansible Vault, we need to encrypt the data by providing a password, and the same for
decrypting the content. This password of passwords obviously should be kept in some (other)
safe place.
Encrypt
The encrypt command encrypts the existing file. We can also pass the --output argument to
specify the name of the newly encrypted file. If the --output is not provided, the file will simply
be overwritten by the encrypted content.
Ansible offers the ability to encrypt multiple files with different passwords; each file would have
its own. This can be done by supplying the --vault-id parameter to the encrypt command. In
our case, we have given an identifier called secret@prompt. Prompt, in this case, means that
the password will be supplied in the command line.
76
Decrypt
The decrypt command decrypts the existing file.
If the file has been given a –vault-id during the encryption, as we have seen in the previous
example, we could provide the --vault-id in the command line with the same name as we
provided during the encryption.
View
The view command enables us to see the content.
Edit
The edit command allows us to edit the file. When we use the edit command, an editor will
open the file ready to be edited in the command line.
Rekey
Changing a password for an already encrypted file is rather simple using the rekey command.
This command is useful, as otherwise we would need to do two operations—decrypt and
encrypt—to achieve the same thing. We can provide multiple files to the command.
77
www.dbooks.org
Using secrets within the playbook
When referencing an encrypted file within an Ansible playbook, we need to provide a password
for the content to be decrypted during the execution. The ansible-playbook command offers
an option to supply the password through the --vault-id option.
The @prompt option will prompt the user to provide the password in the command line.
…
tasks:
- name: print variable
debug:
msg: {{ secret_variable }}
no_log: true
Example code
Let’s create a file called secret_file.yml with the following content.
password: some_very_secret_password
You will be asked for a password, which will be used to encrypt the file. After supplying the
password (in my case, the password is 1234) and reopening the file, we will see that the file has
been encrypted, and that it is unreadable by a human.
78
Code Listing 87: Secret_file.yml after encryption
$ANSIBLE_VAULT;1.1;AES256
66626463316264313732366638323532653130363232383131643434643235393763343039383362
3363356465383565613938623666306161663463396361370a366362623965663437353934636165
32373964313433343130343634663061393166656333373466663631383631643063623336313830
3234386136663232650a343862336163616264666565326630353934343635643663376536373464
35343664626635626263383764663139653165363835323332336632316462656262663839633833
3762343138656233333531326139336633393431323438663233
Let’s now create a playbook where we will use this secret information.
---
- name: Handling secret information
hosts: lb
become: yes
gather_facts: false
tasks:
- name: Load encrypted variable
include_vars:
file: secret_file.yml
- name: Retrieve information from the secret_file but not show output
debug:
msg: "{{ password }}"
no_log: true
We are already familiar with the tasks section. We can see a new module called
include_vars, which is responsible for loading files with variables so that those are available to
other tasks. This is very handy, as we can reference our encrypted file (secret_file.yml).
Additionally, we would like to display the password value in the subsequent two tasks; one,
however, should suppress the output as we have previously seen, by using the no_log: true
argument.
We can now execute the playbook and see that providing the --vault-id parameter with
@prompt will actually prompt for a password. I will supply the password used for encrypting the
file.
79
www.dbooks.org
Code Listing 89: Run playbook with secret data
We can clearly see that in the first task, the output gets properly shown, while in the second,
that’s not the case.
80
Chapter 13 Ansible Runtime Facts
When executing playbooks, Ansible retrieves certain information and stores it for possible
reuse. Information returned in Ansible terms are called facts.
We can utilize and reuse this information to decide on taking certain actions or simply use this
information in the configuration when deploying some artifacts to other systems. A typical
example is the IP address. We can retrieve one IP address from one system and reuse this
information when configuring another system.
---
- name: Retrieving and displaying facts
hosts: webservers
become: yes
gather_facts: true
tasks:
- name: Retrieve server information
debug:
var: ansible_facts
In the example shown in Code Listing 90, we can retrieve all the information about the
webservers in scope. Pay attention to the gather_facts: true, as this has to be enabled in
order to retrieve the variables.
Before starting to execute the tasks, Ansible will have its own internal task that will indeed
gather the information about the machines that are in scope for the given run.
81
www.dbooks.org
Figure 44: Ansible facts output
We can certainly obtain the individual values from the returned long list of values.
Let’s say that we would like to retrieve the Linux distribution and the first IP address from the
server in question.
---
- name: Retrieving facts individually
hosts: webservers
become: yes
gather_facts: true
tasks:
- name: Retrieve server information
debug:
var: ansible_facts['distribution']
In the output, we can see that the distribution is CentOS and the private IP address (in this case)
is 10.0.2.15. To obtain the first value from an array of values, we used the .0 notation.
82
Figure 45: Result of retrieving individual facts
It’s now even more evident how we can use this information in the task, where we can decide
whether to install a package given the distribution name of the host, or some other variables that
might be useful to base the decision on.
83
www.dbooks.org
Chapter 14 Ansible Tags
Sometimes we have the requirement of only running one specific task within a plethora of tasks
configured in a playbook. In other words, instead of executing the playbook itself, we might
choose to run only a part. Ansible enables this scenario with the tags attribute. Tags are
annotations to the task that identify or group them with other tasks.
---
- name: Tags Playbook
hosts: localhost
gather_facts: true
connection: local
tasks:
- name: Display information
debug:
msg:
- "Distro of {{ ansible_facts['hostname'] }}:
{{ ansible_facts['distribution'] }}"
- "IP of {{ ansible_facts['hostname'] }}:
{{ ansible_default_ipv4.address }}"
tags: info
- name: Never
debug:
msg: "This command has to be explicitly called"
tags: [never, debug]
84
Through the tags keyword, we have marked all of the tasks, de facto attaching a label to them.
We can see that more than one tag can be assigned at the same time. As mentioned
previously, this is very useful if we want to group certain tasks together, and sometimes one
given task may belong to more than one group.
There are two special tags defined by Ansible: never and always. The never tag, if specified,
will prevent the execution of the tasks, unless this is not explicitly specified to run. On the other
hand, always is the default value of any tag.
When executing the playbook, we have a few possibilities on how to include or exclude certain
tags from being executed
Example Description
ansible-playbook p.yml -t all all is a special keyword that will run all
the tasks (except tasks marked as never).
ansible-playbook p.yml -t tagged tagged is a special keyword that will run all
of the tasks that have been explicitly
tagged (at least one tag).
ansible-playbook p.yml --skip-tags info Runs all the tags, but not the one(s)
specified.
ansible-playbook p.yml -–list-tags Lists all of the tags defined in the current
playbook.
ansible-playbook p.yml -t info -–list- Lists all the tasks that are tagged with the
tasks label info.
If we set the playbook.yml file to run only the tasks labeled info, only the tasks named
Display information and Post execution will run, as shown in the following figure.
85
www.dbooks.org
Figure 46: Running tasks tagged "info"
Instead, if we were to run everything but not tags with the info label, then only the Apply
changes task would be executed.
In both cases, we can see that the task named never was never executed. If we want to
execute this task, too, we need to explicitly specify it when running the playbook.
86
Chapter 15 Ansible Roles
In Chapter 9, we saw how to work with the Ansible Playbook and how to utilize tasks. We are
able to automate a large number of processes in this way.
Another cool feature that exists in other programming and scripting languages is code reuse,
and Ansible in that sense is not any different. When writing code to automate webservers or
databases, or managing another kind of host, we would also like to be able to share this work
with others.
Ansible has created the concept of a role to help solve these issues. Each role is basically
limited to a particular functionality or desired output, with all the necessary steps to provide that
result. You might think of it as a library or module in other programming languages.
• Allows code reuse and makes the Ansible projects more manageable.
• Allows the creation of generic code that can be shared between teams or projects.
• Contains a set of (pre)packaged tasks.
• Has to be used within playbook.
• Has a predefined directory structure.
• Is written in YAML, as is the case for playbook or tasks.
Note: The concept of the Ansible role is simple: it is a group of variables, tasks,
files, and handlers that are stored in a standardized file structure.
There are two ways of creating such a structure: manually, or by using the ansible-galaxy
command. As we like automation, we will utilize the command to generate a skeleton folder
structure for a role called webservice.
Code Listing 94: Initializing a role skeleton structure
When we run the command, a predefined directory structure will be created for us. Figure 49
shows the directory hierarchy.
87
www.dbooks.org
Figure 49: Role Skeleton creation
We can see that the command has created the skeleton with a few subfolders and files.
Directory Description
\ The root directory is named after the role name.
Contains default variables for the role. Variables in this directory have
defaults
the lowest priority, so they are easy to override.
files Contains (static) files that are to be copied to the remote host.
Contains the general information about the role itself, such as author,
meta
description, and license, as well the dependencies to other roles.
All of the subdirectories contain a main.yml file, which is the default file to be included in the
execution pipeline.
88
• Create a playbook first, and when it’s getting too complex, start translating and porting
this code to a role.
• Start working on and creating the role from the beginning. This comes with the
experience and the actual need of the application.
• Reuse an already available role, something we will explore in Chapter 16.
MongoDB is a NoSQL database, and it’s often used as the backend of web applications. The
procedure to follow to install the MongoDB on the CentOS server is described on the MongoDB
website.
What is important to understand in general is that Ansible just gives a means of automation, but
not the actual recipe of how exactly each application works. So, looking into the official
documentation is crucial to understanding what to automate.
By reading the official MongoDB documentation, we will see that we need to perform the
following operations:
1. Add the yum repository, as CentOS doesn’t have MongoDB available by default.
2. Install MongoDB.
3. Open Firewall ports to be able to access it from other servers (web server).
4. Start the service.
While there are many other operations that could be added, such as configuring the ulimit and
other settings for better performance, we will omit those for the sake of brevity.
Let’s start by creating the inventory file. In our case, the hostname is called db
(192.168.3.199).
[database]
db ansible_host=192.168.3.199
[webservers]
web160 ansible_host=192.168.3.160
web161 ansible_host=192.168.3.161
In the following ansible.cfg code, we can see that there is a roles_path property being set to
the local folder called roles. The role_path is defining where Ansible is going to look for the
roles by default.
89
www.dbooks.org
Code Listing 96: ansible.cfg with roles defined
[defaults]
inventory = ./inventory
roles_path = ./roles
With this information, we are now ready to create our role by using the command line tool
ansible-galaxy.
Role creation
The mongodb role is going to contain two types of automation: one for installing the mongodb
server, and another one to install the mongodb client.
Let’s create the role called mongodb and place it under the ./roles folder as defined in the
argument --init-path. This code should be executed in the root folder, where the ansible.cfg
or playbook.yml files are placed; otherwise, please do specify the full path to the roles
directory.
As shown in the following figure, we can see that the skeleton of the role has been successfully
created under the roles folder.
Variables
Under the vars folder, let’s open the main.yml file and set the following variables.
90
Code Listing 98: Content of vars/main.yml
---
# vars file for mongodb
mongo_db_version: "4.4"
mongo_db_journal_enabled: "false"
mongo_db_server_port: 27017
Here we are defining the version of the MongoDB we would like to install, and two more
configuration options to be set after the MongoDB installation.
Tasks
Now we can start filling out the tasks by opening the main.yml file under the tasks folder. This
task is just a bit longer, but we will go through each task and explain it.
---
- name: Add yum MongoDB repository
ansible.builtin.template:
src: mongodb-org.repo.j2
dest: /etc/yum.repos.d/mongodb-org-{{ mongo_db_version }}.repo
mode: 0644
tags: [never, mongodbclient, mongodbserver]
91
www.dbooks.org
name: mongod
state: started
tags: [never, mongodbserver]
The first thing to notice is the file indentation. There is no need to specify the tasks keyword
like we were doing in the playbook, as Ansible will automatically assume that the file under the
tasks\main.yml file contains tasks.
We can see six tasks defined. Let’s explain what they do in detail.
As the CentOS yum package manager doesn’t have the MongoDB repository predefined, we
have to add it to the list of available repositories. To achieve this, we are using a template file
located in the templates folder called mongodb-org.repo.j2. The transformed file is then going
to be sent to the default yum configuration location, which is /etc/yum.repos.d.
This file internally uses the variable called mongo_db_version. This variable is defined in the
vars/main.yml file. This file contains the variables defined for the given role.
After executing the playbook, we should see a transformed file to be delivered into the
/etc/yum.repos.d folder on the database server.
92
Install MongoDB server
This task is responsible for installing the MongoDB (mongodb-org) package. We do this only
after the repository location has been added to yum. With the state present, we are telling
Ansible to install the package.
This code corresponds as if we were executing the following command on the managed server:
This task is responsible for installing the MongoDB client package. We do this only after the
repository location has been added to yum. With state present, we are telling Ansible to actually
install the package.
This code corresponds as if we were executing the following command on the managed server:
This command will install all of the necessary components of MongoDB shell.
After installing MongoDB, and before starting it to run as a service, we are going to perform just
a few customizations in the MongoDB configuration file.
One thing to notice here is that the change in the configuration file triggers a handler called
restart_mongo_db, which is defined in the handlers/main.yml file as follows.
---
# handlers file for mongodb
- name: restart_mongo_db
ansible.builtin.systemd:
name: mongod
state: restarted
tags: [never, mongodbserver]
Open Firewall Port 27017
This task is supposed to run after MongoDB has been installed. The task is responsible for
opening the port 27017 on the database server to allow only connections from the web
server(s), hence the use of groups[‘webservers’]. This is important, as in general it’s a good
practice to secure the system to only those hosts that need to communicate to the database.
93
www.dbooks.org
The variable mongo_db_server_port is defined in the vars/main.yml file.
# mongod.conf
# network interfaces
net:
port: 27017
bindIp: 127.0.0.1, {{ ansible_all_ipv4_addresses.0 }} # Enter 0.0.0.0,::
to bind to all IPv4 and IPv6 addresses or, alternatively, use the
net.bindIpAll setting.
#security:
#operationProfiling:
#replication:
#sharding:
## Enterprise-Only Options
94
#auditLog:
#snmp:
Playbooks
We are going to have two playbooks to be placed in the root directory of our project:
database.yml and webservers.yml. The first will be responsible for the installation and setup
of the mongodb on the db server, while the other will be responsible for installing the MongoDB
client application on the web server, so that we query the database and test what have we done
so far.
---
- name: Installation of the MongoDB database
hosts: database
become: yes
gather_facts: yes
roles:
- mongodb
We can see that the playbook now is quite simple to read, as we have eliminated the tasks and
handlers.
The new keyword we use is roles. Under the roles, in general, we can specify more than one
role. Roles would be executed in exactly the same order we place them in a list. In our case, we
only have one, so we are specifying the mongodb role.
To run the code against the db server, however, we will be using the tag to specify that we only
want to install the server (without the client).
We can see that by running this code, we only install the mongodb server.
95
www.dbooks.org
Figure 51: Result of the execution of the database.yml playbook
To test that the database has been properly installed, we can directly log in on the db server
and run the mongo command. The mongo command is the MongoDB client tool that gets
installed with the server.
After launching the mongo command without any parameter, we will automatically log into the
localhost MongoDB instance. If this is successful, this would mean that MongoDB is up and
running.
We are showing an additional command, show dbs, which will list all of the currently available
databases.
96
The next playbook is about installing the webservers.
---
- name: Installation of the MongoDB client
hosts: webservers
become: yes
gather_facts: yes
roles:
- mongodb
And see that both configured web servers were updated, as shown in the following figure.
We can now finally test that we can run queries from the web server against the db-installed
MongoDB.
We need to log in on one of the webservers, let’s say web160, and run the following command.
$ mongo --host db
We can see that we are successfully logged in to the MongoDB server and getting the result by
executing a query.
97
www.dbooks.org
Figure 54: MongoDB client connecting to db
98
Chapter 16 Ansible Galaxy
In the previous chapter, we saw that we can create roles from scratch. We introduced the
concept of code sharing and code reuse. We also used the ansible-galaxy command line to
initiate the role skeleton.
Ansible has taken this concept further and created Ansible Galaxy, Ansible’s official hub for
sharing Ansible content. You can visit the web application here.
Ansible Galaxy is essentially a large public repository of Ansible roles. We can programmatically
interact with the repository by using the already mentioned command line tool ansible-galaxy.
We can use the ansible-galaxy tool to list, install, or remove existing roles prepackaged and
maintained by someone else.
By running the following command, we can see all the operations supported by the tool.
ansible-galaxy remove <role-name> Removes (deletes) the role from the local folder.
99
www.dbooks.org
Using roles
In this chapter, we are going to see how we can use the prebuilt roles and integrate them in our
solution, where we will:
• Install and configure the httpd server on our two web servers.
• Deploy a simple webpage to the webserver(s).
• Configure the load balancer to route the calls to the web server.
This is a typical setup for a web infrastructure where we expect a higher load and ability to scale
out by adding additional web servers over time. This is why we placed a load balancer that can
efficiently distribute incoming network traffic across a group of backend servers (in our case,
web servers).
Web Server 2
Ansible Manager 192.168.3.161
Server
User
[loadbalancer]
lb ansible_host=192.168.3.200
[webservers]
web160 ansible_host=192.168.3.160
web161 ansible_host=192.168.3.161
100
The ansible.cfg is very basic, and it contains only a reference to the inventory file, something we
have seen previously in roles_path, which is the directory from where the roles will be installed
or loaded.
[defaults]
inventory = ./inventory
roles_path = ./roles
So far, we have enough information to start working on the playbook for the web server. We can
now try to work with the role downloaded; in our case, this will be the package called
geerlingguy.apache. It’s a very good practice to navigate to the role project website where we
can see some examples of usage and customization. Links to the pages are typically shown in
the Ansible Galaxy website, if needed. The number of downloads and the score are a sign of
the quality of the package itself.
Alternatively, we could also consult the README.md file bundled with the installation, which
contains similar information.
This package is responsible for installing the Apache Server (httpd) on various Linux
distributions (RedHat, Debian, etc.), and it’s quite generic in what it can do.
Let’s start exploring it by installing the role with the following command.
After running the command, we can see that in the ./roles folder, we have the fully downloaded
role.
101
www.dbooks.org
Figure 57: Role downloaded and installed in the ./roles directory
In our case, we will simply use the default options without any customizations. Now we can
create a webserver.yml playbook. As we can see, it has very basic information that we have
already seen previously, including hosts against which to act, elevation by using the become
keyword, and the role section itself.
An additional point to mention is that we are also installing the PHP package as a dependency
needed to run the website.
---
- hosts: webservers
become: true
tasks:
- name: Enable running PHP code on Apache
yum:
name: "{{ item }}"
update_cache: yes
state: latest
loop:
- php
roles:
- role: geerlingguy.apache
After running the playbook, we can check if the outcome of the installation is correct.
The full output is quite large, but we can see in Figure 58 that the task has executed correctly,
and that the script includes the task called setup-RedHat.yml. We mentioned previously that
this role supports various Linux distributions.
102
Figure 58: Result of running the webserver.yml playbook
Now we can check if the web server is running properly on both the web servers by calling the
http://192.168.3.160 on the desktop machine, and we will notice that result is not being returned.
This is mainly because on the web server, the default firewall ports are not open. So, let’s
change our playbook by adding two more tasks. Firewall has to be open for both http and https
protocols on the two web servers. The two additional tasks to be added to playbook are as
follows.
103
www.dbooks.org
Code Listing 112: Open Firewall to ports 80/443
After rerunning the playbook, we can see the two tasks being executed and completing.
Now we can also retry checking the browser to see if the page will be displayed properly.
Refreshing the page now, we get the result: the default page that Apache Server displays upon
installation. We will get the same result for both web servers.
104
Figure 61: Browsing the webpage on the web servers
Ansible Manager
Server
GitHub
upload download download
Now we will add two new tasks to the webservers.yml file to be able to perform the checkout
locally, and after the checkout, to deploy the files to the remote server.
105
www.dbooks.org
Code Listing 113: Code needed to deploy the application (code previously shown is omitted)
…
vars:
local_git_directory: site
…
- name: Check out a git repository on the control node
local_action:
module: ansible.builtin.git
repo: https://github.com/zoranmax/ansible-succinctly-book.git
dest: "{{ local_git_directory }}"
force: yes
run_once: true
tags: [never, deploy]
Checkout
To check out the repository from GitHub, we will use the local_action module in combination
with ansible.builtin.git, which we haven’t seen before.
The local_action will execute the code on the control node (amgr) rather than on the
managed machine (in our case, web servers). We also had to specify that this task should be
run only once: run_once: true.
The Git module used supports several attributes; here we are only using what is really strictly
necessary:
In this example, the repository is a real repository hosted on GitHub, and it only contains two
files: index.php and the syncfusion logo. The idea is simply to show the concept rather than
deploying large websites.
The task will check out the files in the folder defined in the local_git_directory variable,
defined at the top of the playbook, which in our case is a local folder ./site.
106
Copy the website
Copying files involves the module called copy. This module can copy single files or directories:
In our case, we will copy the content of the checkout GitHub code we previously placed in the
./site folder to the default Apache Server folder where the website is hosted: /var/www/html.
Now that we have deployed the simple website to both servers, we can navigate through the
browser and check if the deployment was successful.
We can now open the URL directly from the Windows desktop and navigate to
http://192.168.3.160 or http://192.168.3.161, which are the two IP addresses of the web servers
we originally set up.
107
www.dbooks.org
Figure 64: Browser showing the deployed page
Web Server 2
Ansible Manager 192.168.3.161
Server
User
There are many great software load balancers that can be used for this purpose, such as Nginx,
HAProxy, and Citrix ADC.
108
In our example, we are going to use HAProxy, which is an open-source product, so we can also
benefit from the fact that it is free.
We can reuse the same directory where we defined the playbook for the web server, and
download the role geerlingguy.haproxy.
We are already familiar with the command, so we can simply run it.
The next step is to create a new playbook called load_balancer.yml with the following content.
---
- hosts: loadbalancer
become: true
vars:
haproxy_backend_balance_method: 'roundrobin'
haproxy_backend_servers:
- name: webserver1
address: 192.168.3.160:80
- name: webserver2
address: 192.168.3.161:80
tasks:
- name: Open firewall
firewalld:
service: "{{ item }}"
state: enabled
immediate: yes
permanent: yes
loop:
- http
- https
109
www.dbooks.org
By looking at the documentation of the role on GitHub, we can see that it has many
configuration options. By checking the defaults/main.yml, we can see all available options.
We are going to configure the basic ones to let us perform the work:
Everything seems to be working okay. To test that the load balancer (lb 192.168.3.200) will
return any data, we can again browse the content by using the browser by navigating to
http://192.168.3.200. Now we can clearly see that the HAProxy is redirecting the calls to one of
the servers we previously configured.
110
Figure 67: HAProxy using the content from web160 (192.168.3.160)
To demonstrate that the reverse-proxy will redirect the URL in a round-robin way, we can also
use the curl command, and issue 10 calls in a sequence by running the following bash
command on the amgr server.
In the result, we will clearly see that the servers are going to be selected alternatively.
111
www.dbooks.org
Final Words
I would like to thank you for reading this book—I really hope you enjoyed the content and the
examples. Ansible has been growing over the last few years into a very mature product, and it is
production-ready.
I tried to present to you the basic (but still very useful) use cases that you will encounter in your
professional life while remaining true to the Succinctly name.
This book should give you a good foundation to get started with Ansible, and the rest of the
discovering should be an evolutive process on your side. The official Ansible documentation is
another helpful resource with many examples.
One important thing to get from this book is that first you need to understand what you are trying
to automate, understand the technology (web server, operating system, database,
infrastructure, etc.), and then how to automate it by using Ansible.
Typically, you will spend more time investigating the configuration of a particular software
component rather than how to automate it with Ansible.
112