At home, I own some IT devices that run 24/7. To keep them up to date and install updates automatically, I leave the job to Ansible. In case you are completely new to Ansible, you'll find a good introduction in:
- Getting Started with Ansible
- Demystifying Ansible for Linux sysadmins
- Quick start guide to Ansible for Linux sysadmins
My home network includes the following devices:
- Two Raspberry Pi running Raspbian OS
- My KVM host running Debian 10 Buster
- Two RHEL 8 hosts
- A Synology DS213air
- Four RHEL hosts on an isolated network

The host marked by the red square is my Ansible Control Node. Its job is to update my Linux environment. I chose it because this host can reach all other hosts in the environment. Other hosts, for example, the Red Hat Enterprise Linux 7 (RHEL)-Ansible hosts, are only able to reach hosts inside the isolated network.
[ You might also like: How to create an Ansible Playbook ]
Prerequisites
On all my hosts, there is a user account that can use sudo
to run commands with root privileges. For convenience, I created an SSH key-pair and distributed the SSH public key to the hosts I would like to update using Ansible. If you need some help generating the SSH keys, check out Using ssh-keygen and sharing for key-based authentication in Linux by Tyler Carrigan.
In order to use the host RHEL8-Squid as an Ansible Control Node, I'll have to enable a repo that provides Ansible and install it:
$ sudo subscription-manager repos --enable=ansible-2.9-for-rhel-8-x86_64-rpms
$ sudo dnf -y install ansible
For other distributions, please see the official documentation.
The Ansible default configuration file is found at /etc/ansible/ansible.cfg
. Since this file is shipped and controlled by the RPM package, I like to create a custom config file at ~/.ansible.cfg
by just copying the original one and editing it. I’ve only made a few changes:
$ egrep -v "^$|^#|^\[" ~/.ansible.cfg
inventory = ~/ansible/hosts
private_key_file = /home/user/.ssh/ansible_id_rsa
As you can see, I've created an ansible
directory in my HOME
directory to store my host inventory file. I've also specified the path to the SSH private key Ansible should use to connect to the nodes on the network.
With this configuration, all the magic is controlled from my HOME
directory, and I won't need any root privileges on my Ansible Control Node to get the following job done.
Create a static inventory file
In this use case, I use a static inventory file by putting my hosts with their FQDN into the ~/ansible/hosts
file.
[special]
localhost
tower-pc.lan
[yum]
rhel7-ansible.private1
rhel7-t1.private1
rhel8-t1.private1
rpm-repo-r8.private1
podhost-r8-1.lan
[apt]
raspi-sht21.lan
pi-hole.lan
[ipkg]
diskstation.lan
As you can see, I grouped the hosts on my network by the package manager they use. This comes in handy when creating the playbook to update them. The group [special] contains my Ansible Control Node itself and my KVM hypervisor, where my Ansible Control Node runs.
For more information on Ansible's inventory, see: How to build your inventory.
Check the connectivity
Before I create the playbook that updates my hosts, I check if my Ansible Control Node RHEL8-Squid can connect to all of my hosts by using the following ad-hoc command:
$ ansible all -m ping -T 30
rhel7-t1.private1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
rhel7-ansible.private1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
rpm-repo-r8.private1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"ping": "pong"
}
rhel8-t1.private1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"ping": "pong"
}
podhost-r8-1.lan | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"ping": "pong"
}
[WARNING]: Platform linux on host tower-pc.lan is using the discovered Python
interpreter at /usr/bin/python, but future installation of another Python
interpreter could change this. See https://docs.ansible.com/ansible/2.9/referen
ce_appendices/interpreter_discovery.html for more information.
tower-pc.lan | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
[WARNING]: sftp transfer mechanism failed on [diskstation.lan]. Use
ANSIBLE_DEBUG=1 to see detailed information
[WARNING]: Platform linux on host diskstation.lan is using the discovered
Python interpreter at /usr/bin/python, but future installation of another
Python interpreter could change this. See https://docs.ansible.com/ansible/2.9/
reference_appendices/interpreter_discovery.html for more information.
diskstation.lan | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
[WARNING]: Platform linux on host pi-hole.lan is using the discovered Python
interpreter at /usr/bin/python, but future installation of another Python
interpreter could change this. See https://docs.ansible.com/ansible/2.9/referen
ce_appendices/interpreter_discovery.html for more information.
pi-hole.lan | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
[WARNING]: Platform linux on host raspi-sht21.lan is using the discovered
Python interpreter at /usr/bin/python, but future installation of another
Python interpreter could change this. See https://docs.ansible.com/ansible/2.9/
reference_appendices/interpreter_discovery.html for more information.
raspi-sht21.lan | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
The default timeout for an Ansible connection is 10 seconds. Because my Synology Diskstation is in standby mode most of the time, I'm using the option -T
to specify a timeout of 30 seconds to give it some time to wake up.
The warnings don't bother me at this time, so I move on to create the playbook.
In case you would like to know more about the ad-hoc commands, read Introduction to ad-hoc commands in the official docs.
The playbook
My playbook contains three plays. Each play runs a task on hosts belonging to a certain group in my inventory except [special]. In this simple example, each play connects to a group of hosts, updates them, and reboots them afterward in case updates were installed.
To determine if updates were installed, I register variables that store the return values of the tasks where I registered them. I use these to check whether the status of a task is changed. If it is, the system will be rebooted. Here's the playbook:
---
- hosts: yum
tasks:
- name: Update all installed packages using YUM module
yum:
name: '*'
state: latest
update_cache: yes
update_only: yes
register: yum_update_status
- name: Remove packates not needed anymore
yum:
autoremove: yes
- name: Reboot when packages were updated
reboot:
when: yum_update_status.changed
- hosts: apt
tasks:
- name: Update all installed packages using APT module
apt:
name: '*'
state: latest
update_cache: yes
only_upgrade: yes
register: apt_update_status
- name: Remove packages not needed anymore
apt:
autoremove: yes
- name: Reboot when packages were updated
reboot:
post_reboot_delay: 60
when: apt_update_status.changed
- hosts: ipkg
tasks:
- name: Update the Packages installed on Diskstation
command: /opt/bin/ipkg update && /opt/bin/ipkg upgrade
As you may have noticed, the Diskstation wasn't rebooted. That's because only userspace tools will be updated, and there is no need for a reboot. If a new OS version is available for the Diskstation, I will update it manually because there is no Ansible module for it yet. It's a similar story with the hosts tower-pc.lan and rhel8-squid.lan. I left them out of this playbook on purpose. My Ansible Control Node and my KVM hypervisor are important enough to me that I'll update them manually.
Here's a look at the first playbook run:
$ ansible-playbook -T 30 -b --ask-become-pass pkg_update.yml
BECOME password:
PLAY [yum] **************************************************************************************************************************************
TASK [Gathering Facts] **************************************************************************************************************************
ok: [podhost-r8-1.lan]
ok: [rhel7-t1.private1]
ok: [rhel8-t1.private1]
ok: [rpm-repo-r8.private1]
ok: [rhel7-ansible.private1]
TASK [Update all installed packages using YUM module] *******************************************************************************************
ok: [rhel8-t1.private1]
ok: [podhost-r8-1.lan]
ok: [rpm-repo-r8.private1]
ok: [rhel7-t1.private1]
ok: [rhel7-ansible.private1]
TASK [Remove packates not needed anymore] *******************************************************************************************************
ok: [rhel7-t1.private1]
ok: [rhel7-ansible.private1]
ok: [rhel8-t1.private1]
ok: [podhost-r8-1.lan]
ok: [rpm-repo-r8.private1]
TASK [Reboot when packages were updated] ********************************************************************************************************
skipping: [rhel7-ansible.private1]
skipping: [rhel7-t1.private1]
skipping: [rhel8-t1.private1]
skipping: [rpm-repo-r8.private1]
skipping: [podhost-r8-1.lan]
PLAY [apt] **************************************************************************************************************************************
TASK [Gathering Facts] **************************************************************************************************************************
ok: [pi-hole.lan]
ok: [raspi-sht21.lan]
TASK [Update all installed packages using APT module] *******************************************************************************************
changed: [pi-hole.lan]
changed: [raspi-sht21.lan]
TASK [Remove packages not needed anymore] *******************************************************************************************************
ok: [pi-hole.lan]
ok: [raspi-sht21.lan]
TASK [Reboot when packages were updated] ********************************************************************************************************
changed: [pi-hole.lan]
changed: [raspi-sht21.lan]
PLAY [ipkg] *************************************************************************************************************************************
TASK [Gathering Facts] **************************************************************************************************************************
ok: [diskstation.lan]
TASK [Update the Packages installed on Diskstation] *********************************************************************************************
changed: [diskstation.lan]
PLAY RECAP **************************************************************************************************************************************
diskstation.lan : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
pi-hole.lan : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
podhost-r8-1.lan : ok=3 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
raspi-sht21.lan : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
rhel7-ansible.private1 : ok=3 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
rhel7-t1.private1 : ok=3 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
rhel8-t1.private1 : ok=3 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
rpm-repo-r8.private1 : ok=3 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
As you can see, my RHEL machines were already up to date. Nothing to update or remove, and therefore no reboot required. My Raspberry Pis, on the other hand, had updates available and they were installed. Both devices were rebooted afterward. The Diskstation has its status changed, too. But be aware this is because I'm using the command
module, which returns changed every time it runs, regardless of whether something on your node changed.
[ A free guide from Red Hat: 5 steps to automate your business. ]
Wrap up
In this article, I showed you an easy but not very sophisticated example of how I keep my Linux devices at home up to date using Ansible automation. It shows you how to use the groups from your inventory in different plays of your playbook using some of the Ansible modules and a simple command.
About the author
Jörg has been a Sysadmin for over ten years now. His fields of operation include Virtualization (VMware), Linux System Administration and Automation (RHEL), Firewalling (Forcepoint), and Loadbalancing (F5). He is a member of the Red Hat Accelerators Community and author of his personal blog at https://www.my-it-brain.de.
Browse by channel
Automation
The latest on IT automation for tech, teams, and environments
Artificial intelligence
Updates on the platforms that free customers to run AI workloads anywhere
Open hybrid cloud
Explore how we build a more flexible future with hybrid cloud
Security
The latest on how we reduce risks across environments and technologies
Edge computing
Updates on the platforms that simplify operations at the edge
Infrastructure
The latest on the world’s leading enterprise Linux platform
Applications
Inside our solutions to the toughest application challenges
Original shows
Entertaining stories from the makers and leaders in enterprise tech
Products
- Red Hat Enterprise Linux
- Red Hat OpenShift
- Red Hat Ansible Automation Platform
- Cloud services
- See all products
Tools
- Training and certification
- My account
- Customer support
- Developer resources
- Find a partner
- Red Hat Ecosystem Catalog
- Red Hat value calculator
- Documentation
Try, buy, & sell
Communicate
About Red Hat
We’re the world’s leading provider of enterprise open source solutions—including Linux, cloud, container, and Kubernetes. We deliver hardened solutions that make it easier for enterprises to work across platforms and environments, from the core datacenter to the network edge.
Select a language
Red Hat legal and privacy links
- About Red Hat
- Jobs
- Events
- Locations
- Contact Red Hat
- Red Hat Blog
- Diversity, equity, and inclusion
- Cool Stuff Store
- Red Hat Summit