A system administrator, in the vast majority of cases, has to take care of more than one server, so he often has to perform repetitive tasks on all of them. In these cases automation is a must. Ansible is an open source software owned by Red Hat; it is written in the Python programming lanaguage, and it is a provisioning and configuration management software which help us in the aforementioned cases. In this tutorial we will see how to install it and the basic concepts behind its usage.
In this tutorial you will learn:
- How to install Ansible on the most used Linux distributions
- How to configure Ansible
- What is the Ansible inventory
- What are the Ansible modules
- How to run a module from the command line
- How to create and run a playbook
Software requirements and conventions used
Category | Requirements, Conventions or Software Version Used |
---|---|
System | Distribution independent |
Software | Ansible, Python |
Other | None |
Conventions | # – requires given linux-commands to be executed with root privileges either directly as a root user or by use of sudo command$ – requires given linux-commands to be executed as a regular non-privileged user |
Installing Ansible
The Ansible package is contained in the official repositories of the most used Linux distributions so it can be easily installed using their native package managers. To install it on Debian we can run:
$ sudo apt-get update && apt-get install ansible
To install Ansible on Fedora, instead:
$ sudo dnf install ansible
Ansible is in the Archlinux “Community” repository; we can install it using pacman:
$ sudo pacman -Sy ansible
If we want to install Ansible on CentOS8, we have to add the epel-release software source to our system, since the package is not available in the default repositories. To do so we run the following command:
$ sudo dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
After that, we can simply run:
$ sudo dnf install ansible
For other distribution-specific installation instructions we can consult the dedicated page
of the Ansible official documentation.
Introducing Ansible
The fundamental peculiarity of Ansible is that it is an agentless provisioning system. This means that we don’t need to install any agent or software daemon on the servers we want to control. All we need is to install and configure Ansible on the so called control machine. The tasks we configure will be performed, in the vast majority of cases, via a simple ssh connection.
The Ansible configuration file
Ansible can be configured by specifying parameters and their values in one or more configuration files. The application, in order of priority, looks for the following files:
- The file specified via the ANSIBLE_CONFIG variable
- The
ansible.cfg
file in the current working directory - The
.ansible.cfg
file in the user home directory - The
/etc/ansible/ansible.cfg
file
The /etc/ansible/ansible.cfg
is the last one, so it is used as a fallback and the default. For obvious reasons, this is not the appropriate place to describe all the possible parameters which can be specified in a configuration file, however, here is an excerpt of the file content:
[defaults] # some basic default values... #inventory = /etc/ansible/hosts #library = /usr/share/my_modules/ #module_utils = /usr/share/my_module_utils/ #remote_tmp = ~/.ansible/tmp #local_tmp = ~/.ansible/tmp #plugin_filters_cfg = /etc/ansible/plugin_filters.yml #forks = 5 #poll_interval = 15 #sudo_user = root #ask_sudo_pass = True #ask_pass = True #transport = smart #remote_port = 22 #module_lang = C #module_set_locale = False
The ones in the example are commented parameters which are defined with their default values. Among them, you can see the inventory
parameters, which has the /etc/ansible/hosts
value. We will see what this is in the next section.
The “hosts” or “inventory” file
The ansible “hosts” file, is where we basically set the IP address or the hostnames of the machines we want to control with Ansible (this is the “inventory” in the Ansible jargon). On a standard installation, the file is located in the /etc/ansible
directory. Inside the inventory file, hosts can be grouped or ungrouped. We can specify an host by itself, for example:
server1
When we want to perform operations on more than one host, however, it is very useful to put hosts in groups, created, for example, using their “role” as criteria. Supposing the hosts we are dealing with are all used as webservers, we could write:
[webservers] server1 server2
Ansible modules
Ansible modules are basically small programs used to perform the tasks we need; each of them is designed to perform a single basic operation, in order to ensure granularity. They can be executed from the command line or inside playbooks. The complete list of all modules can be found on the dedicated page of the official documentation. Again, here we cannot examine all the modules, but here are some examples.
The apt, dnf and yum modules are used to manage packages with the file managers the take their name
from. The seboolean module is used to manager the status of SELinux booleans, the user module is used to manage user accounts, etc.
Using modules from the command line
As we said in the previous section, modules can be used from the command line or from playbooks. We will focus on the latter in the next section; here we will demonstrate how to use a module from the command line, with the ansible
command. In this example we will use the ping module. This module has nothing to do with the ping command, but it is used to check that we can login on the remote servers, and that a Python interpreter is installed on them. The module returns the “pong” value on success:
$ ansible webservers -m ping --ask-pass
We invoked the ansible command specifying that we want to run the task on the hosts members of the “webservers” group and with the -m
option we passed the name of the module we want to use. We also used the --ask-pass
option, why? Although I previously added the remote servers fingerprint to the control machine ssh “known hosts” file, I didn’t configure ssh access via public key, so an ssh password should be provided when we run a task. The --ask-pass
option makes so that the password is asked interactively. Here is the output of the command
above:
SSH password: server2 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" } server1 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "ping": "pong" }
Ansible playbooks
What is a playbook? Ansible playbooks are no other than YAML files where we specify the tasks we want to perform using Ansible, and the hosts they should be performed on. Let’s see a playbook example. In the following file we setup a task to ensure the Vim text editor is installed and at the latest available version:
--- - name: Update webservers hosts: webservers remote_user: egdoc become: yes tasks: - name: Ensure Vim is installed and at the latest version dnf: name: vim state: latest ...
Let’s analyze the above. The ---
and ...
we can see, respectively at the beginning and at the end of the file, are part of the standard YAML syntax: they are optional and mark the beginning and the end of the file. The instructions and their values are represented in a dictionary format, as key: value
pairs.
A playbook can contain multiple so-called plays; in this case we just defined one. Indeed the first thing we did was to specify its name
, which in this example is “Update webservers”. The second key we used is hosts
: with it we can define the host group the tasks should be performed on. In this case we specified webservers
as value, which comprehends the machines we defined in the previous examples (server1 and server2).
The next key we used was remote_user
. With it, we can specify what is the user we should login as, via ssh, in the remote servers. After that, we used the become
key. This key accepts a boolean value and with it we specify whether
privilege escalation should used to perform the tasks or not. In this case, since we login in the remote machines using the “egdoc” user, and we need root privileges to install a package, we set it to yes
. It’s important to notice
that privilege escalation are configured in the /etc/ansible/ansible.cfg
configuration file, in the dedicated section. In this case the default values are the following:
[privilege_escalation] #become=True #become_method=sudo #become_user=root #become_ask_pass=False
After defining the play information, we started to specify our list of tasks. To do so we used the tasks
keyword. Each task has a name
which is used for documentation and in task handlers.
With dnf:
we specified that we want to use the “dnf” module, which, as we saw before, is used to manage packages using the default package manager in the Red Hat family of distributions. Inside this section, with the name
keyword
we specified the packages name. In this example we are interested only in a single package, but multiple packages can be specified using a list. For example:
dnf: name: [vim, nano]
With the state
keyword of the dnf
module we basically specify what we want to do with the specified package(s). In this case we used latest
as the value: with it we ensure that the package is installed and at the latest available version on the distribution used on the remote machine. Other possible values we can use are remove
or absent
, which causes the package(s) to be uninstalled, or present
which just ensure the package is installed. I recommend you to check the official module documentation for the complete list of keys and values that can be used with the module.
Here we go, we just defined our first playbook. How can we run it?
Running a playbook
To run a playbook we use the dedicated ansible-playbook
command. The command accepts a series of options, and takes one or more playbook files as arguments. To run the playbook we defined in the previous section, for example we would run the following command:
$ ansible-playbook --ask-pass ---ask-become-pass /path/to/playbook.yml
You can notice that in this case we invoked the command with the --ask-become-pass
options. This option is needed because in the playbook file we assigned the yes
value to the become
key, since we need privilege escalation in order to install packages on the remote machines. The --ask-become-pass
option makes so that the sudo
password is asked when we run the playbook. In this case, since we used also --ask-pass
, the SSH password will be used as the default password for privilege escalation. Here is the output we receive when we run the playbook:
SSH password: BECOME password[defaults to SSH password]: PLAY [Update webservers] ******************************************************************************************************************************************* TASK [Gathering Facts] ********************************************************************************************************************************************* ok: [server1] ok: [server2] TASK [Ensure Vim is installed at the latest version] *************************************************************************************************************** changed: [server1] changed: [server2] PLAY RECAP ********************************************************************************************************************************************************* server1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 server2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
First we are asked to provide the “SSH” password, then the “BECOME” one. As we already said, the SSH password will be used as the default value in this case. As you can see before the task we specified in the playbook, another task is execute: “Gathering Facts”. This task is executed by default in order to gather useful variables about remote hosts that can be used in playbooks.
After the tasks are executed, we get a recap of the play(s) we specified. In this case we can see, that two tasks have been executed correctly (ok=2
) and one task has caused a change (changed=1
). This makes sense: the change occurred since the vim package has been installed.
Now, if we try to execute the playbook again, we can see that no changes occur, since vim is already installed and at the last version available:
PLAY RECAP ********************************************************************************************************************************************************* server1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 server2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Conclusions
In this tutorial we learned what Ansible is and what are its peculiarities. We saw how to install it on some of the most used Linux distributions, how to configure it, and some basic concepts: what is an inventory and what are the
Ansible modules. We also saw how to run a module from the command line and how to write and run a playbook. This was meant just as an introduction to the Ansible world; get you hands dirty, experiment and read the official documentation for a more in depth knowledge!