Updating five Linux guests manually is not difficult. Updating them consistently, recording failures, and rebooting only the machines that need it is where the routine starts to drift. A small Ansible playbook can make the maintenance window repeatable without turning it into a large automation project.
The following example targets Debian-family systems. Test it on a disposable guest and adapt the inventory groups to the environment.
Use a small inventory #
Keep infrastructure roles visible:
1[linux_guests]
2dns-a ansible_host=192.0.2.120
3monitoring ansible_host=192.0.2.130
4wiki ansible_host=192.0.2.140
5
6[linux_guests:vars]
7ansible_user=labadmin
Verify access before changing anything:
1ansible linux_guests -i inventory.ini -m ansible.builtin.ping
Ansible's ping module checks that it can connect and run Python on the managed host; it is not an ICMP echo test.
Update, detect, and reboot #
The playbook updates the package cache, performs the distribution upgrade, and checks Debian's reboot marker:
1---
2- name: Patch Linux guests
3 hosts: linux_guests
4 become: true
5 serial: 1
6
7 tasks:
8 - name: Update package cache and installed packages
9 ansible.builtin.apt:
10 update_cache: true
11 cache_valid_time: 3600
12 upgrade: dist
13
14 - name: Check whether a reboot is required
15 ansible.builtin.stat:
16 path: /var/run/reboot-required
17 register: reboot_required
18
19 - name: Reboot when required
20 ansible.builtin.reboot:
21 msg: Rebooting after scheduled package maintenance
22 reboot_timeout: 900
23 when: reboot_required.stat.exists
serial: 1 processes one host at a time. That is deliberately conservative for
services with redundant instances: DNS should remain available while one guest
reboots. It does not create redundancy where none exists, so order standalone
dependencies explicitly or place them in separate plays.
Use the fully qualified module names. They make the source of each module clear and avoid ambiguity when collections add similarly named modules.
Preview and limit the run #
Check mode is useful, but package managers cannot predict every maintainer script or dependency change perfectly. Treat it as a preview, not a guarantee:
1ansible-playbook -i inventory.ini patch.yml --check --diff
Start the real run against one non-critical host:
1ansible-playbook -i inventory.ini patch.yml --limit monitoring
After that succeeds, run the intended group. Watch both Ansible output and the service-level checks. A successful SSH reconnect proves that the operating system returned, not that DNS answers correctly or that the wiki can reach its database.
Add explicit preflight checks #
Package automation should stop early when the environment is not ready. A simple pre-task can require a recent backup marker or verify available space. The exact threshold belongs to the workload, but the failure should be clear:
1 pre_tasks:
2 - name: Collect filesystem information
3 ansible.builtin.setup:
4 gather_subset:
5 - mounts
6
7 - name: Require free space on the root filesystem
8 ansible.builtin.assert:
9 that:
10 - item.size_available > 1073741824
11 fail_msg: More than 1 GiB must be free on the root filesystem
12 loop: "{{ ansible_mounts }}"
13 when: item.mount == '/'
Facts and thresholds vary across platforms, so test this logic against the
supported guest images. Do not add ignore_errors merely to keep the run moving;
a preflight failure is doing its job.
Run service checks after reboot #
Put the cheapest meaningful check near the service it protects. For a web application, an HTTP health endpoint is more informative than a process list:
1 - name: Wait for the internal web service
2 ansible.builtin.uri:
3 url: http://127.0.0.1:8080/health
4 status_code: 200
5 register: health
6 retries: 12
7 delay: 5
8 until: health.status == 200
Do not expose a new unauthenticated health endpoint solely for Ansible. Use an existing local check, and keep sensitive response content out of logs.
Keep policy outside the playbook #
Automation should not silently decide when maintenance is allowed. Keep the window, backup prerequisite, notification, and rollback decision in a short runbook. The playbook performs the repeatable mechanics; the operator still decides whether the environment is ready.
Patch automation earns trust through boring runs. Begin with a few guests, preserve readable output, and add complexity only after the simple path has failed in a way that complexity would actually solve.
Keep it understandable.