Chapter 1. Introduction to Ansible
In this chapter, you’ll learn how Ansible works, the basic concepts on which Ansible is built, its architecture, and why it is essential to use. You’ll also prepare your environment to run Ansible, and boot up some minimal virtual machines, which you’ll use to run examples.
Finally, you’ll start executing your first Ansible commands to practice what you’ve learned in the chapter.
Ansible is an automation tool for managing your infrastructure elements such as cloud, virtual or bare metal servers, network, application configuration.
Like other automation tools, Ansible uses a code-first approach to describe the state of your infrastructure, so you expect the same result every time you run it.
Ansible focuses on three important topics to make it the perfect tool for your IT operations:
-
Ansible provides instructions to automate operations
-
Ansible provides configuration management
-
Ansible let you automate the deployment of your applications into the infrastructure
This chapter is one of the most important of the book because you’ll get the ground knowledge to understand the rest of the book.
Ansible Overview
Before we discuss the more advanced capabilities of Ansible, it is important to understand what Ansible is, why it is used, and how it works. These concepts will be expanded upon in subsequent chapters.
Why Ansible?
While in the past, you managed only a small number of servers; for example, two web servers, two database servers, and one proxy instance. Nowadays, things are more complex with options such as cloud, virtual, and bare metal infrastructures. Applications are bigger and distributed, meaning requirements on infrastructure are higher than ever before. This implies that there are now more servers to install, configure, and manage.
Due to the increased complexity of infrastructure, managing these resources manually is simply not an option anymore. Changes applied manually can introduce errors with unintended consequences invalidating the state of the entire environment.
Automation is the key to maintaining this complexity under control.
Moreover, Ansible let you store your infrastructure as code, making it maintainable and reproducible every time.
So, how does Ansible work to communicate with nodes and execute operations against them?
Ansible Nodes
Ansible, unlike other solutions, uses a push-out approach, meaning that instructions are transmitted from a central location and to each of the managed instances (or hosts).
One of the big advantages of this approach is that the remote (or managed) machines need no specialized software (or agent) to be installed; Ansible is considered to be an agentless technology.
Also, this approach keeps nodes more performant, as no extra software is installed, and more secure because you are eliminating a vector attack from the node.
Ansible also supports pull approach, but it is not the default one and we’ll not cover that.
When you want to start using Ansible, you first set up a control node. This is usually a local machine where you will install Ansible and store all the instructions to push out to the managed nodes.
The control node connects to each of the different managed nodes and apply all the instructions defined for each case, for example, installing system packages, copying some files from the control to the managed node, adding users, and so on…
This communication occurs over the network, usually (but not always) over the SSH protocol, making all these interactions secure.
Important
Linux/Unix hosts use SSH by default, while Windows hosts are configured with WinRM or with OpenSSH.
Figure 1-1 shows the communication between a control node and the managed nodes:
We’ve been talking about instructions and executing instructions against the managed nodes, but what are these instructions?
Ansible Modules
Ansible connects to managed nodes and pushes out small programs called modules.
These modules implement the logic to execute against against the node. Examples include running the yum
command to install a package or running scp
or sftp
to copy a file from the control to the managed node.
Once Ansible is connected to the nodes, it transfers the modules required by your instructions to the remote node(s) for execution. Then, these modules are executed, and finally, Ansible removes them when it finishes.
Figure Figure 1-2 depicts the scenario where Ansible copies the modules to the managed nodes.
Ansible comes with a set of built-in modules that provide support for most of the common tasks that you will need. However, you can also source modules from external sources or create custom modules of your own to extend the capabilities provided by Ansible. Modules are typically written using Python or PowerShell when targeting Windows instances.
With these concepts in mind, let’s prepare your local machine to be suitable for running Ansible.
Installing and Running Ansible
This section will walk you the steps for running Ansible on your local machine (control node), and configuring a virtual machine to run it as a managed node.
In terms of hardware, for running Ansible standalone, you only need 1 CPU, 2 GB of memory, and 20 GB of hard disk space.
But, for this book, you’ll need additional hardware resources in order to run a virtual machine as a managed node. So, the recommended settings for this environment are 2-4 CPUs, 8-16 Gb of memory, and 40Gb of hard disk space.
Ansible has a different set of requirements for control nodes and managed nodes. Control nodes can be installed ot any UNIX style machine with Python 3.9 or newer installed as well as Windows machines using Windows Subsystem for Linux (WSL) installed. Windows environments without WSL are not currently (as of this writing) supported.
Managed nodes, however, do allow for additional flexibility in terms of the software requirements that can be used. Python 2 (2.7) or 3 (3.5 - 3.11) both are supported.
The examples within this book have been tested and run in a MacOS Ventura 13.5.1 and Python 3.9.6. The following snippet shows how to verify the installed version of Python:
python3-V
Python
3
.9.6
(
default,May
7
2023
,
23
:32:44)
Installing Ansible
As we previously mentioned, Ansible is an agentless automation tool. Once Ansible is installed, it does not add any third-party tooling, such as a database. Ansible is just another CLI tool that is installed on your machine (control node).
There are three possible options for installing Ansible on a control node:
-
Install the release with your OS package manager (
yum
,dnf
,apt
,pkg
, … ). -
Install with
pip
(the Python package manager). -
Install from source or tarballs.
For example, in Fedora or RHEL, you’ll use dnf
to install Ansible, as shown in the following snippet:
sudodnf
install
ansible
In Ubuntu/Debian based-systems, you might use apt
as shown in the following snippet:
sudoapt
update
sudo
apt
install
software-properties-common
sudo
apt-add-repository
--yes
--update
ppa:ansible/ansible
sudo
apt
install
ansible
The preferred way to install Ansible on a MacOS is with pip
:
pipinstall
--user
ansible
Important
Your machine must have pip
/pip3
installed. You can find the installation instructions in the following link.
In this reference, you can review the installation process for each of the supported systems.
At the time of writing this book we used the Ansible 2.15.2 version, but any other supported version should work.
ansible--version
ansible
[
core
2
.15.2]
Remote Machine Setup
Although Ansible only needs to be installed into the control node (usually your local machine), the remote machines have two minimal requirements:
-
Have access to the host using SSH (SSHD)
-
Python 2 (version 2.7 or later) or Python 3 (version 3.5 or later).
Ansible manages remote machines using SSH. For running Ansible examples, you’ll need a remote machine to connect and apply the instructions defined in the Ansible files (i.e install a package, copy some files, create users, …)
In the real world, this remote machine might be a virtual machine, a cloud virtual machine, or a physical machine in a data center.
Since you need a remote machine to use Ansible, in this section, you’ll start two virtual machines from your local machine to make it a more generic example and run from your development machine without requiring any external components.
Host and Guest machines
When using Virtual Machines, you need to be aware of two important concepts: the guest and the host machine.
- Host
-
The physical machine where you install the operating system that manages the computer. In the scope of this book, it’s your machine.
- Guest
-
The virtual machine that is installed, executed, and hosted on the host physical machine.
Figure 1-3 shows a schema of Host/Guest machines interaction.
Even though you can use any other hypervisor to run a virtual machine, we decided to use Oracle VM Virtual Box as it’s widely adopted and supported on most platforms.
Oracle VM VirtualBox
Oracle VM VirtualBox is a type-2 hypervisor for x86 virtualization that runs virtual machines on the local computer, and depending on the machine enable virtualization in the BIOS.
VirtualBox can load multiple guest operating systems under a single host operating system (host OS). Each guest can be started, paused, and stopped independently within its virtual machine.
To install VirtualBox, go to Virtual Box Download Page, select your version for your operating system, and install it.
Figure Figure 1-4 shows the VirtualBox homepage with download options:
Once installed, you don’t need to do anything else as Vagrant will be used to create, start, and manage the virtual machines.
Vagrant
Vagrant is an open-source software solution for building and maintaining portable virtual software development environments.
To install Vagrant, go to Vagrant Download Page, select your operative system package, and install it.
Figure Figure 1-5 depicts the Vagrant homepage with download options:
Once the installation process finishes, open a new terminal and run the following command to validate that Vagrant has been installed successfully:
vagrant--version
Vagrant
2
.3.4
Next, before you can start the virtual machines, you need to create a Vagrantfile
to define and configure the virtual machines and their supporting resources, including networking.
Let’s create a new file named Vagrantfile
configuring two virtual machines called staging
and prod
, based on the Fedora operating system, and creating a public network between the guest machines and the host machine:
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant
.
configure
(
"
2
"
)
do
|
config
|
config
.
vm
.
define
"
staging
"
do
|
staging
|
staging
.
vm
.
box
=
"
fedora/37-cloud-base
"
staging
.
vm
.
box_version
=
"
37.20221105.0
"
staging
.
ssh
.
password
=
"
vagrant
"
staging
.
vm
.
network
"
public_network
"
,
use_dhcp_assigned_default_route
:
true
staging
.
ssh
.
forward_agent
=
true
staging
.
vm
.
provision
"
shell
"
,
inline
:
"
ip -4 -o a
"
end
config
.
vm
.
define
"
prod
"
do
|
prod
|
prod
.
vm
.
box
=
"
fedora/37-cloud-base
"
prod
.
vm
.
box_version
=
"
37.20221105.0
"
prod
.
ssh
.
password
=
"
vagrant
"
prod
.
vm
.
network
"
public_network
"
,
use_dhcp_assigned_default_route
:
true
prod
.
ssh
.
forward_agent
=
true
prod
.
vm
.
provision
"
shell
"
,
inline
:
"
ip -4 -o a
"
end
config
.
vm
.
provider
"
virtualbox
"
do
|
vb
|
vb
.
memory
=
"
2048
"
end
end
Defines the staging machine
Sets Fedora from the Vagrant hosted box marketplace as the operating system
Sets the password for SSH connection (username is vagrant by default)
Configures a public network between the guest and host (same IP space)
Executes a shell script to print the IP address in the guest machine
Defines the prod machine
Sets memory value for each machine
To start both machines, run the following command within the same directory as the Vagrantfile
:
vagrantup
It is important to note that if you have more than one network adapter in your machine (i.e LAN and Wi-Fi), you’ll need to choose which network adapter the machine should be assigned.
Select the adapter with the active Internet connection so that the gateway is automatically configured to access the internet when downloading content is required.
The output should be similar to the following:
Bringing
machine
'staging'
up
with
'virtualbox'
provider...
Bringing
machine
'prod'
up
with
'virtualbox'
provider...
=
=
>
staging:
Importing
base
box
'fedora/37-cloud-base'
...
=
=
>
staging:
Matching
MAC
address
for
NAT
networking...
=
=
>
staging:
Checking
if
box
'fedora/37-cloud-base'
version
'37.20221105.0'
is
up
to
date...
=
=
>
staging:
Setting
the
name
of
the
VM:
vagrant_staging_1677076653292_56104
=
=
>
staging:
Clearing
any
previously
set
network
interfaces...
=
=
>
staging:
Available
bridged
network
interfaces:
1
)
en0:
Wi-Fi
(
AirPort
)
2
)
en2:
Thunderbolt
2
3
)
en1:
Thunderbolt
1
4
)
bridge0
5
)
p2p0
6
)
awdl0
7
)
llw0
=
=
>
staging:
When
choosing
an
interface,
it
is
usually
the
one
that
is
=
=
>
staging:
being
used
to
connect
to
the
internet.
=
=
>
staging:
staging:
Which
interface
should
the
network
bridge
to?
1
=
=
>
staging:
Preparing
network
interfaces
based
on
configuration...
staging:
Adapter
1
:
nat
staging:
Adapter
2
:
bridged
=
=
>
staging:
Forwarding
ports...
staging:
22
(
guest
)
=
>
2222
(
host
)
(
adapter
1
)
=
=
>
staging:
Running
'pre-boot'
VM
customizations...
=
=
>
staging:
Booting
VM...
=
=
>
staging:
Waiting
for
machine
to
boot.
This
may
take
a
few
minutes...
staging:
SSH
address:
127
.0.0.1:2222
staging:
SSH
username:
vagrant
...
staging:
1
:
lo
inet
127
.0.0.1/8
scope
host
lo
\
valid_lft
forever
preferred_lft
forever
staging:
2
:
eth0
inet
10
.0.2.15/24
brd
10
.0.2.255
scope
global
dynamic
noprefixroute
eth0
\
valid_lft
86398sec
preferred_lft
86398sec
staging:
3
:
eth1
inet
192
.168.1.92/24
brd
192
.168.1.255
scope
global
dynamic
noprefixroute
eth1
\
valid_lft
43198sec
preferred_lft
43198sec
=
=
>
prod:
Importing
base
box
'fedora/37-cloud-base'
...
=
=
>
prod:
Matching
MAC
address
for
NAT
networking...
=
=
>
prod:
Checking
if
box
'fedora/37-cloud-base'
version
'37.20221105.0'
is
up
to
date...
=
=
>
prod:
Setting
the
name
of
the
VM:
vagrant_prod_1677076756188_18776
=
=
>
prod:
Fixed
port
collision
for
22
=
>
2222
.
Now
on
port
2200
.
...
=
=
>
prod:
Rsyncing
folder:
/Users/asotobu/git/ansible-tutorial/apps/vagrant/
=
>
/vagrant
=
=
>
prod:
Running
provisioner:
shell...
prod:
Running:
inline
script
prod:
1
:
lo
inet
127
.0.0.1/8
scope
host
lo
\
valid_lft
forever
preferred_lft
forever
prod:
2
:
eth0
inet
10
.0.2.15/24
brd
10
.0.2.255
scope
global
dynamic
noprefixroute
eth0
\
valid_lft
86397sec
preferred_lft
86397sec
prod:
3
:
eth1
inet
192
.168.1.93/24
brd
192
.168.1.255
scope
global
dynamic
noprefixroute
eth1
\
valid_lft
43198sec
preferred_lft
43198sec
//
Use the vagrant
tool to open an SSH connection to each machine and execute the ifconfig
tool to get the network information from each machine.
For example, to get the IP of the prod
virtual machine, you should execute the following command:
vagrantssh
prod
-c
"ifconfig"
eth0:
flags
=
4163
<UP,BROADCAST,RUNNING,MULTICAST>mtu
1500
inet
10
.0.2.15netmask
255
.255.255.0broadcast
10
.0.2.255
inet6
fe80::e248:38f1:e906:ab80
prefixlen
64
scopeid
0x20<link>
ether
52
:54:00:86:4c:24txqueuelen
1000
(
Ethernet)
RX
packets
1127
bytes
129961
(
126
.9KiB
)
RX
errors
0
dropped
0
overruns
0
frame
0
TX
packets
922
bytes
145449
(
142
.0KiB
)
TX
errors
0
dropped
0
overruns
0
carrier
0
collisions
0
Before using Ansible against these machines, the final task is to test communicating with both virtual machines to confirm it is capable of establishing an SSH connection.
Let’s connect using the previous IPs, with username vagrant
and password vagrant
, to validate that we can log in to both virtual machines.
ssh
vagrant@192.168.1.92
Theauthenticity
of
host
192
.168.1.92
(
192
.168.1.92)
' can'
tbe
established.
ED25519
key
fingerprint
is
SHA256:CW3D3uc0+i77Fmcwk5Iskdrr98d70ZCrq2HUBLLwmvM.
This
key
is
not
known
by
any
other
names
Are
you
sure
you
want
to
continue
connecting
(
yes/no/[
fingerprint])
?yes
Warning:
Permanently
added
'192.168.1.92'
(
ED25519)
to
the
list
of
known
hosts.
[
vagrant@localhost~
]
$
And exit
from the guest terminal.
logout
Connection
to
192
.168.1.92closed.
And repeat the steps above with the staging
virtual machine.
Important
During the book, we’ll use the IPs given by our host machine, which most likely differs from your assigned addresses. Substitute the IP addresses accordingly.
Clean Up
You can stop the virtual machines using the following command:
vagranthalt
==
>prod:
Attempting
graceful
shutdown
of
VM...
==
>staging:
Attempting
graceful
shutdown
of
VM...
In case you plan to not use them anymore, run the destroy
command to remove them from the local disk:
vagranthalt
==
>prod:
Attempting
graceful
shutdown
of
VM...
==
>staging:
Attempting
graceful
shutdown
of
VM...
❯
vagrant
destroy
prod:
Are
you
sure
you
want
to
destroy
the
'prod'
VM?
[
y/N]
y
==
>prod:
Destroying
VM
and
associated
drives...
staging:
Are
you
sure
you
want
to
destroy
the
'staging'
VM?
[
y/N]
y
==
>staging:
Destroying
VM
and
associated
drives...
Now that you’ve built the infrastructure to run Ansible, let’s explore key concepts of Ansible and execute a few commands against the machines you’ve created in this section.
Core Ansible Components
Once installed, you can start using Ansible from your laptop. However, it is important to understand four core components of Ansible:
-
Inventories
-
CLIs
-
Hosts
-
Configuration options
Although we will cover them in detail in other chapters, we will provide a brief introduction so that you will be able to understand the first set of Ansible examples.
Hosts
As mentioned before, there are two kinds of hosts in Ansible: the control hosts and managed hosts.
The Control host is where the Ansible CLI is executed and this can be your local machine, a CI/CD server machine, a container, or any host that can execute Ansible.
On the other hand, managed hosts, also referred to as nodes, are the target devices (servers, network appliances, or any computer) you aim to configure with Ansible. In the previous section, when we introduced you Ansible CLI tool, you executed a module against a single machine or host. Still, Ansible lets you specify a group/(s) of hosts.
The Ansible Inventory File defines all the managed hosts and groups of managed hosts to run automation tasks. Because nodes are associated to one or more grouped within inventories, you run commands (and playbooks) against specific hosts and/or groups. This makes applying a change to multiple hosts easier than applying the change host by host.
For example, if you’ve have three nodes (classified under backend group), you can install a particular piece of software, such as Java, with a single command instead of repeating it individually against each host.
The inventory files are written in either INI or YAML format, but the most common format is INI. Hosts are defined by their DNS name, hostname, or IP.
Let’s see an initial example of a simple inventory file defining both of the hosts you created earlier using Vagrant.
192.168.1.92
192.168.1.93
In this example, no group has been defined; all hosts are ungrouped.
Even though no group is defined explicitly, Ansible implicitly creates two groups: all
and ungrouped
.
The all
group contains every host defined in the inventory file, grouped or not.
The ungrouped
group contains all hosts that do not have another group besides all
; in the previous example, both hosts fall in this category.
The same example in the YAML file is shown in the following snippet:
ungrouped
:
hosts
:
192.168.1.92
:
192.168.1.93
:
Tip
The default location for Ansible inventory is /etc/ansible/hosts
. If you set hosts there, you are setting the inventory globally, and it’s unnecessary to specify the inventory file location using the -i
option. You can override the default Ansible inventory file location by setting the new location in the ANSIBLE_INVENTORY
environment variable or within an ansible.cfg
file which will be covered in detail later on.
Groups
Assume that the 192.168.1.92
host is a web server and 192.168.1.93
host is a database server; you will probably need to apply different commands in each instance, so let’s group them so they are identified in the inventory file.
To create a group, set a new INI section with the group’s name in the file.
An INI section is defined on a line in square brackets ([
and ]
), containing the section’s name between the brackets.
Let’s create an inventory file with two groups (webservers and dbservers):
[webservers]
192.168.1.92
[dbservers]
192.168.1.93
The equivalent inventory in YAML format is shown in the following snippet:
webservers
:
hosts
:
192.168.1.92
:
dbservers
:
hosts
:
192.168.1.93
:
You can set more than one host in each of the group as shown in the following example:
[webservers]
web1.example.com
web2.example.com
[dbservers]
192.168.0.45
db2.example.com
db3.example.com
Important
You can put the same host in more than one group; for example, one host can belong to both a testing and performance groups.
Group names should follow the variable names format. In summary, a variable name can only include letters, numbers, and underscores. Python keywords or playbook keywords are not valid variable names. A variable name cannot begin with a number.
Table 1-1 shows valid and invalid variable names.
Valid | Invalid |
---|---|
foo |
*foo |
foo_env |
async, environment (Python keyword) |
foo_port |
foo-port, foo.port, foo port |
foo5, _foo |
5foo, 46 |
Adding variables to inventory
Just as a programming language, Ansible provides the concept of variables. A variable lets you change a common function’s behavior by providing a different value as an input parameter.
For example, the admin password for a database server might be different in staging than in a production environment. But, installing the database server itself should, for the most part, be the same. The password could be a variable that, depending on the environment, could have one value or another.
You can use variables in a variety of places including module arguments, within conditional when
statements, in templates, and in loops.
Variables are set in multiple places in Ansible, and one of these places also includes the inventory. In the inventory file, you can store variable values to apply to a specific host or group.
In Ansible, there are two kinds of variables:
- User Variables
-
Variables you create, set, and use as a part of your logic.
- Special Variables
-
Variables provided by Ansible. You can use them to get information from Ansible (
inventory_file
,groups
, …) named Magic variables, or to set the connection details to hosts (ansible_user
,ansible_become_user
,ansible_ssh_private_key_file
, …).
Let’s set the ansible user in the inventory file:
[webservers]
web1.example.com ansible_user
=
vagrant
Based on the previous inventory definition, Ansible uses vagrant
username instead of the system provided value to establish the SSH connection against the managed host.
The equivalent in YAML format is as follows:
webservers
:
hosts
:
web1.example.com
:
ansible_user
:
vagrant
If all hosts in a group share a variable value, you can apply that variable to all groups at once.
To define a variable to multiple hosts, you can place them in a special section that follows the format <group_name>:vars
.
For example, to change the Ansible port (this is the port Ansible uses for establishing the SSH connection) to a different value for all hosts defined in a group, define a new section with the :vars
suffix as shown in the following snippet:
[webservers]
web1.example.com
web2.example.com
[webservers:vars]
ansible_port
=
5555
The equivalent inventory in YAML format is as follows:
webservers
:
hosts
:
web1.example.com
:
vars
:
ansible_port
:
5555
Multiple inventory files
As your inventory grows with more entries, you may need more than one file to organize your hosts and groups. There are four possible options you can choose beyond using a single file:
-
You can create a directory with multiple inventory files.
-
You can pull inventory dynamically, for example, hosts created in the cloud.
-
You can use a mix of dynamic and static inventory files.
-
You can use
-i
argument multiple times to append inventory files.
We’ve covered the basics of creating and using inventories. We’ll cover more advanced use cases in future chapters.
In the following section, you’ll so the CLI tools provided by Ansible.
CLI Tools
When Ansible was installed (as described in the Installing Ansible section), the Ansible CLI tool and a few other CLI tools supporting the Ansible ecosystem were installed.
In this section, we’ll provide an overview of two of the most important tools you’ll use — ansible
and ansible-playbook
.
We’ll then briefly discuss a few other tools.
ansible CLI
ansible
defines and runs a single task against a set of hosts.
This tool can be used in any situation. Still, because of its simplicity, it is mainly used for test/demo purposes or executing sporadic tasks (such as shutting down hosts).
Let’s see an initial example of using the ansible
tool.
You’ll use Ansible to ping the hosts you created earlier to validate that it’s up and running.
First of all, create an inventory file called inventory
with both host IPs defined.
192.168.1.92
192.168.1.93
Next, ping hosts using the ansible
tool with the previously defined inventory.
The ansible
command format uses <pattern> as the first argument and then a list of optional arguments.
The -m
argument is used to specify an Ansible module (such as the ping module) to execute the logic of executing a ping to the host.
If no module is set, it defaults to the command module, which executes a command against the selected hosts matched by the specified pattern.
Important
The command(s) will not be processed through the shell.
In a terminal window, run the following command:
ansibleall
-i
inventory
-m
ping
192
.168.1.101
|
UNREACHABLE!
=
>
{
"changed"
:false,
"msg"
:
"Failed to connect to the host via ssh: asotobu@192.168.1.101: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password)."
,
"unreachable"
:
true
192
.168.1.102
|
UNREACHABLE!
=
>
{
"changed"
:false,
"msg"
:
"Failed to connect to the host via ssh: asotobu@192.168.1.102: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password)."
,
"unreachable"
:
true
The command is failing because it is trying to connect using the current user logged in to the terminal, which, in our case, is asotobu
.
To fix it, you must specify the username to establish the SSH connection.
Use the --user
option of the ansible
CLI with vagrant
to override the default username.
Repeat the execution but set the username:
ansibleall
-i
inventory
--user
vagrant
-m
ping
192
.168.1.101
|
SUCCESS
=
>
{
"ansible_facts"
:
{
"discovered_interpreter_python"
:
"/usr/bin/python3"
}
,
"changed"
:false,
"ping"
:
"pong"
192
.168.1.102
|
SUCCESS
=
>
{
"ansible_facts"
:
{
"discovered_interpreter_python"
:
"/usr/bin/python3"
}
,
"changed"
:false,
"ping"
:
"pong"
Important
If the unreachable error persists, add the --ask-pass
argument in the command after the username.
Now, everything worked as expected; you executed a simple command that verified all managed hosts were up and running.
ansible
has an extensive list of optional arguments; Table 1-2 summarizes the most important ones.
Flag | Description |
---|---|
|
Specify inventory host path or a comma separated host list. |
|
Run operations with become (does not imply password prompting). When using this option, the user executing the Ansible command “becomes” another user, different from the user that logged into the machine. It uses existing privilege escalation tools like |
|
Specify the number of parallel processes to use, defaults to 5. |
|
Run asynchronously, failing after <SECONDS>. |
|
Ask for a privilege escalation password. |
|
Privilege escalation method to use. Defaults to |
|
Privilege escalation password file. |
|
Run operations as this user. Defaults to root. |
|
Set additional variables as key=value or YAML/JSON. When specifying a file containing a set of variables, prepend the file with |
|
Name of the module to execute. Defaults to |
|
The module options in key=value format or JSON |
|
Ask for the vault password |
|
Print more debug messages |
ansible-playbook
As described previously, the ansible
tool is only used in certain circumstances (ad-hoc commands); what you will use in most circumstances is ansible-playbook
tool.
It is similar to the ansible
tool but executes sets of tasks defined in Ansible playbooks against the targeted hosts instead of a single ad-hoc command.
A Playbook is a list of tasks executed automatically with limited manual effort across all the targeted hosts. In summary, playbooks tell Ansible what to do to which devices, for example, installing Java 17 hosts where the backend is deployed. We’ll cover playbooks in detail in the next chapter.
The format of the ansible-playbook
command is a list of optional arguments and then at least the location of one playbook (but it can be a list).
ansible-playbook
has an extensive list of optional arguments. Table 1-3 summarizes the most important ones.
Flag | Description |
---|---|
|
Specify inventory host path or comma-separated host list. |
|
Run operations with become (does not imply password prompting). When using this option, the user executing the Ansible command “becomes” another user, different from the user that logged into the machine. It uses existing privilege escalation tools like |
|
Ask for privilege escalation password. |
|
Privilege escalation method to use. Defaults to |
|
Privilege escalation password file. |
|
Run operations as this user defaults to root. |
|
Specify the number of parallel processes to use. Defaults to 5. |
|
Set additional variables as key=value or YAML/JSON. When specifying a file containing a set of variables, prepend the file with |
|
Only run plays and tasks tagged with these values. |
|
Only run plays and tasks whose tags do not match these values. |
|
Further limit selected hosts to an additional pattern |
|
connection type to use defaults to smart. |
|
|
Other CLI tools
The installation process copies ansible
and ansible-playbook
tools to your machine, but those are not the only tools Ansible provides.
Below is a complete list of Ansible tools that are available for use.
This is an introduction to each command; you’ll learn about them deeply in other chapters.
- ansible-config
-
A CLI tool to initialize, list, view, or dump the configuration files.
- ansible-doc
-
Displays information related to modules installed in Ansible libraries. It shows a terse listing of plugins and their short descriptions, provides a printout of their documentation, and can create a short snippet as an example, which can be pasted into a playbook.
- ansible-galaxy
-
Command to create, download, build, or publish Ansible roles and collections.
- ansible-inventory
-
Displays Ansible inventory information; by default, it uses the inventory script JSON format.
- ansible-pull
-
Used to pull a remote copy of Ansible playbooks on each managed node, each set to run via cron and update playbook source via a source repository. This inverts the default push architecture of Ansible into a pull architecture, which has near-limitless scaling potential.
- ansible-vault
-
Utility for encrypting/decrypting Ansible data files. Typically, you’ll encrypt/decrypt Ansible variables (for example, usernames/passwords, application configuration files with sensitive information, and third-party API keys). Still, since Ansible tasks or handlers are data objects and these can also can be encrypted.
In the following section, you will learn how to select the target hosts where Ansible executes the commands.
Hosts Patterns
When executing Ansible either using the ansible
tool or defining a playbook, you must select which nodes or groups Ansible will run against.
In the previous section, you used a pattern to select all
hosts from the inventory to execute the command against.
But this was just an example, as an Ansible pattern can refer to a single host, an IP address, an inventory group, or a set of groups defined within inventory files.
This section will teach you about various host patterns; from simple to advanced types. Simple patterns are enough, in most cases, to select hosts. But it is important to have a correct understanding of patterns as it might be the difference between solving a complex problem efficiently.
Let’s assume an inventory file contains the following:
[webservers]
web1.example.com
web2.example.com
[dbservers]
db1.example.com
db2.example.com
db3.example.com
[staging]
web1.example.com
db1.example.com
To target all hosts you’ve seen before, use the all
pattern, and Ansible runs commands against all hosts defined in the inventory.
To target a specific host, use the hostname directly; for example, you could use db1.example.com
.
Target multiple hosts, separating them using a comma (,
) or a colon (:
), for example web1.example.com,db1.example.com
.
You can refer to a group by its name, webservers
to execute Ansible commands against all hosts belonging to the group.
Or, you can also target more than one group, separating them by colon (:
) like webservers:dbservers
.
Group patterns also support two operators: the excluding operator (!
) and the inclusion operator (&
).
For example, targeted host for the following pattern webservers:!staging
is web2.example.com
as web1.example.com
is in the exclusion group.
If you use the following pattern, webservers:&staging
, the target host is web1.example.com
as it’s the only host in both groups.
Important
The following order is used when processing operators:
-
:
and,
.&
.!
You can use wildcard patterns to specify a group of hosts directly.
For example, *.example.com
will match every host of the example.com
domain.
You can use regular expressions instead of wildcards if you need it by starting the pattern with a (~
) character. For example, ~(web|db).*\.example\.com
.
You can add dynamic patterns referring to elements as variables you set at runtime. For example, with the following pattern webservers:!{{ excluded }}
excluded groups are set as an Ansible variable named excluded
.
You can also refer to hosts using an array-like expressions.
Some examples include the following:
dbservers[
0
]
# db1.example.com
dbservers[
-1]
# db3.example.com
dbservers[
0
:2]
# == db1.example.com, db2.example.com
dbservers[
1
:]
# == db2.example.com, db3.example.com
dbservers[
:3]
# == db1.example.com, db2.example.com, db3.example.com
Important
Patterns depend on inventory. If a host or group is not listed in your inventory, you cannot use a pattern to target it.
Another important Ansible concept is the configuration files that let you configure Ansible globally.
Configuring Ansible
You can configure Ansible via a configuration file affecting the overall execution. For example, you can set the become password file globally so that you do not need to type it repeatedly when running Ansible.
The default configuration parameters should be sufficient for most users. However, there are some situations in which you might want to customize how Ansible operates.
The Ansible configuration is a file in defined in INI format, using a hash sign (#
) for line comments and a semicolon (;
) for line and inline comments.
You can see an example in the following snippet, where the inventory source is set:
# line comment
inventory
=
/etc/ansible/hosts ; Sets inventory file location
The Ansible configuration file can be set in different places and loaded in the following order:
-
Set the
ANSIBLE_CONFIG
environment variable pointing out to the configuration file. -
ansible.cfg
file created in the same directory where execute Ansible. -
~/.ansible.cfg
file located at the root of your home directory. -
/etc/ansible/ansible.cfg
global file at the default Ansible directory.
An extensive list of properties can be set in an Ansible configuration file. They are all defined in the official documentation’s Ansible Configuration Settings section.
Since managing Ansible configuration properties might be complicated and you can be overwhelmed by the sheer number of options, Ansible provides the ansible-config
CLI tool which enables you to scaffold an ansible.cfg
file.
Also, can be used as helping tool for checking possible configuration options, its default values, a description of its usage.
To generate a complete configuration file example, run the following command in a terminal window:
ansible-configinit
--disabled
>
ansible.cfg
The generated file is a commented-out example describing the settings and valid values for each property. To enable any property, remove the comment.
To list all available options, run:
ansible-configlist
The output is a scrolling page describing each of the settings.
To print all the current values used during Ansible execution, use the dump
option.
ansible-configdump
The output is a scrolling page showing each key with the value used at runtime.
After executing your first Ansible command (an ad-hoc command) and exploring the basics of Ansible, it’s time to understand ad-hoc commands in further detail.
Ad-hoc commands
In the previous section, you executed the ping command to all hosts defined in the inventory file using the ansible
CLI tool.
This scenario is known in Ansible as an ad-hoc command.
Ad-hoc commands show how easy it is to apply a single task on one or more managed nodes. They are compelling and let you automate some operations, but they have some limitations:
-
Only a single task can be executed.
-
They steps are not reusable or can be versioned within a source control repository as the CLI based commands are imperatively executed on a control node.
In the following chapter, we will discuss you playbooks, which solves the aforementioned challenges. However, even with these limitations, ad-hoc commands are still very useful.
Ad-hoc commands takes the following form:
ansible
\
<
pattern>
\
<
ansible
arguments>
\
-m
<
module>
\
-a
"<module arguments>"
ansible
CLI toolHost pattern
Arguments of the tool (
-i
,-b
, …)Module to execute (i.e
ping
)Module arguments, accepts options either through the
key=value
syntax or a JSON.
Let’s see some examples where the ad-hoc is useful.
Rebooting Servers
One such use case where ad-hoc commands can help is the halting/stopping/rebooting of servers.
To reboot all managed servers defined in the inventory file, run the following command:
ansibleall
-i
inventory
-a
"/sbin/reboot"
Important
If the user does not have permissions to execute the rebooting command, then it requires privilege escalation to become another user with the required permissions to reboot the server, such as root
.
In the following snippet, Ansible executes the the command under the root
user:
ansibleall
-i
inventory
-a
"/sbin/reboot"
-u
root
--become
--ask-become-pass
If you use the --ask-become-pass
argument, Ansible prompts you for the password for privilege escalation.
The command reboots the given server, so the connection with Ansible is stopped.
Managing Users
The user command allows for the creation, management, and removal of user accounts on managed nodes.
Let’s see how to add a new user to all machines using an ad-hoc command:
ansibleall
-i
inventory
-u
root
--become
--ask-become-pass
-m
user
-a
"name=ada password=<crypted password here>"
The equivalent command using JSON formatted arguments is shown in the following snippet:
ansibleall
-i
ungrouped_inventory
--user
vagrant
-m
user
-a
'{"name":"ada", "password":<crypted password here>}'
Removing a user is just as easy:
ansibleall
-i
inventory
-m
ansible.builtin.user
-a
"name=john state=absent"
Later on, you will learn about states in Ansible. For now, think of states as the way to set the desired result of a resource once Ansible execution has completed. In the above example, since state was specified to be “absent”, the result after Ansible has completed executing is the removal of the user from the host.
Gathering Facts
In some situations, you might need details associated with hosts in your inventory. Ansible organizes these details into facts
.
Facts are information of the remote hosts, such as the hostname, IPs, disk, disk space, date time, bios, …
You can use these facts later in a playbook.
Let’s gather facts from the two virtual machines you created at the beginning of this chapter: using the setup
module:
ansibleall
-i
inventory
-m
ansible.builtin.setup
192
.168.1.102
|
SUCCESS
=
>
{
"ansible_facts"
:
{
"ansible_all_ipv4_addresses"
:
[
"10.0.2.15"
,
"192.168.1.102"
]
,
"ansible_all_ipv6_addresses"
:
[
"fe80::e248:38f1:e906:ab80"
,
"fd9c:3006:46b1:318:16d5:ffce:8307:bc52"
,
"fe80::17e:a1e:12c7:e295"
]
...
The output is in JSON format providing all kinds of information about the host.
With a basic understanding of Ansible, the basic concepts like inventory, and how to execute Ansible modules, it’s time to move on and learn how to use Ansible to write tasks as code and automate their execution.
In the following chapter, we’ll introduce one of the most important concepts of Ansible: playbooks.
Get Red Hat Certified Engineer (RHCE) Ansible Automation Study Guide now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.