Exercise 1.9 - System Roles with Ansible

Return to Workshop

Exercise Description

RHEL 7.4 introduced RHEL System Roles, a collection of Ansible roles and modules that provide a stable and consistent configuration interface for remote management of Red Hat Enterprise Linux. These System Roles are applicable to RHEL 6.10 and above, easing management of 3 generations of RHEL. The effort is based on development of the Linux System Roles project (link: https://linux-system-roles.github.io/upstream). The following roles are provided and supported as follows:

There are five roles available on RHEL 8:

  • selinux

  • kdump (kernel crash dump)

  • network

  • timesync

  • storage

For further information, see the Ansible Quick-Start Guide for Sysadmins (https://www.redhat.com/en/blog/system-administrators-guide-getting-started-ansible-fast) or Quick Start Video (https://www.ansible.com/resources/videos/quick-start-video) for help learning how to use Ansible.

Section 1: Installing RHEL System Roles and Ansible

Step 1: Install required packages

We need to install two packages RPM packages to use system roles: ansible and rhel-system-roles. Use subscription-manager to list the Ansible Engine repositories available. Note that the generic "2" repository will always provide the latest release of the 2.X stream as opposed to configuring a more specific version such as 2.8.

sudo subscription-manager refresh
sudo subscription-manager repos --list | grep ansible

Now persistently enable the Ansible Engine repository for RHEL 8 using Red Hat Subscription Manager:

sudo subscription-manager repos --enable ansible-2-for-rhel-8-x86_64-rpms

And install the required packages:

sudo yum install rhel-system-roles ansible

Step 2: Documentation

The rhel-system-roles package will install by default to the following locations where SUBSYSTEM is the name of the subsystem that contains the individual role manages. Examples may include network, timesync, or other subsystems as they become supported. Each subsystem role will include a README file which documents how to use the role and supported parameter values, as well as the matching README in the linux-system-roles Ansible Galaxy landing space (link: https://galaxy.ansible.com/linux-system-roles/network)

Documentation

/usr/share/doc/rhel-system-roles/SUBSYSTEM/

Ansible Roles

/usr/share/ansible/roles/rhel-system-roles.SUBSYSTEM/

Section 2: Example Usage of the rhel-system-roles.network role

This example assumes the following:

  • Generally, Ansible is not installed on every system, but rather on a single system designated as the Ansible management or control node who’s purpose is to manage other systems via Ansible.

  • Since we’re running Ansible on the same instance we’re changing, the target of the Ansible playbook is localhost.

  • We’ve added a 2nd network interface to our test system, eth1.

Step 1:

Take a look at the networking playbook that we have provided:

cat example-network-playbook.yml
---
- hosts: localhost
  become: yes
  vars:
    network_connections:
      - name: DBnic
        state: up
        type: ethernet
        interface_name: eth1
        autoconnect: yes
        ip:
          dhcp4: no
          auto6: no
          address:
           - "10.10.10.{{ 254 | random( start=2) }}/24"
  roles:
    - role: rhel-system-roles.network

As you can probably tell, this playbook creates a network interface called DBnic, with an IPv4 address of 10.10.10.<something>, and brings it up. This is just a simple example of what can be done with the system network role.

Step 2:

Now let’s test access to our localhost using the Ansible ad-hoc command:

ansible localhost -m ping
localhost | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

Step 3:

Query the Ansible Facts for this system to see the network configuration:

ansible localhost -m setup -a 'gather_subset=network filter=ansible_interfaces'
localhost | SUCCESS => {
    "ansible_facts": {
        "ansible_interfaces": [
            "lo",
            "eth1",
            "eth0"
        ]
    },
    "changed": false
}

Step 4:

Examine the current configuration of eth1 using the ip and nmcli commands:

ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq_codel state UP group default qlen 1000
    link/ether 06:62:b5:81:e3:65 brd ff:ff:ff:ff:ff:ff
    inet 10.10.0.63/24 brd 10.10.0.255 scope global dynamic noprefixroute eth0
       valid_lft 2055sec preferred_lft 2055sec
    inet6 fe80::462:b5ff:fe81:e365/64 scope link
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq_codel state UP group default qlen 1000
    link/ether 06:7d:2c:e9:2f:01 brd ff:ff:ff:ff:ff:ff
    inet 10.10.0.135/24 brd 10.10.0.255 scope global dynamic noprefixroute eth1
       valid_lft 3588sec preferred_lft 3588sec
    inet6 fe80::6cb5:e657:5c52:e6d1/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
nmcli con
NAME                UUID                                  TYPE      DEVICE
System eth0         5fb06bd0-0bb0-7ffb-45f1-d6edd65f3e03  ethernet  eth0
Wired connection 1  9738a5c6-39dd-3515-aa1c-895f763851a6  ethernet  eth1
ens3                50e9a523-3280-4238-a07b-dbfd7d335273  ethernet  --

Step 5:

Now let’s run our playbook to create a new connection profile called DBnic, turn off DHCP, and assign a static ip address:

ansible-playbook example-network-playbook.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'


PLAY [localhost] ******************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************
ok: [localhost]

TASK [rhel-system-roles.network : Check which services are running] ***************************************************
ok: [localhost]

TASK [rhel-system-roles.network : Check which packages are installed] *************************************************
ok: [localhost]

TASK [rhel-system-roles.network : Print network provider] *************************************************************
ok: [localhost] => {
    "msg": "Using network provider: nm"
}

TASK [rhel-system-roles.network : Install packages] *******************************************************************
skipping: [localhost]

TASK [rhel-system-roles.network : Enable and start NetworkManager] ****************************************************
ok: [localhost]

TASK [rhel-system-roles.network : Enable network service] *************************************************************
skipping: [localhost]

TASK [rhel-system-roles.network : Ensure initscripts network file dependency is present] ******************************
skipping: [localhost]

TASK [rhel-system-roles.network : Configure networking connection profiles] *******************************************
[WARNING]: [003] <info>  #0, state:up persistent_state:present, 'DBnic': connection DBnic,
3b54603b-c603-46b9-9bd9-e6fc295e7a11 already up to date

[WARNING]: [004] <info>  #0, state:up persistent_state:present, 'DBnic': up connection DBnic,
3b54603b-c603-46b9-9bd9-e6fc295e7a11 (not-active)

changed: [localhost]

TASK [rhel-system-roles.network : Re-test connectivity] ***************************************************************
ok: [localhost]

PLAY RECAP ************************************************************************************************************
localhost                  : ok=7    changed=1    unreachable=0    failed=0    skipped=3    rescued=0    ignored=0

Step 6:

Now let’s see how the Ansible playbook changed our network configuration:

ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc fq_codel state UP group default qlen 1000
    link/ether 06:62:b5:81:e3:65 brd ff:ff:ff:ff:ff:ff
    inet 10.10.0.63/24 brd 10.10.0.255 scope global dynamic noprefixroute eth0
       valid_lft 3522sec preferred_lft 3522sec
    inet6 fe80::462:b5ff:fe81:e365/64 scope link
       valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 06:7d:2c:e9:2f:01 brd ff:ff:ff:ff:ff:ff
    inet 10.10.10.10/24 brd 10.10.10.255 scope global noprefixroute eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::2bcc:cea2:c7fb:6bba/64 scope link noprefixroute
       valid_lft forever preferred_lft forever
nmcli con
NAME                UUID                                  TYPE      DEVICE
System eth0         5fb06bd0-0bb0-7ffb-45f1-d6edd65f3e03  ethernet  eth0
DBnic               3b54603b-c603-46b9-9bd9-e6fc295e7a11  ethernet  eth1
ens3                50e9a523-3280-4238-a07b-dbfd7d335273  ethernet  --
Wired connection 1  9738a5c6-39dd-3515-aa1c-895f763851a6  ethernet  --

Summary - What We’ve Learned

Linux System Roles based on Ansible playbooks make it easy and consistent to enable specific services and configurations on your RHEL hosts, and across many versions. We experimented with the network system role in this exercise, but storage, kdump (kernel crash dump), selinux, and timesync are also available.

Return to Workshop