Chapter 3: Implementing an Ansible Playbook

Managing Ansible configuration files

Two small files in your project directory shape how every command behaves: ansible.cfg tells Ansible where the inventory is, how to log in and how to become root, and ansible-navigator.yml tells navigator which execution environment to use and how to run.

Two files per project

Configures Ansible itself: inventory location, remote user, connection options, privilege escalation. Read by every Ansible tool.

Configures ansible-navigator: which execution environment image, when to pull it, whether to write playbook artifacts, default mode.

Writing ansible.cfg

The file is made of sections, each holding key = value settings. Section names are in square brackets. For basic work you need two sections:

[defaults]
inventory = ./inventory
remote_user = user
ask_pass = false

[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false
DirectiveWhat it does
inventoryPath to the inventory file (or a directory of inventory files and scripts).
remote_userUser to log in as on managed hosts. If unset, the current user's name is used. Inside an execution environment run by ansible-navigator, that is always root.
ask_passPrompt for an SSH password? false is the default and is right when you use SSH keys.
becomeSwitch user on the managed host (typically to root) after connecting? Plays can override this.
become_methodHow to switch users. sudo is the default; su is an alternative.
become_userUser to switch to. Defaults to root.
become_ask_passPrompt for the become_method password? Defaults to false.

Here is how those settings line up along the path from your terminal to a task running as root:

Connecting and escalating privileges: Ansible logs in as the remote user with an SSH key, then uses sudo to become root before running the task.

Which configuration file wins?

Ansible looks for its configuration in several places, uses the first file it finds, and ignores the rest. Settings are not merged across files. Toggle the files below to see which one Ansible would use.

Checking the active settings

ansible-navigator config shows every setting, its default, where the current value came from, and the value in effect:

  Name                        Default  Source                               Current
...output omitted...
44│Default ask pass           True     default                              False
45│Default ask vault pass     True     default                              False
46│Default become             False    /home/student/project/ansible.cfg    True
47│Default become ask pass    False    /home/student/project/ansible.cfg    True
...output omitted...
50│Default become method      False    /home/student/project/ansible.cfg    sudo
51│Default become user        False    /home/student/project/ansible.cfg    root

Read the Default column carefully: True means "this setting is still at its default value", not that the setting itself is true. Here become and become_ask_pass were set in the project's ansible.cfg (Default is False, Source is the file, Current is True). Press Esc or type :q to leave.

Configuring automation content navigator

Navigator reads its own settings file, in YAML (.yml or .yaml) or JSON. It uses the first one it finds:

Like ansible.cfg, each project can have its own navigator settings. A typical file:

ansible-navigator:
  execution-environment:
    image: utility.lab.example.com/ee-supported-rhel8:latest
    pull:
      policy: missing
  playbook-artifact:
    enable: false

At home there is no image to name. The file from section 1.6 switches the execution environment off instead, so navigator runs workstation's own ansible-core:

---
ansible-navigator:
  mode: stdout
  playbook-artifact:
    enable: false
  execution-environment:
    enabled: false

A project-level ansible-navigator.yml replaces this file rather than adding to it. If an exercise has you create one, keep the execution-environment: enabled: false lines in it.

Configuring connections

Ansible needs to know several things to reach its managed hosts:

Inventory location

[defaults]
inventory = ./inventory

The path can point at a single static file or at a directory holding several inventory files and dynamic inventory scripts.

SSH key authentication

By default Ansible logs in as the same user name as the person running it; remote_user changes that. If the local user has an SSH private key that the remote user trusts, Ansible logs in automatically. That is how you should set things up.

[user@controlnode ~]$ ssh-keygen

ssh-copy-id also records the host key in your ~/.ssh/known_hosts.

[user@controlnode ~]$ ssh-copy-id root@web1.example.com
The authenticity of host 'web1.example.com (192.0.2.181)' can't be established.
ECDSA key fingerprint is 70:9c:03:cd:de:ba:2f:11:98:fa:a0:b3:7c:40:86:4b.
Are you sure you want to continue connecting (yes/no)? yes
...output omitted...
Number of key(s) added: 1
[user@controlnode ~]$ ssh root@web1.example.com

If you must use passwords

Password-based SSH works, but ansible-navigator then needs playbook artifacts disabled so it can prompt you:

ansible-navigator:
  playbook-artifact:
    enable: false

Then run with -m stdout and --ask-pass (or set ask_pass = true in ansible.cfg to always be prompted):

[user@controlnode ~]$ ansible-navigator run -m stdout --ask-pass site.yml

Escalating privileges

For security and auditing, you often connect as an unprivileged user and then escalate to root. That is configured in [privilege_escalation]:

On RHEL 8 and 9, members of the wheel group can use sudo after typing their password. To let an automation user use sudo without a password, drop a file into /etc/sudoers.d/ (owned by root, mode 0400):

## password-less sudo for Ansible user
someuser ALL=(ALL) NOPASSWD:ALL

With key-based SSH as someuser and password-less sudo, the whole configuration becomes:

[defaults]
inventory = ./inventory
remote_user = someuser
ask_pass = false

[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false

Comments

Both files accept # at the start of a line as a comment. ansible.cfg also accepts ;, which comments out everything to its right.

# The whole line is a comment
[defaults]
inventory = ./inventory   ; the rest of this line is a comment

Takeaway

Write an ansible.cfg with [defaults] and [privilege_escalation] sections. Use the chapter lab to check this on a real host.