Compare commits
4 Commits
ce1c7e36bb
...
preview
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57768b598f | ||
|
|
2fac832345 | ||
|
|
bd6934ee8e | ||
| e41c25b64f |
@@ -194,7 +194,10 @@ jobs:
|
||||
steps:
|
||||
- name: Remove Old Docker Image
|
||||
run: |
|
||||
docker image rm $(docker image ls ${DOCKER_IMAGE} 2> /dev/null | awk '$NF != "U" && NR>1 {print $2}')
|
||||
IMAGE_IDS=$(docker image ls "${DOCKER_IMAGE}" 2>/dev/null | awk '$NF != "U" && NR>1 {print $2}')
|
||||
if [ -n "$IMAGE_IDS" ]; then
|
||||
docker image rm $IMAGE_IDS
|
||||
fi
|
||||
|
||||
Notify:
|
||||
needs: [Check-Rebuild, Build, Deploy-Staging, Test-Staging, Merge, Deploy-Production, Test-Production, Clean]
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 157 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
311
content/post/20-automating-proxmox-update-ansible/index.fr.md
Normal file
311
content/post/20-automating-proxmox-update-ansible/index.fr.md
Normal file
@@ -0,0 +1,311 @@
|
||||
---
|
||||
slug: automating-proxmox-update-ansible
|
||||
title: Automatiser les mises à jour de Proxmox VE avec Ansible
|
||||
description: Automatisez les mises à jour d’un cluster Proxmox VE avec Ansible, Semaphore UI et Ntfy, incluant les vérifications Ceph, les redémarrages progressifs et les rapports.
|
||||
date: 2026-06-09
|
||||
draft: false
|
||||
tags:
|
||||
- proxmox
|
||||
- ansible
|
||||
- semaphore-ui
|
||||
- ntfy
|
||||
categories:
|
||||
- homelab
|
||||
---
|
||||
## Intro
|
||||
|
||||
Dans mon homelab, les mises à jour font partie de ces choses faciles à repousser.
|
||||
|
||||
Pas parce qu’elles sont compliquées, mais parce qu’elles sont manuelles. Je dois me connecter au bon système, vérifier l’état, appliquer les mises à jour, redémarrer si nécessaire, vérifier que tout revient correctement, puis répéter le même processus pour le composant suivant.
|
||||
|
||||
Et comme c’est manuel, je le garde généralement pour plus tard.
|
||||
|
||||
Quand Proxmox VE 9.1 est sorti, je voulais déjà mettre à jour mon cluster, mais pas manuellement. Puis Proxmox VE 9.2 est devenu disponible il y a quelques jours, et je n’avais toujours pas construit de processus propre autour de cela. C’était un bon déclencheur pour enfin commencer à automatiser les mises à jour des parties importantes de mon homelab.
|
||||
|
||||
L’objectif plus large est de simplifier et d’automatiser le patching de plusieurs composants clés :
|
||||
|
||||
- Proxmox VE
|
||||
- OPNsense
|
||||
- TrueNAS
|
||||
|
||||
J’ai décidé de commencer par Proxmox parce qu’il est central dans le lab, et parce qu’un workflow de mise à jour progressive est un bon candidat pour l’automatisation.
|
||||
|
||||
---
|
||||
## Les Outils Utilisés
|
||||
|
||||
Le processus de mise à jour est construit autour de quelques composants que j’utilise déjà dans le lab.
|
||||
|
||||
[Proxmox VE](https://www.proxmox.com/en/proxmox-virtual-environment/overview) est ma plateforme de virtualisation. Le cluster utilise aussi Ceph, donc avant de toucher à un nœud, je veux m’assurer que le cluster est en bonne santé et que Ceph remonte `HEALTH_OK`.
|
||||
|
||||
[Ansible](https://docs.ansible.com/) est utilisé pour décrire le workflow de mise à jour sous forme de playbook.
|
||||
|
||||
[Semaphore UI](https://semaphoreui.com/) est utilisé pour exécuter le playbook depuis une interface web et le planifier.
|
||||
|
||||
[Ntfy](https://ntfy.sh/) est utilisé pour les notifications. Si les mises à jour sont planifiées, j’ai besoin de savoir quand quelque chose se passe, surtout si le cluster n’est pas prêt ou si une mise à jour échoue.
|
||||
|
||||
---
|
||||
## Création d’un Topic Ntfy Dédié
|
||||
|
||||
Avant de planifier quoi que ce soit, je voulais un canal de notification dédié au homelab.
|
||||
|
||||
J’ai créé un topic `homelab` dans Ntfy et un utilisateur dédié nommé `semaphore` avec un accès en écriture seule à ce topic.
|
||||
|
||||
```bash
|
||||
ntfy user add semaphore
|
||||
ntfy access semaphore homelab wo
|
||||
```
|
||||
|
||||
L’idée est que Semaphore a uniquement besoin de publier des messages. Il n’a pas besoin d’un accès en lecture.
|
||||
|
||||
J’ai aussi ajouté le topic sur mon téléphone mobile afin de pouvoir recevoir des notifications lorsque l’automatisation s’exécute.
|
||||
|
||||
Dans Semaphore, j’ai créé un groupe de variables nommé `Ntfy Homelab` pour stocker les valeurs nécessaires aux playbooks :
|
||||
|
||||
- `ntfy_url`
|
||||
- `ntfy_topic`
|
||||
- `ntfy_user`
|
||||
- `NTFY_PASSWORD`
|
||||
|
||||
Le mot de passe est stocké comme variable d’environnement dans l’onglet `Secrets`.
|
||||
|
||||

|
||||
|
||||
---
|
||||
## Conception du Workflow de Mise à Jour Proxmox
|
||||
|
||||
Pour Proxmox, je ne voulais pas d’un playbook qui exécute simplement `apt upgrade` sur tous les nœuds. À la place, il fait les actions suivantes :
|
||||
|
||||
- Vérifier la santé du cluster
|
||||
- Arrêter et envoyer une notification Ntfy si le cluster n’est pas prêt
|
||||
- Pour chaque nœud, vérifier si des mises à jour sont disponibles, et si oui :
|
||||
- Activer le mode maintenance
|
||||
- Attendre que les LXC et les VM quittent le nœud
|
||||
- Mettre à jour les paquets
|
||||
- Désactiver le rééquilibrage Ceph
|
||||
- Redémarrer le nœud
|
||||
- Activer le rééquilibrage Ceph
|
||||
- Désactiver le mode maintenance
|
||||
- Attendre que Ceph soit en bonne santé
|
||||
- Envoyer un rapport Ntfy final
|
||||
|
||||
Le playbook complet est disponible sur mon [dépôt Homelab](https://github.com/Vezpi/Homelab/blob/main/ansible/proxmox/update_proxmox.yml)
|
||||
|
||||
---
|
||||
## Détails du Workflow
|
||||
|
||||
Avant de démarrer la mise à jour progressive, le playbook vérifie :
|
||||
|
||||
- Le quorum du cluster Proxmox
|
||||
- La santé de Ceph
|
||||
|
||||
Si l’une de ces vérifications échoue, le playbook s’arrête et envoie une notification Ntfy au lieu d’essayer de continuer.
|
||||
|
||||
```yaml
|
||||
- name: Verify cluster quorum
|
||||
ansible.builtin.command: pvecm status
|
||||
register: quorum_status
|
||||
changed_when: false
|
||||
failed_when: quorum_status.stdout is not search('Quorate:\\s*Yes')
|
||||
|
||||
- name: Verify Ceph health
|
||||
ansible.builtin.command: ceph health
|
||||
register: ceph_health
|
||||
changed_when: false
|
||||
failed_when: "'HEALTH_OK' not in ceph_health.stdout"
|
||||
```
|
||||
|
||||
C’est une partie importante de l’automatisation. Une mise à jour planifiée ne doit pas continuer aveuglément si le cluster n’est pas dans un bon état.
|
||||
|
||||
Le playbook met à jour les nœuds Proxmox avec `serial: 1`.
|
||||
|
||||
Cela signifie qu’un seul nœud est traité à la fois, ce qui est exactement ce que je veux pour une mise à jour de cluster.
|
||||
|
||||
Pour chaque nœud, le playbook commence par rafraîchir les dépôts et vérifie si des mises à jour sont disponibles en utilisant le mode check d’Ansible.
|
||||
|
||||
```yaml
|
||||
- name: Refresh repositories
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
|
||||
- name: Check if updates are available
|
||||
ansible.builtin.apt:
|
||||
upgrade: dist
|
||||
check_mode: true
|
||||
register: apt_check
|
||||
```
|
||||
|
||||
Si aucune mise à jour n’est disponible pour un nœud, la partie lourde du workflow est ignorée.
|
||||
|
||||
Si des mises à jour sont disponibles, le playbook stocke la version actuelle de Proxmox, active le mode maintenance, attend que les invités quittent le nœud, applique les mises à jour, redémarre le nœud, puis attend que Ceph soit de nouveau en bonne santé.
|
||||
|
||||
```yaml
|
||||
- name: Enable maintenance mode
|
||||
ansible.builtin.command: >
|
||||
ha-manager crm-command node-maintenance enable {{ inventory_hostname_short }}
|
||||
```
|
||||
|
||||
Une fois le mode maintenance activé, le playbook attend qu’il ne reste plus aucun LXC en cours d’exécution sur le nœud :
|
||||
|
||||
```yaml
|
||||
- name: Wait for LXCs to leave node
|
||||
ansible.builtin.shell: |
|
||||
pct list | awk 'NR>1 && $2=="running" {count++} END {print count+0}'
|
||||
register: lxc_count
|
||||
changed_when: false
|
||||
until: lxc_count.stdout | int == 0
|
||||
retries: 60
|
||||
delay: 15
|
||||
```
|
||||
|
||||
Il fait la même chose pour les VM en cours d’exécution :
|
||||
|
||||
```yaml
|
||||
- name: Wait for VMs to leave node
|
||||
ansible.builtin.shell: |
|
||||
qm list | awk 'NR>1 && $3=="running" {count++} END {print count+0}'
|
||||
register: vm_count
|
||||
changed_when: false
|
||||
until: vm_count.stdout | int == 0
|
||||
retries: 60
|
||||
delay: 15
|
||||
```
|
||||
|
||||
Une fois que le nœud est vide, la mise à niveau des paquets peut s’exécuter :
|
||||
|
||||
```yaml
|
||||
- name: Update packages
|
||||
ansible.builtin.apt:
|
||||
upgrade: full
|
||||
autoremove: true
|
||||
autoclean: true
|
||||
```
|
||||
|
||||
Avant de redémarrer, le playbook définit `noout` sur les OSD Ceph :
|
||||
|
||||
```yaml
|
||||
- name: Disable Ceph rebalancing
|
||||
ansible.builtin.command: ceph osd set noout
|
||||
```
|
||||
|
||||
Puis le nœud est redémarré :
|
||||
|
||||
```yaml
|
||||
- name: Reboot node
|
||||
ansible.builtin.reboot:
|
||||
reboot_timeout: 900
|
||||
post_reboot_delay: 30
|
||||
```
|
||||
|
||||
Après le redémarrage, le rééquilibrage Ceph est réactivé, le mode maintenance est désactivé, et le playbook attend que Ceph revienne à `HEALTH_OK`.
|
||||
|
||||
```yaml
|
||||
- name: Enable Ceph rebalancing
|
||||
ansible.builtin.command: ceph osd unset noout
|
||||
|
||||
- name: Disable maintenance mode
|
||||
ansible.builtin.command: >
|
||||
ha-manager crm-command node-maintenance disable {{ inventory_hostname_short }}
|
||||
|
||||
- name: Wait for Ceph to be healthy
|
||||
ansible.builtin.command: ceph health
|
||||
register: ceph_status
|
||||
changed_when: false
|
||||
until: "'HEALTH_OK' in ceph_status.stdout"
|
||||
retries: 60
|
||||
delay: 15
|
||||
delegate_to: "{{ groups['nodes'][0] }}"
|
||||
```
|
||||
|
||||
Le résultat est une mise à jour progressive contrôlée au lieu d’une procédure manuelle nœud par nœud.
|
||||
|
||||
---
|
||||
## Envoi d’un Rapport de Mise à Jour
|
||||
|
||||
À la fin du workflow, le playbook envoie un rapport via Ntfy. Il détermine d’abord si au moins un nœud a été mis à jour :
|
||||
|
||||
```yaml
|
||||
- name: Determine if updates occurred
|
||||
ansible.builtin.set_fact:
|
||||
updates_performed: "{{ groups['nodes'] | map('extract', hostvars) | selectattr('update_report', 'defined') | list | length > 0 }}"
|
||||
```
|
||||
|
||||
Ensuite, il envoie un message au topic `homelab`.
|
||||
|
||||
Si aucune mise à jour n’était disponible, la notification l’indique et utilise une priorité plus basse.
|
||||
|
||||
Si des mises à jour ont été appliquées, la notification liste les nœuds mis à jour et affiche la version de Proxmox avant et après la mise à jour.
|
||||
|
||||
La logique de rapport est basée sur le fact `update_report` sauvegardé pendant la mise à jour du nœud :
|
||||
|
||||
```yaml
|
||||
- name: Save update report
|
||||
ansible.builtin.set_fact:
|
||||
update_report:
|
||||
old: "{{ pve_old_version.stdout }}"
|
||||
new: "{{ pve_new_version.stdout }}"
|
||||
```
|
||||
|
||||
Le corps de la notification construit ensuite un résumé à partir de tous les nœuds :
|
||||
|
||||
```yaml
|
||||
body: |
|
||||
{% set updated_nodes = [] %}
|
||||
{% for node in groups['nodes'] %}
|
||||
{% if hostvars[node].update_report is defined %}
|
||||
{% set _ = updated_nodes.append(node) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if not updates_performed %}
|
||||
No updates available on the cluster.
|
||||
{% else %}
|
||||
The following nodes were updated:
|
||||
{% for node in updated_nodes %}
|
||||
{% if hostvars[node].update_report.old == hostvars[node].update_report.new %}
|
||||
- {{ hostvars[node].inventory_hostname_short }}: version {{ hostvars[node].update_report.old }} (unchanged)
|
||||
{% else %}
|
||||
- {{ hostvars[node].inventory_hostname_short }}: version {{ hostvars[node].update_report.old }} → {{ hostvars[node].update_report.new }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
Cela rend la tâche planifiée beaucoup plus facile à considérer comme fiable. Je n’ai pas besoin d’ouvrir Semaphore à chaque fois pour savoir ce qui s’est passé.
|
||||
|
||||
---
|
||||
## Exécuter le Playbook depuis Semaphore
|
||||
|
||||
Une fois le playbook prêt, je l’ai poussé dans le dépôt et j’ai configuré un modèle de tâche Semaphore pour l’exécuter.
|
||||
|
||||

|
||||
|
||||
À partir de là, je pouvais lancer le workflow et le regarder agir sur le cluster.
|
||||
|
||||
Pendant l’exécution, le nœud cible entre en mode maintenance et les workloads en cours d’exécution sont migrés hors de celui-ci.
|
||||
|
||||

|
||||
|
||||
C’est à ce moment-là que l’automatisation devient vraiment utile. Le playbook n’applique pas seulement les mises à jour. Il prend aussi en charge les étapes opérationnelles autour de la mise à jour.
|
||||
|
||||
---
|
||||
## Planification de la Mise à Jour
|
||||
|
||||
Après avoir affiné le playbook et validé le workflow, j’ai créé une planification dans Semaphore.
|
||||
|
||||
Dans `Schedule`, j’ai cliqué sur `New Schedule`, sélectionné `Cron`, donné un nom, sélectionné une planification hebdomadaire, puis choisi le vendredi à 4h00 UTC.
|
||||
|
||||

|
||||
|
||||
À ce stade, le processus de mise à jour de Proxmox n’est plus quelque chose dont je dois me souvenir pour le faire manuellement.
|
||||
|
||||
Il s’exécute selon une planification, vérifie l’état du cluster avant de faire quoi que ce soit, met à jour un nœud à la fois, et envoie une notification avec le résultat.
|
||||
|
||||
---
|
||||
## Conclusion
|
||||
|
||||
Ce projet est parti d’un problème simple : je ne mettais pas mon homelab à jour régulièrement parce que le processus était encore trop manuel.
|
||||
|
||||
L’automatisation des mises à jour Proxmox était la première étape importante. La partie importante n’était pas seulement d’exécuter les mises à niveau de paquets, mais de les entourer des vérifications et des étapes opérationnelles qui ont du sens pour un cluster Proxmox avec Ceph.
|
||||
|
||||
Semaphore me donne une façon propre d’exécuter et de planifier le playbook. Ansible décrit le processus de manière répétable. Ntfy boucle la boucle en me disant ce qui s’est passé.
|
||||
|
||||
Les prochaines étapes logiques sont de continuer avec la même approche pour les autres composants clés du lab : OPNsense et TrueNAS.
|
||||
311
content/post/20-automating-proxmox-update-ansible/index.md
Normal file
311
content/post/20-automating-proxmox-update-ansible/index.md
Normal file
@@ -0,0 +1,311 @@
|
||||
---
|
||||
slug: automating-proxmox-update-ansible
|
||||
title: Automating Proxmox VE Updates with Ansible
|
||||
description: Automate Proxmox VE cluster updates with Ansible, Semaphore UI and Ntfy, including Ceph checks, rolling reboots and reports.
|
||||
date: 2026-06-09
|
||||
draft: false
|
||||
tags:
|
||||
- proxmox
|
||||
- ansible
|
||||
- semaphore-ui
|
||||
- ntfy
|
||||
categories:
|
||||
- homelab
|
||||
---
|
||||
## Intro
|
||||
|
||||
In my homelab, updates are one of those things that are easy to postpone.
|
||||
|
||||
Not because they are complicated, but because they are manual. I need to connect to the right system, check the state, apply the updates, reboot if needed, verify that everything comes back correctly, and then repeat the same process for the next component.
|
||||
|
||||
And because it is manual, I usually keep it for later.
|
||||
|
||||
When Proxmox VE 9.1 came out, I already wanted to update my cluster, but not manually. Then Proxmox VE 9.2 became available few days ago, and I still had not built a clean process around it. That was a good trigger to finally start automating updates for the important parts of my homelab.
|
||||
|
||||
The larger goal is to simplify and automate patching for several key components:
|
||||
|
||||
- Proxmox VE
|
||||
- OPNsense
|
||||
- TrueNAS
|
||||
|
||||
I decided to start with Proxmox because it is central to the lab, and because a rolling update workflow is a good candidate for automation.
|
||||
|
||||
---
|
||||
## The Tools Involved
|
||||
|
||||
The update process is built around a few components I already use in the lab.
|
||||
|
||||
[Proxmox VE](https://www.proxmox.com/en/proxmox-virtual-environment/overview) is my virtualization platform. The cluster also uses Ceph, so before touching a node, I want to make sure the cluster is healthy and that Ceph is reporting `HEALTH_OK`.
|
||||
|
||||
[Ansible](https://docs.ansible.com/) is used to describe the update workflow as a playbook.
|
||||
|
||||
[Semaphore UI](https://semaphoreui.com/) is used to run the playbook from a web interface and schedule it.
|
||||
|
||||
[Ntfy](https://ntfy.sh/) is used for notifications. If updates are scheduled, I need to know when something happens, especially if the cluster is not ready or if an update fails.
|
||||
|
||||
---
|
||||
## Creating a Dedicated Ntfy Topic
|
||||
|
||||
Before scheduling anything, I wanted a notification channel dedicated to the homelab.
|
||||
|
||||
I created a `homelab` topic in Ntfy and a dedicated user named `semaphore` with write-only access to this topic.
|
||||
|
||||
```bash
|
||||
ntfy user add semaphore
|
||||
ntfy access semaphore homelab wo
|
||||
```
|
||||
|
||||
The idea is that Semaphore only needs to publish messages. It does not need read access.
|
||||
|
||||
I also added the topic on my mobile phone so I can receive notifications when the automation runs.
|
||||
|
||||
In Semaphore, I created a variable group named `Ntfy Homelab` to store the values needed by the playbooks:
|
||||
|
||||
- `ntfy_url`
|
||||
- `ntfy_topic`
|
||||
- `ntfy_user`
|
||||
- `NTFY_PASSWORD`
|
||||
|
||||
The password is stored as an environment variable in the `Secrets` tab.
|
||||
|
||||

|
||||
|
||||
---
|
||||
## Designing the Proxmox Update Workflow
|
||||
|
||||
For Proxmox, I did not want a playbook that simply runs `apt upgrade` on all nodes. Instead, it doing the following:
|
||||
|
||||
- Check cluster health
|
||||
- Stop and send a Ntfy notification if the cluster is not ready
|
||||
- For each node, check if updates are available, if so:
|
||||
- Enable maintenance mode
|
||||
- Wait for LXCs and VMs to leave the node
|
||||
- Update packages
|
||||
- Disable Ceph rebalancing
|
||||
- Reboot the node
|
||||
- Enable Ceph rebalancing
|
||||
- Disable maintenance mode
|
||||
- Wait for Ceph to be healthy
|
||||
- Send a final Ntfy report
|
||||
|
||||
The full playbook is available on my [Homelab repo](https://github.com/Vezpi/Homelab/blob/main/ansible/proxmox/update_proxmox.yml)
|
||||
|
||||
---
|
||||
## Workflow Details
|
||||
|
||||
Before starting the rolling update, the playbook checks:
|
||||
|
||||
- Proxmox cluster quorum
|
||||
- Ceph health
|
||||
|
||||
If one of these checks fails, the playbook stops and sends a Ntfy notification instead of trying to continue.
|
||||
|
||||
```yaml
|
||||
- name: Verify cluster quorum
|
||||
ansible.builtin.command: pvecm status
|
||||
register: quorum_status
|
||||
changed_when: false
|
||||
failed_when: quorum_status.stdout is not search('Quorate:\\s*Yes')
|
||||
|
||||
- name: Verify Ceph health
|
||||
ansible.builtin.command: ceph health
|
||||
register: ceph_health
|
||||
changed_when: false
|
||||
failed_when: "'HEALTH_OK' not in ceph_health.stdout"
|
||||
```
|
||||
|
||||
This is an important part of the automation. A scheduled update should not blindly continue if the cluster is not in a good state.
|
||||
|
||||
The playbook updates the Proxmox nodes with `serial: 1`.
|
||||
|
||||
That means only one node is handled at a time, which is exactly what I want for a cluster update.
|
||||
|
||||
For each node, the playbook first refreshes the repositories and checks if updates are available using Ansible check mode.
|
||||
|
||||
```yaml
|
||||
- name: Refresh repositories
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
|
||||
- name: Check if updates are available
|
||||
ansible.builtin.apt:
|
||||
upgrade: dist
|
||||
check_mode: true
|
||||
register: apt_check
|
||||
```
|
||||
|
||||
If no updates are available for a node, the heavy part of the workflow is skipped.
|
||||
|
||||
If updates are available, the playbook stores the current Proxmox version, enables maintenance mode, waits for guests to leave the node, applies the updates, reboots the node, and then waits for Ceph to be healthy again.
|
||||
|
||||
```yaml
|
||||
- name: Enable maintenance mode
|
||||
ansible.builtin.command: >
|
||||
ha-manager crm-command node-maintenance enable {{ inventory_hostname_short }}
|
||||
```
|
||||
|
||||
After maintenance mode is enabled, the playbook waits until no running LXC remains on the node:
|
||||
|
||||
```yaml
|
||||
- name: Wait for LXCs to leave node
|
||||
ansible.builtin.shell: |
|
||||
pct list | awk 'NR>1 && $2=="running" {count++} END {print count+0}'
|
||||
register: lxc_count
|
||||
changed_when: false
|
||||
until: lxc_count.stdout | int == 0
|
||||
retries: 60
|
||||
delay: 15
|
||||
```
|
||||
|
||||
It does the same for running VMs:
|
||||
|
||||
```yaml
|
||||
- name: Wait for VMs to leave node
|
||||
ansible.builtin.shell: |
|
||||
qm list | awk 'NR>1 && $3=="running" {count++} END {print count+0}'
|
||||
register: vm_count
|
||||
changed_when: false
|
||||
until: vm_count.stdout | int == 0
|
||||
retries: 60
|
||||
delay: 15
|
||||
```
|
||||
|
||||
Once the node is empty, the package upgrade can run:
|
||||
|
||||
```yaml
|
||||
- name: Update packages
|
||||
ansible.builtin.apt:
|
||||
upgrade: full
|
||||
autoremove: true
|
||||
autoclean: true
|
||||
```
|
||||
|
||||
Before rebooting, the playbook sets Ceph OSD `noout`:
|
||||
|
||||
```yaml
|
||||
- name: Disable Ceph rebalancing
|
||||
ansible.builtin.command: ceph osd set noout
|
||||
```
|
||||
|
||||
Then the node is rebooted:
|
||||
|
||||
```yaml
|
||||
- name: Reboot node
|
||||
ansible.builtin.reboot:
|
||||
reboot_timeout: 900
|
||||
post_reboot_delay: 30
|
||||
```
|
||||
|
||||
After the reboot, Ceph rebalancing is enabled again, maintenance mode is disabled, and the playbook waits for Ceph to return to `HEALTH_OK`.
|
||||
|
||||
```yaml
|
||||
- name: Enable Ceph rebalancing
|
||||
ansible.builtin.command: ceph osd unset noout
|
||||
|
||||
- name: Disable maintenance mode
|
||||
ansible.builtin.command: >
|
||||
ha-manager crm-command node-maintenance disable {{ inventory_hostname_short }}
|
||||
|
||||
- name: Wait for Ceph to be healthy
|
||||
ansible.builtin.command: ceph health
|
||||
register: ceph_status
|
||||
changed_when: false
|
||||
until: "'HEALTH_OK' in ceph_status.stdout"
|
||||
retries: 60
|
||||
delay: 15
|
||||
delegate_to: "{{ groups['nodes'][0] }}"
|
||||
```
|
||||
|
||||
The result is a controlled rolling update instead of a manual node-by-node procedure.
|
||||
|
||||
---
|
||||
## Sending an Update Report
|
||||
|
||||
At the end of the workflow, the playbook sends a report through Ntfy. It first determines if at least one node was updated:
|
||||
|
||||
```yaml
|
||||
- name: Determine if updates occurred
|
||||
ansible.builtin.set_fact:
|
||||
updates_performed: "{{ groups['nodes'] | map('extract', hostvars) | selectattr('update_report', 'defined') | list | length > 0 }}"
|
||||
```
|
||||
|
||||
Then it sends a message to the `homelab` topic.
|
||||
|
||||
If no updates were available, the notification says so and uses a lower priority.
|
||||
|
||||
If updates were applied, the notification lists the updated nodes and shows the Proxmox version before and after the update.
|
||||
|
||||
The report logic is based on the `update_report` fact saved during the node update:
|
||||
|
||||
```yaml
|
||||
- name: Save update report
|
||||
ansible.builtin.set_fact:
|
||||
update_report:
|
||||
old: "{{ pve_old_version.stdout }}"
|
||||
new: "{{ pve_new_version.stdout }}"
|
||||
```
|
||||
|
||||
The notification body then builds a summary from all nodes:
|
||||
|
||||
```yaml
|
||||
body: |
|
||||
{% set updated_nodes = [] %}
|
||||
{% for node in groups['nodes'] %}
|
||||
{% if hostvars[node].update_report is defined %}
|
||||
{% set _ = updated_nodes.append(node) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if not updates_performed %}
|
||||
No updates available on the cluster.
|
||||
{% else %}
|
||||
The following nodes were updated:
|
||||
{% for node in updated_nodes %}
|
||||
{% if hostvars[node].update_report.old == hostvars[node].update_report.new %}
|
||||
- {{ hostvars[node].inventory_hostname_short }}: version {{ hostvars[node].update_report.old }} (unchanged)
|
||||
{% else %}
|
||||
- {{ hostvars[node].inventory_hostname_short }}: version {{ hostvars[node].update_report.old }} → {{ hostvars[node].update_report.new }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
This makes the scheduled job much easier to trust. I do not need to open Semaphore every time to know what happened.
|
||||
|
||||
---
|
||||
## Running the Playbook from Semaphore
|
||||
|
||||
Once the playbook was ready, I pushed it to the repository and configured a Semaphore task template to run it.
|
||||
|
||||

|
||||
|
||||
From there, I could launch the workflow and watch it act on the cluster.
|
||||
|
||||
During execution, the target node enters maintenance mode and the running workloads are migrated away from it.
|
||||
|
||||

|
||||
|
||||
This is the point where the automation becomes really useful. The playbook is not only applying updates. It is also taking care of the operational steps around the update.
|
||||
|
||||
---
|
||||
## Scheduling the Update
|
||||
|
||||
After refining the playbook and validating the workflow, I created a schedule in Semaphore.
|
||||
|
||||
In `Schedule`, I clicked `New Schedule`, selected `Cron`, gave it a name, selected a weekly schedule, and picked Friday at 4:00 AM UTC.
|
||||
|
||||

|
||||
|
||||
At this point, the Proxmox update process is no longer something I need to remember to do manually.
|
||||
|
||||
It runs on a schedule, checks the state of the cluster before doing anything, updates one node at a time, and sends a notification with the result.
|
||||
|
||||
---
|
||||
## Conclusion
|
||||
|
||||
This project started with a simple problem: I was not updating my homelab regularly because the process was still too manual.
|
||||
|
||||
Automating Proxmox updates was the first milestone. The important part was not only running package upgrades, but wrapping them in the checks and operational steps that make sense for a Proxmox cluster with Ceph.
|
||||
|
||||
Semaphore gives me a clean way to run and schedule the playbook. Ansible describes the process in a repeatable way. Ntfy closes the loop by telling me what happened.
|
||||
|
||||
The next logical steps are to continue the same approach for the other key components of the lab: OPNsense and TrueNAS.
|
||||
Reference in New Issue
Block a user