1

This is my first time posting here, and I’m quite excited.

I’ve written a playbook and would like to know whether it achieves the intended results fully, and if there’s anything I should improve or optimize.

What I want this playbook to do:

  • Upgrade the current version of Ubuntu to the latest point release within the same version series. For example: If the system is running Ubuntu 22.04, it should be updated to 22.04.5. If it’s on 24.04.1, it should become 24.04.3, and so on.

  • Ensure that none of my existing configurations are lost or overwritten during the update process. Sometimes during upgrades, the system may prompt to keep or replace config files — I want to make sure all existing configurations are preserved automatically.

  • Finally, if a reboot is required after the update, the system should automatically reboot.

I’ve tested the playbook a few times and it seems to work, but I’d like to be fully confident that it’s safe and does exactly what I intend 🙂.

Here is the playbook I wrote:

--- - name: Update Ubuntu Servers hosts: nesus become: yes gather_facts: yes tasks: - name: Display current version debug: msg: "Current version: {{ ansible_distribution }} {{ ansible_distribution_version }}" - name: Update APT cache apt: update_cache: yes cache_valid_time: 0 - name: Check and terminate any running APT processes shell: | pkill -9 apt-get || true pkill -9 apt || true pkill -9 dpkg || true sleep 2 ignore_errors: yes - name: Remove APT lock files shell: | rm -f /var/lib/dpkg/lock-frontend rm -f /var/lib/dpkg/lock rm -f /var/cache/apt/archives/lock rm -f /var/lib/apt/lists/lock ignore_errors: yes - name: Check for available updates command: apt list --upgradable register: upgradable_packages changed_when: false - name: Show number of available updates debug: msg: "{{ upgradable_packages.stdout_lines | length - 1 }} packages are available for update" when: upgradable_packages.stdout_lines | length > 1 - name: Upgrade packages while preserving existing configurations apt: upgrade: dist update_cache: yes autoremove: yes autoclean: yes force_apt_get: yes dpkg_options: 'force-confold,force-confdef' environment: DEBIAN_FRONTEND: noninteractive register: apt_upgrade when: upgradable_packages.stdout_lines | length > 1 ignore_errors: yes - name: Update completed debug: msg: "Update completed successfully!" when: apt_upgrade.changed | default(false) - name: No updates required debug: msg: "System is already up-to-date." when: upgradable_packages.stdout_lines | length <= 1 - name: Show updated version debug: msg: "Updated version: {{ ansible_distribution }} {{ ansible_distribution_version }}" when: apt_upgrade.changed | default(false) - name: Check if a reboot is required stat: path: /var/run/reboot-required register: reboot_required - name: Reboot required notice debug: msg: "System reboot is required!" when: reboot_required.stat.exists 
3
  • 4
    Blindly kill -9-ing any running apt processes strikes me as a spectacularly bad idea. What if you're interrupting an upgrade process halfway through installing a core library ? Commented Oct 9 at 4:31
  • @Shadur-don't-feed-the-AI I see your point now, thank you very much for the clarification. I’ll definitely test it and get back to you with the results. Commented Oct 9 at 6:19
  • In general, it is better if your script easy exit on any error. Goal is not to cover the errors, you r goal should be that they do not happen. Commented Nov 4 at 4:39

1 Answer 1

2

Problem #1: IaC, idempontence

I see a conceptional mistake in your playbook. It makes your world only more complex.

What you have written, it could be more quickly replicated with a shell script.

Ansible is an IaC system. IaC means infrastructure-as-code. Part of the concept is that your playbooks are not scripts. Your playbooks are only defining, how your system should look after you have runned them.

Your playbook must have idempontent operations, meaning that if you execute them multiple times, they will get the same result as if you would do them first time.

For example, guaranteeing that the package kiscica is installed on the system, that is an idempotent operation. Why? Because if you execute it first time, then it installs kiscica if it was not installed yet. If you execute it second, third times, nothing will happen.

Note the essential difference between the command to change something, and between a description which shows, how the system should look.

The Ansible playbook is not a script. The ansible playbook is a definition of an end state of a system.

Rule of thumb is that if you execute an ansible playbook a second time, it should be all green.

Increasing a distribution version is not an idempotent operation. Upgrading it to version X is.

Problem #2: Effectivity

You could have written this all in a shell script. It had been shorter, simpler, more effective. You do not really need Ansible for that.

Problem #3: You are about to destroy your package database

Your dpkg, apt locks are there because something is using them. Their goal is exactly to prevent what you want to reach. If you delete the lock, and someone is actually working on the package DB, you will have, for example, installed packages without being visible in the package list.

By the way, the usual cause of the locked dpkg files is the wonderful idea of the packagekit service, which is running behind your back and periodically lock the database. You are the sysadm, you need to be able to turn it off beautifully (spiler: systemctl stop packagekitd or apt-get --purge remove packagekitd, latter might have unintended side effects).

You can also use the lsof and pstree commands to find out, who is using a file (incl. lock file), and where is it running in the process tree.

Problem #4: dist-upgrade is a wild thing

You can do it on an automatized way, but do it only if you systems are really well maintained.

Problem #5: Green-yellow-red

If a task execution did not change anything on the system, it should result a green state. If it changed something, it should be yellow. If there was a problem, it should be red.

Ordinary shell commands from ansible can not do this. You will need to develop correct script, giving back this information, for example in their exit code, and then instruct ansible to interpret this exit code well. You do this with a lot of || true and with many ignore_errors: yes. So it will be always green, even if it has changed things, and if there was an error, you won't see it.


Little deviations are possible from the IaC concept. For example, I find quite often backup solutions written as ansible playbook. But using Ansible instead shellscript leads to that you have a monstrous, slow, buggy and hardly manageable shellscript.

You can see all the Ansible tasks: all (or nearly all) of them are idempotent. The essential difference between, for example, a sed/awk command and an ansible builtin.lineinfile task is: the first describes a change. The second describes an end state.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.