Bookmark this page

Chapter 9. Automating Linux Administration Tasks

Abstract

Goal Automate common Linux system administration tasks with Ansible.
Objectives
  • Subscribe systems, configure software channels and repositories, enable module streams, and manage RPM packages on managed hosts.

  • Manage Linux users and groups, configure SSH, and modify Sudo configuration on managed hosts.

  • Manage service startup, schedule processes with at, cron, and systemd, reboot managed hosts with reboot, and control the default boot target on managed hosts.

  • Partition storage devices, configure LVM, format partitions or logical volumes, mount file systems, and add swap spaces.

  • Configure network settings and name resolution on managed hosts, and collect network-related Ansible facts.

Sections
  • Managing Software and Subscriptions (and Guided Exercise)

  • Managing Users and Authentication (and Guided Exercise)

  • Managing the Boot Process and Scheduled Processes (and Guided Exercise)

  • Managing Storage (and Guided Exercise)

  • Managing Network Configuration (and Guided Exercise)

Lab
  • Automating Linux Administration Tasks

Managing Software and Subscriptions

Objectives

  • Subscribe systems, configure software channels and repositories, enable module streams, and manage RPM packages on managed hosts.

Managing Packages with Ansible

The ansible.builtin.dnf Ansible module uses dnf on the managed hosts to handle package operations. The following example is a playbook that installs the httpd package on the servera.lab.example.com managed host.

---
- name: Install the required packages on the web server
  hosts: servera.lab.example.com
  tasks:
    - name: Install the httpd packages
      ansible.builtin.dnf:
        name: httpd    1
        state: present 2

1

The name keyword specifies the name of the package to install.

2

The state keyword indicates the expected state of the package on the managed host:

present

Ansible installs the package if it is not already installed.

absent

Ansible removes the package if it is installed.

latest

Ansible updates the package if it is not already at the most recent available version. If the package is not installed, Ansible installs it.

The following table compares some uses of the ansible.builtin.dnf Ansible module with the equivalent dnf command.

Ansible taskDNF command
- name: Install httpd
  ansible.builtin.dnf:
    name: httpd
    state: present
dnf install httpd
- name: Install or upgrade httpd
  ansible.builtin.dnf:
    name: httpd
    state: latest
dnf upgrade httpd or dnf install httpd if the package is not yet installed.
- name: Upgrade all packages
  ansible.builtin.dnf:
    name: '*'
    state: latest
dnf upgrade
- name: Remove httpd
  ansible.builtin.dnf:
    name: httpd
    state: absent
dnf remove httpd
- name: Install Development Tools
  ansible.builtin.dnf:
    name: '@Development Tools' 1
    state: present

1

With the ansible.builtin.dnf Ansible module, you must prefix group names with the @ character. Remember that you can retrieve the list of groups with the dnf group list command.

dnf group install "Development Tools"
- name: Remove Development Tools
  ansible.builtin.dnf:
    name: '@Development Tools'
    state: absent
dnf group remove "Development Tools"
- name: Install perl DNF module
  ansible.builtin.dnf:
    name: '@perl:5.26/minimal' 1
    state: present

1

To manage a package module, prefix its name with the @ character. The syntax is the same as with the dnf command. For example, you can omit the profile part to use the default profile: @perl:5.26. Remember that you can list the available package modules with the dnf module list command.

dnf module install perl:5.26/minimal

Important

Red Hat Enterprise Linux 8 provides several package modules, but Red Hat Enterprise Linux 9.0 did not when it was initially released. Package modules for additional versions of software components are expected to become available in future minor releases of Red Hat Enterprise Linux.

Run the ansible-navigator doc ansible.builtin.dnf command for additional parameters and playbook examples.

Optimizing Multiple Package Installation

To operate on several packages, the name keyword accepts a list. The following example shows a playbook that installs three packages on servera.lab.example.com.

---
- name: Install the required packages on the web server
  hosts: servera.lab.example.com
  tasks:
    - name: Install the packages
      ansible.builtin.dnf:
        name:
          - httpd
          - mod_ssl
          - httpd-tools
        state: present

With this syntax, Ansible installs the packages in a single DNF transaction. This is equivalent to running the dnf install httpd mod_ssl httpd-tools command.

A commonly seen but less efficient and slower version of this task is to use a loop.

---
- name: Install the required packages on the web server
  hosts: servera.lab.example.com
  tasks:
    - name: Install the packages
      ansible.builtin.dnf:
        name: "{{ item }}""
        state: present
      loop:
        - httpd
        - mod_ssl
        - httpd-tools

Avoid using this method because it requires the module to perform three individual transactions; one for each package.

Gathering Facts about Installed Packages

The ansible.builtin.package_facts Ansible module collects the installed package details on managed hosts. It sets the ansible_facts['packages'] variable with the package details.

The following playbook calls the ansible.builtin.package_facts module, and the ansible.builtin.debug module to display the content of the ansible_facts['packages'] variable and the version of the installed NetworkManager package.

---
- name: Display installed packages
  hosts: servera.lab.example.com
  tasks:
    - name: Gather info on installed packages
      ansible.builtin.package_facts:
        manager: auto

    - name: List installed packages
      ansible.builtin.debug:
        var: ansible_facts['packages']

    - name: Display NetworkManager version
      ansible.builtin.debug:
        msg: "Version {{ ansible_facts['packages']['NetworkManager'][0]['version'] }}"
      when: "'NetworkManager' in ansible_facts['packages']"

When run, the playbook displays the package list and the version of the NetworkManager package:

[user@controlnode ~]$ ansible-navigator run -m stdout lspackages.yml

PLAY [Display installed packages] ******************************************

TASK [Gathering Facts] *****************************************************
ok: [servera.lab.example.com]

TASK [Gather info on installed packages] ***********************************
ok: [servera.lab.example.com]

TASK [List installed packages] *********************************************
ok: [servera.lab.example.com] => {
    "ansible_facts['packages']": {
        "ModemManager": [
            {
                "arch": "x86_64",
                "epoch": null,
                "name": "ModemManager",
                "release": "3.el9",
                "source": "rpm",
                "version": "1.18.2"
            }
        ],
...output omitted...
        "zstd": [
            {
                "arch": "x86_64",
                "epoch": null,
                "name": "zstd",
                "release": "2.el9",
                "source": "rpm",
                "version": "1.5.1"
            }
        ]
    }
}

TASK [Display NetworkManager version] **************************************
ok: [servera.lab.example.com] => {
    "msg": "Version 1.36.0"
}

PLAY RECAP *****************************************************************
servera.lab.example.com    : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Reviewing Alternative Modules to Manage Packages

For other package managers, Ansible usually provides a dedicated module. The ansible.builtin.apt module uses the APT package tool available on Debian and Ubuntu. The ansible.windows.win_package module can install software on Microsoft Windows systems.

The following playbook uses conditionals to select the appropriate module in an environment composed of Red Hat Enterprise Linux systems running major versions 7, 8, and 9.

---
- name: Install the required packages on the web servers
  hosts: webservers
  tasks:
    - name: Install httpd on RHEL 8 and 9
      ansible.builtin.dnf:
        name: httpd
        state: present
      when:
        - "ansible_facts['distribution'] == 'RedHat'"
        - "ansible_facts['distribution_major_version'] >= '8'"

    - name: Install httpd on RHEL 7 and earlier
      ansible.builtin.yum:
        name: httpd
        state: present
      when:
        - "ansible_facts['distribution'] == 'RedHat'"
        - "ansible_facts['distribution_major_version'] <= '7'"

As an alternative, the generic ansible.builtin.package module automatically detects and uses the package manager available on the managed hosts. With the ansible.builtin.package module, you can rewrite the previous playbook as follows:

---
- name: Install the required packages on the web servers
  hosts: webservers
  tasks:
    - name: Install httpd
      ansible.builtin.package:
        name: httpd
        state: present

However, the ansible.builtin.package module does not support all the features that the more specialized modules provide.

Also, operating systems often have different names for the packages they provide. For example, the package that installs the Apache HTTP Server is httpd on Red Hat Enterprise Linux and apache2 on Ubuntu.

In that situation, you still need a conditional for selecting the correct package name depending on the operating system of the managed host.

Registering and Managing Systems with Red Hat Subscription Management

To entitle your Red Hat Enterprise Linux systems to product subscriptions, one option is to use the community.general.redhat_subscription and community.general.rhsm_repository modules. These modules interface with the Red Hat Subscription Management tool on the managed hosts.

Important

The Subscription Management modules previously described are part of the community.general Ansible Content Collection, and are not supported by Red Hat at this time.

As an alternative, you could use the ansible.builtin.command module to call the command-line Subscription Management commands. To avoid accidental reregistration and allow the playbook to be idempotent, you need to determine what condition to use to skip subscription-related tasks that have already been completed.

Registering and Subscribing New Systems

The first two tasks you usually perform with the Red Hat Subscription Management tool is to register the new system and attach an available subscription.

Without Ansible, you perform these tasks with the subscription-manager command:

[user@host ~]$ subscription-manager register --username=yourusername \
> --password=yourpassword
[user@host ~]$ subscription-manager attach --pool=poolID

Remember that you can list the pools available to your account with the subscription-manager list --available command.

The community.general.redhat_subscription module performs the registration and the subscription in one task.

- name: Register and subscribe the system
  community.general.redhat_subscription:
    username: yourusername
    password: yourpassword
    pool_ids: poolID
    state: present

A state keyword set to present indicates to register and to subscribe the system. When it is set to absent, the module unregisters the system.

Enabling Red Hat Software Repositories

The next task after the subscription is to enable Red Hat software repositories on the new system.

Without Ansible, you would execute the subscription-manager command for that purpose:

[user@host ~]$ subscription-manager repos \
> --enable "rhel-9-for-x86_64-baseos-rpms" \
> --enable "rhel-9-for-x86_64-appstream-rpms"

Remember that you can list the available repositories with the subscription-manager repos --list command.

With Ansible, you can use the community.general.rhsm_repository module:

- name: Enable Red Hat repositories
  community.general.rhsm_repository:
    name:
      - rhel-9-for-x86_64-baseos-rpms
      - rhel-9-for-x86_64-appstream-rpms
    state: present

Configuring an RPM Package Repository

To enable support for a third-party Yum repository on a managed host, Ansible provides the ansible.builtin.yum_repository module.

Declaring an RPM Package Repository

When run, the following playbook declares a new Yum repository on servera.lab.example.com.

---
- name: Configure the company Yum/DNF repositories
  hosts: servera.lab.example.com
  tasks:
    - name: Ensure Example Repo exists
      ansible.builtin.yum_repository:
        file: example   1
        name: example-internal
        description: Example Inc. Internal YUM/DNF repo
        baseurl: http://materials.example.com/yum/repository/
        enabled: yes
        gpgcheck: yes   2
        state: present  3

1

The file keyword specifies the name of the file to create under the /etc/yum.repos.d/ directory. The module automatically adds the .repo extension to that name.

2

Typically, software providers digitally sign RPM packages using GPG keys. By setting the gpgcheck keyword to yes, the RPM system verifies package integrity by confirming that the package was signed by the appropriate GPG key. It refuses to install a package if the GPG signature does not match. Use the ansible.builtin.rpm_key Ansible module, described later in this section, to install the required GPG public key.

3

When you set the state keyword to present, Ansible creates or updates the .repo file. When state is set to absent, Ansible deletes the file.

The resulting /etc/yum.repos.d/example.repo file on servera.lab.example.com is as follows:

[example-internal]
baseurl = http://materials.example.com/yum/repository/
enabled = 1
gpgcheck = 1
name = Example Inc. Internal YUM repo

The ansible.builtin.yum_repository module exposes most of the repository configuration parameters as keywords. Run the ansible-navigator doc ansible.builtin.yum_repository command for additional parameters and playbook examples.

Note

Some third-party repositories provide the configuration file and the GPG public key as part of an RPM package that can be downloaded and installed using the dnf install command.

For example, the Extra Packages for Enterprise Linux (EPEL) project provides the https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm package that deploys the /etc/yum.repos.d/epel.repo configuration file.

For this repository, use the ansible.builtin.dnf module to install the EPEL package instead of the ansible.builtin.yum_repository module.

Importing an RPM GPG Key

When the gpgcheck keyword is set to yes in the ansible.builtin.yum_repository module, you also need to install the GPG key on the managed host. The ansible.builtin.rpm_key module in the following example deploys the GPG public key hosted on a remote web server to the servera.lab.example.com managed host.

---
- name: Configure the company Yum/DNF repositories
  hosts: servera.lab.example.com
  tasks:
    - name: Deploy the GPG public key
      ansible.builtin.rpm_key:
        key: http://materials.example.com/yum/repository/RPM-GPG-KEY-example
        state: present

    - name: Ensure Example Repo exists
      ansible.builtin.yum_repository:
        file: example
        name: example-internal
        description: Example Inc. Internal YUM/DNF repo
        baseurl: http://materials.example.com/yum/repository/
        enabled: yes
        gpgcheck: yes
        state: present
Revision: rh294-9.0-c95c7de