paquets tiers : les épingles tiennent, cache ou pas

- épingles posées en préférences apt (990) par chaque rôle et par le socle
  avant son dist-upgrade ; garde : paquet plus récent ou épingle inopérante
- quatre épingles relevées (step-cli, icingadb-redis, icinga-php-*)
- paquets_tiers : --force-confold aux mises à jour ; une mise à jour compte
  comme un changement
- CHANGELOG 66

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-10-03 21:52:25 -04:00
parent badaf753f8
commit 715f9de84d
6 changed files with 172 additions and 7 deletions

View file

@ -1,5 +1,52 @@
# CHANGELOG — Set-OPS
## 2026-10-03 (66) — Les épingles tiennent, cache ou pas
**Demande de l'exploitant** : faire respecter les épingles de `paquets-tiers.yml`.
**Deux fuites, mesurées sur les 21 paquets épinglés et toutes les machines.** (1) Sans le
cache du contrôleur — c'est le cas des runners — les rôles installaient la dernière version
publiée (`apt install alloy`, sans version). (2) Le socle (`common_packages`, `upgrade: full`)
montait tout au-delà de l'épingle, cache ou pas. Résultat le jour même : sept paquets plus
récents que leur épingle chez les deux locataires, et un au site (`icinga-php-thirdparty`
1.0.1, alors que le cache avait posé 1.0.0).
**Épingles relevées** sur ce que portent les locataires (en plus des trois de Grafana, 65) :
`step-cli` 0.31.0-1, `icingadb-redis` 8.2.10-1, `icinga-php-library` et
`icinga-php-thirdparty` 1.0.1-1. Cache retiré, empreintes vérifiées.
**Fait — les épingles deviennent des préférences apt** (`paquets_tiers/tasks/epingles.yml`),
une par paquet, priorité 990 : apt préfère la version épinglée à toute plus récente, y monte
un paquet plus ancien, et ne rétrograde jamais. Éprouvé avant d'écrire : au site, épingle sur
1.20.0-1 alors que 1.20.1-1 existe — candidat 1.20.0-1, `dist-upgrade` s'y arrête ; chez un
locataire déjà en 1.20.1-1 — rien ne bouge. Posées par chaque rôle pour ses paquets, et par
le socle pour TOUTES, avant son `dist-upgrade`. **Garde** : refus si un paquet est déjà plus
récent que son épingle (la relever), ou si l'épingle ne prend pas — version absente de
l'index, qu'apt ignore sans un mot (contrôle réservé à l'application réelle : en simulation,
la préférence n'est pas écrite).
**Deux défauts de `paquets_tiers` révélés par la première vraie mise à jour.** Le rôle ne
faisait jusque-là que des premières installations. (1) Sur `site-dns-01`, `alloy` 1.19.2 ->
1.20.1 a demandé quoi faire de `/etc/default/alloy`, modifié par `client_journal` — sans
personne pour répondre : paquet déballé, NON configuré (`iU`). Réparé à la main
(`dpkg --configure --force-confold`, fichier local gardé), puis corrigé : `apt-get` reçoit
désormais `--force-confdef --force-confold`, comme le socle. Éprouvé sur `site-cache-01`
(`ii`, port local conservé). (2) La garde `changed_when` cherchait « 0 nouvellement
installés », qu'apt écrit aussi lors d'une mise à jour : la montée se rapportait « ok ».
Corrigé.
**Appliqué au site** (`client_journal`, 9 VM) : `alloy` 1.20.1-1 partout, configuré, préférence
posée, aucune perte d'envoi. Les autres épingles se poseront au prochain passage du socle ou
des rôles concernés — au site comme chez les locataires, qui n'ont encore rien reçu de ceci.
**Vu au passage, non traité** : le socle réinstalle `cloud-init` à chaque passage (« Installer
les paquets communs ») et `serveur_durci` le retire derrière — un va-et-vient à chaque
déploiement complet.
**Validation** : `ansible-lint` (0 violation), `--syntax-check` des sept playbooks touchés,
`make verifier` conforme (83/83) ; socle simulé sur `site-dns-01` (20 préférences, garde au
vert).
## 2026-10-03 (65) — Épingles Grafana relevées sur ce que portent les locataires
**Demande de l'exploitant** : relever les épingles (point 4 du rapport d'anomalies).

View file

@ -22,7 +22,7 @@ depots:
composant: main
# Sur CHAQUE machine, via `client_pki`. Le paquet le plus critique de la liste.
paquets:
step-cli: 0.30.6-1
step-cli: 0.31.0-1
icinga:
uri: https://packages.icinga.com/debian
@ -37,12 +37,12 @@ depots:
icinga-archive-keyring: 2.0.0-1+debian13
icingacli: 2.14.0-2+debian13
icingadb: 1.5.1-8+debian13
icingadb-redis: 8.2.9-1+debian13
icingadb-redis: 8.2.10-1+debian13
icingadb-web: 1.4.0-1+debian13
icinga-l10n: 1.4.0-1+debian13
icinga-php-legacy: 1.1.0-1+debian13
icinga-php-library: 1.0.0-2+debian13
icinga-php-thirdparty: 1.0.0-2+debian13
icinga-php-library: 1.0.1-1+debian13
icinga-php-thirdparty: 1.0.1-1+debian13
icingaweb2: 2.14.0-2+debian13
icingaweb2-common: 2.14.0-2+debian13
icingaweb2-module-monitoring: 2.12.6-1+debian13

View file

@ -116,6 +116,18 @@
cache_valid_time: 3600
lock_timeout: "{{ common_packages_lock_timeout }}"
# LES ÉPINGLES AVANT LA MISE À JOUR (2026-10-03). `upgrade: full` montait les paquets
# tiers au-dela de `paquets-tiers.yml` : au site, `icinga-php-thirdparty` etait passe de
# 1.0.0 (pose par le cache) a 1.0.1 sans que personne l'ait decide. Les preferences apt de
# TOUTES les epingles sont posees ici, sur toute machine du socle : une epingle sur un
# paquet absent ne fait rien, et fixe la version de sa premiere installation.
- name: Épingler tous les paquets tiers avant la mise à jour
ansible.builtin.include_role:
name: paquets_tiers
tasks_from: epingles.yml
vars:
paquets_tiers_toutes_epingles: true
- name: Appliquer les mises à jour disponibles
ansible.builtin.apt:
upgrade: full

View file

@ -20,3 +20,19 @@ paquets_tiers_noms: []
# télécharge une fois depuis un poste qui a Internet, on déploie ensuite sans en avoir.
paquets_tiers_cache: "{{ lookup('env', 'HOME') }}/.cache/setops/paquets"
paquets_tiers_dest: "/var/cache/setops/paquets"
# LES ÉPINGLES S'APPLIQUENT AUSSI SANS CACHE (2026-10-03). Elles ne valaient que pour ce
# qui sortait du cache du contrôleur. Un runner — qui n'a pas ce cache — installait la
# dernière version publiée, et le `dist-upgrade` du socle (`common_packages`) montait tout
# au-delà de l'épingle, cache ou pas. Mesuré le jour même : sept paquets sur vingt et un
# plus récents que leur épingle chez les locataires, et un au site.
#
# Le fichier qui fait foi est celui du dépôt — le même que lit `make cacher-paquets`.
paquets_tiers_fichier_epingles: "{{ role_path }}/../../paquets-tiers.yml"
# 990 : apt PRÉFÈRE la version épinglée à toute version plus récente (500 par défaut), y
# monte un paquet plus ancien, mais ne RÉTROGRADE jamais un paquet déjà plus récent — ce
# que ferait 1000 et plus. Éprouvé sur `site-dns-01` et `web-dorsal-01` avant d'écrire.
paquets_tiers_priorite: 990
# `true` : épingler TOUS les paquets de paquets-tiers.yml, pas seulement `paquets_tiers_noms`.
# C'est l'appel du socle, avant son `dist-upgrade` (voir common_packages).
paquets_tiers_toutes_epingles: false

View file

@ -0,0 +1,75 @@
---
# LES ÉPINGLES, POSÉES COMME PRÉFÉRENCES APT — cache ou pas (voir defaults/main.yml).
# Une préférence par paquet : plusieurs rôles appellent celui-ci sur la même machine, et
# un fichier commun serait réécrit par chacun avec ses seuls paquets.
- name: Lire les épingles du dépôt
ansible.builtin.set_fact:
paquets_tiers_toutes: >-
{{ (lookup('ansible.builtin.file', paquets_tiers_fichier_epingles) | from_yaml).depots
| dict2items | map(attribute='value.paquets') | list | combine }}
- name: Retenir celles de cet appel (toutes, pour le socle)
ansible.builtin.set_fact:
paquets_tiers_epingles: >-
{{ paquets_tiers_toutes if (paquets_tiers_toutes_epingles | bool)
else (paquets_tiers_toutes | dict2items | selectattr('key', 'in', paquets_tiers_noms)
| items2dict) }}
- name: Épingler les paquets tiers (préférence apt)
ansible.builtin.copy:
dest: "/etc/apt/preferences.d/setops-{{ item.key }}"
content: |
# Géré par Set-OPS (rôle paquets_tiers, depuis paquets-tiers.yml). Ne pas éditer.
Package: {{ item.key }}
Pin: version {{ item.value }}
Pin-Priority: {{ paquets_tiers_priorite }}
owner: root
group: root
mode: "0644"
loop: "{{ paquets_tiers_epingles | dict2items }}"
loop_control:
label: "{{ item.key }}={{ item.value }}"
# CE QUE L'ÉPINGLE NE PEUT PAS FAIRE, ET QU'IL FAUT DIRE. Deux cas où elle ne tient pas :
# - le paquet est DÉJÀ plus récent : 990 ne rétrograde pas (c'est voulu) — l'épingle est
# en retard sur la machine, et il faut la relever, pas laisser l'écart dormir ;
# - la version épinglée n'est PAS dans l'index du dépôt : apt ignore la préférence sans
# un mot et reprend la dernière version — la candidate est alors plus récente.
# Ce second contrôle n'a pas cours en simulation : la préférence n'y est pas écrite, et la
# candidate lue serait celle d'avant l'épingle — une fausse alerte à chaque `--check`.
- name: Comparer installé, candidat et épingle
ansible.builtin.shell: |
set -uo pipefail
{% for nom, version in paquets_tiers_epingles.items() %}
inst=$(dpkg-query -W -f='${Version}' {{ nom }} 2>/dev/null || true)
cand=$(apt-cache policy {{ nom }} 2>/dev/null | awk '/Candidate:/ {print $2}')
if [ -n "$inst" ] && dpkg --compare-versions "$inst" gt "{{ version }}"; then
echo "PLUS_RECENT {{ nom }} installe=$inst epingle={{ version }}"
fi
{% if not ansible_check_mode %}
if [ -n "$cand" ] && [ "$cand" != "(none)" ] && dpkg --compare-versions "$cand" gt "{{ version }}" \
&& { [ -z "$inst" ] || dpkg --compare-versions "$cand" gt "$inst"; }; then
echo "INOPERANTE {{ nom }} candidat=$cand epingle={{ version }}"
fi
{% endif %}
{% endfor %}
true
args:
executable: /bin/bash
environment:
LC_ALL: C
register: paquets_tiers_ecarts
changed_when: false
check_mode: false
when: paquets_tiers_epingles | length > 0
- name: Refuser une épingle qui ne tient pas
ansible.builtin.assert:
that:
- paquets_tiers_ecarts.stdout_lines | default([]) | length == 0
fail_msg: >-
Épingle(s) de paquets-tiers.yml qui ne tiennent pas sur {{ inventory_hostname }} :
{{ paquets_tiers_ecarts.stdout_lines | default([]) | join(' ; ') }}.
PLUS_RECENT : relever l'épingle sur la version installée (puis `make cacher-paquets`).
INOPERANTE : la version épinglée n'est pas dans l'index du dépôt — en choisir une qui y est.
when: paquets_tiers_epingles | length > 0

View file

@ -2,6 +2,10 @@
# DÉGRADER, JAMAIS DEVINER. Sans cache sur le contrôleur, ce rôle ne fait rien et le DIT :
# l'appelant installera depuis le dépôt distant, comme avant. On ne casse pas un
# déploiement connecté parce qu'un cache manque.
# LES ÉPINGLES D'ABORD — voir `epingles.yml`, que le socle appelle aussi pour TOUTES.
- name: Épingler les paquets demandés (préférences apt)
ansible.builtin.include_tasks: epingles.yml
- name: Lire le manifeste du cache du contrôleur
ansible.builtin.slurp:
src: "{{ paquets_tiers_cache }}/MANIFESTE"
@ -65,6 +69,13 @@
loop_control:
label: "{{ item.key }}"
# GARDER LES FICHIERS DE CONFIGURATION QUE LES RÔLES ONT MODIFIÉS (2026-10-03). Tant que ce
# rôle ne faisait que des PREMIÈRES installations, aucun fichier n'avait été touché. Depuis
# que les épingles montent, il MET À JOUR : sur `site-dns-01`, `alloy` 1.19.2 -> 1.20.1 a
# demandé quoi faire de `/etc/default/alloy` (le port y est imposé par `client_journal`),
# sans personne pour répondre — « fin de fichier sur l'entrée standard », paquet déballé et
# NON configuré. Mêmes options que le socle (`common_packages`) : garder la version locale.
#
# EN UN SEUL APPEL, ET C'EST LE POINT. `apt` sait résoudre un ENSEMBLE de fichiers locaux
# — l'un dépendant de l'autre — alors qu'une installation paquet par paquet échouerait sur
# l'ordre. Icinga en apporte dix-sept qui se dépendent mutuellement : les poser un à un
@ -74,12 +85,16 @@
- name: Installer les paquets tiers depuis le cache
ansible.builtin.command:
argv: >-
{{ ["apt-get", "install", "-y", "--no-install-recommends"]
{{ ["apt-get", "install", "-y", "--no-install-recommends",
"-o", "Dpkg::Options::=--force-confdef", "-o", "Dpkg::Options::=--force-confold"]
+ (paquets_tiers_disponibles.values()
| map('regex_replace', '^', paquets_tiers_dest ~ '/') | list) }}
environment:
DEBIAN_FRONTEND: noninteractive
register: paquets_tiers_pose
changed_when: "'0 nouvellement installés' not in paquets_tiers_pose.stdout
and '0 newly installed' not in paquets_tiers_pose.stdout"
# UNE MISE À JOUR EST UN CHANGEMENT (2026-10-03). La garde ne cherchait que « 0 nouvellement
# installés », qu'apt écrit AUSSI quand il met à jour (« 1 mis à jour, 0 nouvellement
# installés ») : `alloy` 1.19.2 -> 1.20.1 sur `site-cache-01` s'est rapporté « ok ».
changed_when: "'0 mis à jour, 0 nouvellement installés' not in paquets_tiers_pose.stdout
and '0 upgraded, 0 newly installed' not in paquets_tiers_pose.stdout"
when: paquets_tiers_disponibles | length > 0