Ansible: From Introduction to Abandonment (Part 12)

Node Environment

Management Node

  • • System Version: Rocky Linux release 8.10 (Green Obsidian), minimal installation
  • • Python Environment: python3.12
  • • Ansible-core Version: 2.15.13

For demonstration purposes, we are installing <span>ansible-core</span> (which includes only the basic modules). For normal use, you can install <span>ansible</span> (which includes some extended modules) for easier usage.

This installation is done via <span>pip</span>.

Managed Node

  • • System Version: Rocky Linux release 8.10 (Green Obsidian), minimal installation
  • • Python Environment: python3.6

The addresses of the three nodes are: <span>192.168.221.142</span>, <span>192.168.221.143</span>, <span>192.168.221.144</span>.

Ansible has requirements for the Python version. If the Ansible version is too high and the Python version on the managed node is too low, there may be cases of module execution failure.

https://docs.ansible.com/ansible/latest/reference_appendices/release_and_maintenance.html#ansible-core-target-node-python-support

Configuring the Basic Environment

# Install Python and two commonly used basic packages
[root@ansible-controller ~]# dnf install -y vim bash-completion python3.12 python3.12-pip sshpass

# Upgrade pip
[root@ansible-controller ~]# python3.12 -m pip install --upgrade pip

Installing Ansible

# Check available Ansible-core versions
[root@ansible-controller ~]# python3.12 -m pip index versions ansible-core
ansible-core (2.18.5)
Available versions: 2.18.5, 2.18.4, 2.18.3, 2.18.2, 2.18.1, 2.18.0, 2.17.11, 2.17.10, 2.17.9, 2.17.8, 2.17.7, 2.17.6, 2.17.5, 2.17.4, 2.17.3, 2.17.2, 2.17.1, 2.17.0, 2.16.14, 2.16.13, 2.16.12, 2.16.11, 2.16.10, 2.16.9, 2.16.8, 2.16.7, 2.16.6, 2.16.5, 2.16.4, 2.16.3, 2.16.2, 2.16.1, 2.16.0, 2.15.13, 2.15.12, 2.15.11, 2.15.10, 2.15.9, 2.15.8, 2.15.7, 2.15.6, 2.15.5, 2.15.4, 2.15.3, 2.15.2, 2.15.1, 2.15.0, 2.14.18, 2.14.17, 2.14.16, 2.14.15, 2.14.14, 2.14.13, 2.14.12, 2.14.11, 2.14.10, 2.14.9, 2.14.8, 2.14.7, 2.14.6, 2.14.5, 2.14.4, 2.14.3, 2.14.2, 2.14.1, 2.14.0, 2.13.13, 2.13.12, 2.13.11, 2.13.10, 2.13.9, 2.13.8, 2.13.7, 2.13.6, 2.13.5, 2.13.4, 2.13.3, 2.13.2, 2.13.1, 2.13.0, 2.12.10, 2.12.9, 2.12.8, 2.12.7, 2.12.6, 2.12.5, 2.12.4, 2.12.3, 2.12.2, 2.12.1, 2.12.0, 2.11.12, 2.11.11, 2.11.10, 2.11.9, 2.11.8, 2.11.7, 2.11.6, 2.11.5, 2.11.4, 2.11.3, 2.11.2, 2.11.1, 2.11.0

# Install Ansible-core
[root@ansible-controller ~]# python3.12 -m pip install ansible-core==2.15.13 argcomplete
...output omitted...

# Configure command auto-completion
[root@ansible-controller ansible]# activate-global-python-argcomplete --user
Adding shellcode to /root/.zshenv...
Added.
Adding shellcode to /root/.bash_completion...
Added.
Please restart your shell or source the installed file to activate it.
[root@ansible-controller ~]# exit
logout
# Reopen a command line

# Check the installed Ansible
[root@ansible-controller ~]# ansible --version
ansible [core 2.15.13]
  config file = /root/ansible_init/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/lib/python3.12/site-packages/ansible
  ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.12.8 (main, Dec 12 2024, 16:30:29) [GCC 8.5.0 20210514 (Red Hat 8.5.0-22)] (/usr/bin/python3.12)
  jinja version = 3.1.6
  libyaml = True

# You can see that there are only 70 basic modules
[root@ansible-controller ~]# ansible-doc -l | wc -l
74

Configuring the Ansible Initialization Environment

This section mainly configures the managed nodes to support running Ansible modules, primarily by installing Python and creating Ansible connection accounts.

Initializing the Managed Node Module Execution Environment

Create a temporary environment to install Python on the managed nodes and create Ansible connection accounts.

# Create ansible main directory
[root@ansible-controller ~]# mkdir ansible_init
[root@ansible-controller ~]# cd ansible_init/

# Create configuration file
[root@ansible-controller ansible_init]# cat ansible.cfg
[defaults]
inventory=./inventory
become=false
host_key_checking=False

# Configure the host inventory for initialization
[root@ansible-controller ansible_init]# cat inventory
master1 ansible_ssh_host=192.168.221.142 HOSTNAME=master1.example.com
worker1 ansible_ssh_host=192.168.221.143 HOSTNAME=worker1.example.com
worker2 ansible_ssh_host=192.168.221.144 HOSTNAME=worker2.example.com

[all:vars]
ansible_ssh_user=root
ansible_ssh_password=redhat

# Test
[root@ansible-controller ansible_init]# ansible all -m raw -a id
worker1 | CHANGED | rc=0 &gt;&gt;
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
Shared connection to 192.168.221.143 closed.

master1 | CHANGED | rc=0 &gt;&gt;
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
Shared connection to 192.168.221.142 closed.

worker2 | CHANGED | rc=0 &gt;&gt;
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
Shared connection to 192.168.221.144 closed.

# Install python3
[root@ansible-controller ansible_init]# ansible all -m raw -a 'dnf install -y python3'
...output omitted...

# Test module
[root@ansible-controller ansible_init]# ansible all -m ping
...output omitted...

Adding Ansible Management User

# Add Ansible user
[root@ansible-controller ~]# openssl passwd -6 -salt randomsalt redhat
$6$randomsalt$nyZMZWZT9mAsW3O4fAAX66nk/RagLSUr4vq921cGE/hsqskAzcOJvaF4uvHUFxYnT1vBB9tGbR7UhEAhuVWu/0
[root@ansible-controller ansible_init]# ansible all -m user \
    -a 'name=ansible uid=10000 groups=wheel append=true password=$6$randomsalt$nyZMZWZT9mAsW3O4fAAX66nk/RagLSUr4vq921cGE/hsqskAzcOJvaF4uvHUFxYnT1vBB9tGbR7UhEAhuVWu/0'
...output omitted...

Configuring the Ansible Automation Environment

This section describes the steps to configure the managed nodes after they have the Ansible module execution environment.

Creating Automation Environment Related Configurations

Create a new environment directory for host management.

# Create a new Ansible directory and enter it
[root@ansible-controller ansible_init]# cd
[root@ansible-controller ~]# mkdir ansible
[root@ansible-controller ~]# cd ansible

# Create Ansible configuration file
[root@ansible-controller ansible]# ansible-config init --disabled -t all &gt; ansible.cfg

# Modify the configuration file as needed
[root@ansible-controller ansible]# grep -Ev "^#|^$|^;" ansible.cfg
[defaults]
fact_caching=jsonfile
fact_caching_connection=./cache
inventory=./inventory
remote_user=ansible
host_key_checking=False
[privilege_escalation]
become=True
become_ask_pass=True
become_method=sudo
become_user=root
[persistent_connection]
[connection]
[colors]
[selinux]
[diff]
[galaxy]
[inventory]
[netconf_connection]
[paramiko_connection]
[jinja2]
[tags]
[runas_become_plugin]
[su_become_plugin]
[sudo_become_plugin]
[callback_tree]
[ssh_connection]
[winrm]
[inventory_plugins]
[inventory_plugin_script]
[inventory_plugin_yaml]
[url_lookup]
[powershell]
[vars_host_group_vars]

# Prepare host inventory
[root@ansible-controller ansible]# cat inventory
master1 ansible_ssh_host=192.168.221.142 HOSTNAME=master1.example.com
worker1 ansible_ssh_host=192.168.221.143 HOSTNAME=worker1.example.com
worker2 ansible_ssh_host=192.168.221.144 HOSTNAME=worker2.example.com

[all:vars]
ansible_ssh_user=ansible
ansible_ssh_password=redhat

Preparing an Initialization Configuration Playbook

  • • You can use <span>ansible-doc -l</span> command to see which modules are available
  • • You can use <span>ansible-doc <module_name></span> to view module documentation, and search for module usage templates by entering <span>/EXAMPLE</span>.

For example, searching for template content using the <span>ansible.builtin.dnf</span> module:

EXAMPLES:

- name: Install the latest version of Apache
  ansible.builtin.dnf:
    name: httpd
    state: latest

- name: Install Apache &gt;= 2.4
  ansible.builtin.dnf:
    name: httpd &gt;= 2.4
    state: present

- name: Install the latest version of Apache and MariaDB
  ansible.builtin.dnf:
    name:
      - httpd
      - mariadb-server
    state: latest

- name: Remove the Apache package
  ansible.builtin.dnf:
    name: httpd
    state: absent

- name: Install the latest version of Apache from the testing repo
  ansible.builtin.dnf:
    name: httpd
    enablerepo: testing
    state: present

- name: Upgrade all packages
  ansible.builtin.dnf:
    name: "*"
    state: latest

Playbook content:

[root@ansible-controller ansible]# cat init.yaml
---
- name: Initial configuration
  hosts: all
  tasks:
  - name: Installing System Packages
    ansible.builtin.dnf:
      name:
      - vim
      - bash-completion
      - python3-pip
      - epel-release
      state: present
  - name: Upgrading system packages
    ansible.builtin.dnf:
      name: '*'
      state: latest
  - name: Set hostname
    ansible.builtin.hostname:
      name: "{{ HOSTNAME }}"
  - name: Set up SSH to disable root login
    ansible.builtin.lineinfile:
      path: /etc/ssh/sshd_config
      regex: "^#?PermitRootLogin"
      line: "PermitRootLogin no"
    notify: restart sshd
  handlers:
  - name: Restart SSH Service
    ansible.builtin.systemd:
      name: sshd
      state: restarted
    listen: restart sshd
...output omitted...

This Playbook does four things:

  • • Installs some basic software packages
  • • Upgrades all system packages
  • • Sets the hostname
  • • Disables root login via SSH

Executing Automation Tasks

[root@ansible-controller ansible]# ansible-playbook init.yaml
BECOME password:
...output omitted...

# Check configuration
[root@ansible-controller ansible]# ansible all -m command -a "dnf upgrade"
BECOME password:
worker2 | CHANGED | rc=0 &gt;&gt;
Last metadata expiration check: 3:12:13 ago on Tue 20 May 2025 07:36:03 PM CST.
Dependencies resolved.
Nothing to do.
Complete!
worker1 | CHANGED | rc=0 &gt;&gt;
Last metadata expiration check: 3:10:43 ago on Tue 20 May 2025 07:37:33 PM CST.
Dependencies resolved.
Nothing to do.
Complete!
master1 | CHANGED | rc=0 &gt;&gt;
Last metadata expiration check: 0:18:26 ago on Tue 20 May 2025 10:29:50 PM CST.
Dependencies resolved.
Nothing to do.
Complete!

[root@ansible-controller ansible]# ansible all -m command -a 'grep -E "^#?PermitRootLogin no" /etc/ssh/sshd_config'
BECOME password:
master1 | CHANGED | rc=0 &gt;&gt;
PermitRootLogin no
worker1 | CHANGED | rc=0 &gt;&gt;
PermitRootLogin no
worker2 | CHANGED | rc=0 &gt;&gt;
PermitRootLogin no

[root@ansible-controller ansible]# ansible all -m command -a 'hostname'
BECOME password:
worker1 | CHANGED | rc=0 &gt;&gt;
worker1.example.com
worker2 | CHANGED | rc=0 &gt;&gt;
worker2.example.com
master1 | CHANGED | rc=0 &gt;&gt;
master1.example.com

Using Collections to Extend Ansible’s Functionality

Installing Collections

<span>ansible-core</span> only includes some core modules, and sometimes additional modules are needed to extend Ansible’s functionality. By installing collections, you can expand Ansible’s capabilities, for example, <span>ansible.posix.selinux</span> is used to manage SELinux.

# Download collection
[root@ansible-controller ansible]# ansible-galaxy collection install ansible.posix
Starting galaxy collection install process
Process install dependency map
Starting collection install process
Downloading https://galaxy.ansible.com/api/v3/plugin/ansible/content/published/collections/artifacts/ansible-posix-2.0.0.tar.gz to /root/.ansible/tmp/ansible-local-111554mi8iz9y/tmpjgw4dlat/ansible-posix-2.0.0-fc5mr3e5
Installing 'ansible.posix:2.0.0' to '/root/.ansible/collections/ansible_collections/ansible/posix'
ansible.posix:2.0.0 was installed successfully

Using Newly Installed Modules

# Check newly installed modules
[root@ansible-controller ansible]# ansible-doc -l | grep ansible.posix
ansible.posix.acl                      Set and retrieve file ACL informatio...
ansible.posix.at                       Schedule the execution of a command ...
ansible.posix.authorized_key           Adds or removes an SSH authorized ke...
ansible.posix.firewalld                Manage arbitrary ports/services with...
ansible.posix.firewalld_info           Gather information about firewalld
ansible.posix.mount                    Control active and configured mount ...
ansible.posix.patch                    Apply patch files using the GNU patc...
ansible.posix.rhel_facts               Facts module to set or override RHEL...
ansible.posix.rhel_rpm_ostree          Ensure packages exist in a RHEL for ...
ansible.posix.rpm_ostree_upgrade       Manage rpm-ostree upgrade transactio...
ansible.posix.seboolean                Toggles SELinux booleans
ansible.posix.selinux                  Change policy and state of SELinux
ansible.posix.synchronize              A wrapper around rsync to make commo...
ansible.posix.sysctl                   Manage entries in sysctl.conf

# View ansible.posix.selinux module documentation
[root@ansible-controller ansible]# ansible-doc ansible.posix.selinux
# Enter /EXAMPLE to search for usage templates

# Prepare a YAML file to configure SELinux based on the template
---
- name: Disable SELinux
  hosts: all
  gather_facts: false
  tasks:
  - name: Disable SELinux
    ansible.posix.selinux:
      policy: targeted
      state: permissive

# Execute Playbook
[root@ansible-controller ansible]# ansible-playbook selinux.yml

# Verify
[root@ansible-controller ansible]# ansible all -m command -a 'grep SELINUX= /etc/selinux/config'
BECOME password:
master1 | CHANGED | rc=0 &gt;&gt;
# SELINUX= can take one of these three values:
SELINUX=permissive
worker1 | CHANGED | rc=0 &gt;&gt;
# SELINUX= can take one of these three values:
SELINUX=permissive
worker2 | CHANGED | rc=0 &gt;&gt;
# SELINUX= can take one of these three values:
SELINUX=permissive

If an error occurs during execution, <span>The error was: ModuleNotFoundError: No module named 'selinux'</span>, it indicates that the managed host does not have the <span>python3-libselinux</span> package installed.

Rebooting Nodes and Verifying Functionality

[root@ansible-controller ansible]# ansible all -m reboot
BECOME password:
worker1 | CHANGED =&gt; {
    "changed": true,
    "elapsed": 14,
    "rebooted": true
}
worker2 | CHANGED =&gt; {
    "changed": true,
    "elapsed": 14,
    "rebooted": true
}
master1 | CHANGED =&gt; {
    "changed": true,
    "elapsed": 15,
    "rebooted": true
}

[root@ansible-controller ansible]# ssh [email protected]
[email protected]'s password:
Permission denied, please try again.
[email protected]'s password:

[root@ansible-controller ansible]# ssh [email protected]
[email protected]'s password:
Last login: Wed May 21 01:32:06 2025 from 192.168.221.141
[ansible@master1 ~]$

[ansible@master1 ~]$ hostname
master1.example.com

[ansible@master1 ~]$ sudo dnf upgrade
[sudo] password for ansible:
Last metadata expiration check: 0:19:34 ago on Wed 21 May 2025 01:14:37 AM CST.
Dependencies resolved.
Nothing to do.
Complete!

Leave a Comment