订阅内容

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:

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
Sample home 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.


关于作者

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.

Read full bio
UI_Icon-Red_Hat-Close-A-Black-RGB

按频道浏览

automation icon

自动化

有关技术、团队和环境 IT 自动化的最新信息

AI icon

人工智能

平台更新使客户可以在任何地方运行人工智能工作负载

open hybrid cloud icon

开放混合云

了解我们如何利用混合云构建更灵活的未来

security icon

安全防护

有关我们如何跨环境和技术减少风险的最新信息

edge icon

边缘计算

简化边缘运维的平台更新

Infrastructure icon

基础架构

全球领先企业 Linux 平台的最新动态

application development icon

应用领域

我们针对最严峻的应用挑战的解决方案

Original series icon

原创节目

关于企业技术领域的创客和领导者们有趣的故事