Write a playbook that uses multiple plays with per-play privilege escalation, and effectively use automation content navigator to find new modules in available Ansible Content Collections and use them to implement tasks for a play.
A playbook is a YAML file containing a list of one or more plays. Remember that a single play is an ordered list of tasks to execute against hosts selected from the inventory. Therefore, if a playbook contains multiple plays, each play might apply its tasks to a separate set of hosts.
This can be very useful when orchestrating a complex deployment which might involve different tasks on different hosts. You can write a playbook that runs one play against one set of hosts, and when that finishes, runs another play against another set of hosts.
Writing a playbook that contains multiple plays is very straightforward. Each play in the playbook is written as a top-level list item in the playbook. Each play is a list item containing the usual play keywords.
The following example shows a simple playbook with two plays.
The first play runs against web.example.com, and the second play runs against database.example.com.
---
# This is a simple playbook with two plays
- name: First play
hosts: web.example.com
tasks:
- name: First task
ansible.builtin.dnf:
name: httpd
state: present
- name: Second task
ansible.builtin.service:
name: httpd
enabled: true
- name: Second play
hosts: database.example.com
tasks:
- name: First task
ansible.builtin.service:
name: mariadb
enabled: truePlays can use different remote users or privilege escalation settings than is specified by the defaults or the current configuration file.
You can override these settings in the play itself at the same level as the hosts or tasks keywords.
Tasks in playbooks are normally executed through a network connection to the managed hosts. Ansible has to connect to each managed host as some user to run those tasks.
By default, if you use ansible-navigator run to run a playbook, then Ansible connects to the managed host as the current user inside the automation execution environment, root.
If you run an Ansible command that does not use execution environments, such as ansible-playbook, then by default Ansible tries to authenticate to the remote managed host using the username of the account you used to run the command.
You can set a remote_user directive in your project's ansible.cfg file to configure Ansible to use a different user account on the managed hosts when it initially logs in.
If you still need the tasks to run as root, then you can use privilege escalation to switch to that user after the initial remote connection.
However, you can also specify the remote user that Ansible uses on a play-by-play basis.
If the remote user defined in the Ansible configuration for task execution is not suitable, it can be overridden by the remote_user keyword within a play.
remote_user: remoteuser
Ansible determines which user account to use when connecting to a managed host based on the following list, selecting the first username it finds in this order:
The ansible_user variable set for the host or group, if set.
The remote_user from the current play, if set.
The remote_user from the ansible.cfg configuration file, if set.
If no value has been set for any of the preceding settings, and you are running playbooks by using ansible-navigator with an execution environment, Ansible uses root.
(If you are using ansible-playbook, Ansible uses the name of the user that ran the command.)
You can also configure privilege escalation in a play.
Use the become Boolean keyword to enable or disable privilege escalation for an individual play or task.
This overrides the setting in the ansible.cfg configuration file.
It can take yes or true to enable privilege escalation, or no or false to disable it.
become: true
If privilege escalation is enabled, use the become_method keyword in the play to specify the privilege escalation method to use for that play.
The example below specifies sudo as the method for privilege escalation.
become_method: sudo
Additionally, with privilege escalation enabled, you can use the become_user keyword in the play to define the user account to use for privilege escalation in that specific play.
become_user: privileged_userThe following example demonstrates some of these keywords in a play:
- name: /etc/hosts is up-to-date
hosts: datacenter-west
remote_user: automation
become: true
tasks:
- name: server.example.com in /etc/hosts
ansible.builtin.lineinfile:
path: /etc/hosts
line: '192.0.2.42 server.example.com server'
state: presentThe large number of modules packaged with Ansible provides administrators with many tools for common administrative tasks.
The following table lists a small number of useful modules as examples. Many others exist.
Table 2.3. Ansible Modules
| Category | Modules |
|---|---|
| Files |
|
| Software |
|
| System |
|
| Net Tools |
|
To see a list of the modules available in your current automation execution environment, run the ansible-navigator doc -l command.
This displays a list of module names and a synopsis of their functions.
[user@controlnode ~]$ ansible-navigator doc -l
add_host Add a host (and alt...
amazon.aws.aws_az_facts Gather information ...
amazon.aws.aws_az_info Gather information ...
amazon.aws.aws_caller_info Get information abo...
amazon.aws.aws_s3 manage objects in S...
...output omitted...
vyos.vyos.vyos_user Manage the collecti...
vyos.vyos.vyos_vlan Manage VLANs on VyO...
wait_for Waits for a conditi...
wait_for_connection Waits until remote ...
yum Manages packages wi...
yum_repository Add or remove YUM r...The ansible-navigator doc -l command displays the short names of modules in the ansible.builtin Ansible Content Collection instead of their FQCNs.
Use the ansible-navigator doc command to display detailed documentation for a module.
If you specify the module_name-m stdout option, formatted documentation is displayed to your terminal.
If you do not specify that option, leaving ansible-navigator in interactive mode, then you can scroll through the documentation in YAML format.
As an alternative, you can run the ansible-navigator collections command in interactive mode and explore the documentation for the collections in the current automation execution environment, and their modules.
The module documentation includes a description of what the module is for, a list of the attributes that you can use to control the module in a task, examples of how to use the module, and other metadata.
You can also view a summary of all the attributes you can use with a module by running the ansible-navigator -s doc command.
This output can serve as a starter template, which can be included in a playbook to implement the module for task execution.
Comments are included in the output to explain how to use each attribute.
The following example shows this output for the module_nameansible.builtin.dnf module.
If you are using the ansible-playbook command provided with limited support in Red Hat Enterprise Linux, or from community Ansible, it uses your control node as an execution environment.
In that case, you can use an ansible-doc command to view documentation for modules installed on the control node, which works with the same options as ansible-navigator doc.
However, the ansible-doc command on your control node cannot be used to inspect documentation for an automation execution environment being used by ansible-navigator.
If a module does not exist to automate some task, special modules are available that can run arbitrary commands on your managed hosts.
The ansible.builtin.command module is the simplest of these commands.
Its cmd argument specifies the command that you want to run.
The following example task runs /opt/bin/makedb.sh on managed hosts.
- name: Run the /opt/bin/makedb.sh command
ansible.builtin.command:
cmd: /opt/bin/makedb.shUnlike most modules, ansible.builtin.command is not idempotent.
Every time the task is specified in a play, it runs and it reports that it changed something on the managed host, even if nothing needed to be changed.
You can try to make the task safer by configuring it only to run based on the existence of a file.
The creates option causes the task to run only if a file is missing; the assumption is that if the task runs, it creates that file.
The removes option causes the task to run only if a file is present; the assumption is that if the task runs, it removes that file.
For example, the following task only runs if /opt/db/database.db is not present:
- name: Initialize the database
ansible.builtin.command:
cmd: /opt/bin/makedb.sh
creates: /opt/db/database.dbThe ansible.builtin.command module cannot access shell environment variables or perform shell operations such as input/output redirection or pipelines.
When you need to perform shell processing, you can use the ansible.builtin.shell module.
Like the ansible.builtin.command module, you pass the commands to be executed as arguments to the module.
Both ansible.builtin.command and ansible.builtin.shell modules require a working Python installation on the managed host.
A third module, ansible.builtin.raw, can run commands directly using the remote shell, bypassing the module subsystem.
This is useful when you are managing systems that cannot have Python installed (for example, a network router).
It can also be used to install Python on a managed host.
When possible, try to avoid the ansible.builtin.command, ansible.builtin.shell, and ansible.builtin.raw modules in playbooks, even though they might seem simple to use.
Because these run arbitrary commands on the managed hosts, it is very easy to write non-idempotent playbooks with these modules.
If you must use them, it is probably best to use the ansible.builtin.command module first, resorting to ansible.builtin.shell or ansible.builtin.raw only if you need their special features.
As another example, the following task using the ansible.builtin.shell module is not idempotent.
Every time the play is run, it rewrites /etc/resolv.conf even if it already consists of the line nameserver 192.0.2.1.
- name: Non-idempotent approach with shell module
ansible.builtin.shell:
cmd: echo "nameserver 192.0.2.1" > /etc/resolv.confYou can create idempotent tasks in several ways using the ansible.builtin.shell module, and sometimes making those changes and using ansible.builtin.shell is the best approach.
But in this case, a better solution would be to use ansible-navigator doc to discover the ansible.builtin.copy module and use that to get the desired effect.
The following example does not rewrite the /etc/resolv.conf file if it already consists of the correct content:
- name: Idempotent approach with copy module
ansible.builtin.copy:
dest: /etc/resolv.conf
content: "nameserver 192.0.2.1\n"The ansible.builtin.copy module tests to see if the state has already been met, and if so, it makes no changes.
The ansible.builtin.shell module allows a lot of flexibility, but also requires more attention to ensure that it runs with idempotency.
You can run idempotent playbooks repeatedly to ensure systems are in a particular state without disrupting those systems if they already are.
The last part of this section investigates some variations of YAML or Ansible Playbook syntax that you might encounter.
Comments can also be used to aid readability. In YAML, everything to the right of the number sign (#) is a comment. If there is content to the left of the comment, precede the hash with a space.
# This is a YAML comment
some data # This is also a YAML comment
Strings in YAML do not normally need to be put in quotation marks even if the string contains no spaces. You can enclose strings in either double or single quotation marks.
this is a string
'this is another string'
"this is yet another a string"
You can write multiline strings in either of two ways. You can use the vertical bar (|) character to denote that newline characters within the string are to be preserved.
include_newlines: |
Example Company
123 Main Street
Atlanta, GA 30303You can also write multiline strings using the greater-than (>) character to indicate that newline characters are to be converted to spaces and that leading white spaces in the lines are to be removed. This method is often used to break long strings at space characters so that they can span multiple lines for better readability.
fold_newlines: >
This is an example
of a long string,
that will become
a single sentence once folded.You have seen collections of key-value pairs written as an indented block, as follows:
name: svcrole svcservice: httpd svcport: 80
Dictionaries can also be written in an inline block format enclosed in braces, as follows:
{name: svcrole, svcservice: httpd, svcport: 80}Avoid the inline block format because it is harder to read. However, there is at least one situation in which it is more commonly used. The use of roles is discussed later in this course. When a playbook includes a list of roles, it is more common to use this syntax to make it easier to distinguish roles included in a play from the variables being passed to a role.
You have also seen lists written with the normal single-dash syntax:
hosts:
- servera
- serverb
- servercLists also have an inline format enclosed in square braces, as follows:
hosts: [servera, serverb, serverc]
You should avoid this syntax because it is usually harder to read.
Some playbooks might use an earlier shorthand method to define tasks by putting the key-value pairs for the module on the same line as the module name. For example, you might see this syntax:
tasks:
- name: Shorthand form
ansible.builtin.service: name=httpd enabled=true state=startedNormally you would write the same task as follows:
tasks:
- name: Normal form
ansible.builtin.service:
name: httpd
enabled: true
state: startedYou should generally avoid the shorthand form and use the normal form.
The normal form has more lines, but it is easier to work with. The task's keywords are stacked vertically and are easier to differentiate. Your eyes can move straight down the play with less left-to-right motion, making it easier to read.
Also, the normal syntax is native YAML; the shorthand form is not. Syntax highlighting tools in text editors can help you more effectively if you use the normal format than if you use the shorthand format.
You might see this syntax in documentation and earlier playbooks from other people, and the syntax does still function.