From 0dd57ba995ac0ea1798423d2c4b4c7f1c990f5d8 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Mon, 31 Aug 2026 18:31:21 -0400 Subject: [PATCH] =?UTF-8?q?artefacts=20:=20deux=20filets=20=E2=80=94=20un?= =?UTF-8?q?=20cache=20mort=20ne=20doit=20plus=20rendre=20un=20tenant=20irr?= =?UTF-8?q?eparable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CE CACHE EST UN MAILLON DONT DEPEND SA PROPRE RECONSTRUCTION. Toute la flotte y prend ses paquets. S il pointe vers un amont mort, apt echoue sur les quinze machines, donc le socle echoue, donc le deploiement n atteint jamais la couche qui poserait le bon amont. LE CORRECTIF SE RETROUVE DANS LA COUCHE QUE LA PANNE EMPECHE D ATTEINDRE, et il faut une main pour en sortir. C est arrive le 2026-08-31 : l amont pointait sur le cache de patient 0, eteint la veille pour liberer de la RAM. apt rendait « 503 Connection timeout » EN CITANT L ADRESSE DU CACHE LOCAL, jamais celle de l amont manquant — la panne accusait le maillon visible. DEUX FILETS, SYMETRIQUES. serveur_artefacts sonde l amont AVANT d ecrire, et refuse s il est muet : mieux vaut un cache qui garde sa configuration precedente qu un cache qu on vient de rendre inutilisable pour toute la flotte. client_artefacts sonde la source AVANT de detourner apt vers elle. Ce fichier ECRASE le plancher d amorcage pose par le socle — le poser sur un cache mort prive la machine du cache du SITE, qui lui fonctionnait. En refusant, le plancher reste : la flotte est degradee mais debout, et REPARABLE PAR UN DEPLOIEMENT. `ignore_errors` ET NON `failed_when: false` — la difference est toute la garde. failed_when: false REECRIT le verdict : la tache n est plus jamais failed, donc `is not failed` est toujours vrai, donc l assertion ne peut pas tirer. Ecrite ainsi, ma premiere version a declare « joignable » un amont mesure MUET la seconde d avant. Une garde qui ne peut pas echouer ne garde rien. Deux controles verifies sur la machine : amont eteint -> refuse ; amont reel -> pose. make verifier : vert. make prouver : CONFORME, 55 OK, 0 echec, 0 saute. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on --- CHANGELOG.md | 37 ++++++++++++++++++ roles/client_artefacts/tasks/main.yml | 47 ++++++++++++++++++++++ roles/serveur_artefacts/tasks/main.yml | 54 ++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60dbe58..808ffa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,42 @@ # CHANGELOG — Set-OPS +## 2026-08-31 — Deux filets : un cache mort ne rend plus un tenant irreparable + +**55 preuves.** Le cache d'un tenant est **un maillon dont depend sa propre +reconstruction**. Toute la flotte y prend ses paquets ; s'il pointe vers un amont mort, +`apt` echoue sur les quinze machines, donc le socle echoue, donc le deploiement n'atteint +jamais la couche qui poserait le bon amont. + +*Le correctif se retrouve dans la couche que la panne empeche d'atteindre* — et il faut +une main pour en sortir. C'est exactement ce qui s'est passe : l'amont pointait sur le +cache de patient 0, eteint la veille pour liberer de la RAM. + +**`apt` rendait `503 Connection timeout` en citant l'adresse du cache LOCAL**, jamais +celle de l'amont manquant. La panne accusait le maillon visible. + +### Deux filets, symetriques + +`serveur_artefacts` sonde l'amont **avant** d'ecrire, et refuse s'il est muet : mieux vaut +un cache qui garde sa configuration precedente qu'un cache qu'on vient de rendre +inutilisable pour toute la flotte. + +`client_artefacts` sonde la source **avant** de detourner `apt` vers elle. Ce fichier +ecrase le plancher d'amorcage pose par le socle — le poser sur un cache mort prive la +machine du cache du SITE, qui lui fonctionnait. En refusant, le plancher reste : la flotte +est degradee mais debout, et **reparable par un deploiement**. + +### `ignore_errors` et non `failed_when: false` — la difference est toute la garde + +`failed_when: false` **reecrit le verdict** : la tache n'est plus jamais `failed`, donc +`is not failed` est toujours vrai, donc l'assertion qui suit ne peut pas tirer. Ecrite +ainsi, ma premiere version a declare « joignable » un amont mesure **muet la seconde +d'avant**. + +*Une garde qui ne peut pas echouer ne garde rien.* Deux controles verifies sur la machine : +amont eteint → refuse ; amont reel → pose. + +make verifier : vert. make prouver : CONFORME, 55 OK, 0 echec, 0 saute. + ## 2026-08-31 — Trois unites en echec sur chaque machine, et personne ne les voyait **55 preuves.** Le deploiement rendait `15/15, failed=0`. Les machines portaient chacune diff --git a/roles/client_artefacts/tasks/main.yml b/roles/client_artefacts/tasks/main.yml index 01e877e..e5da68c 100644 --- a/roles/client_artefacts/tasks/main.yml +++ b/roles/client_artefacts/tasks/main.yml @@ -8,6 +8,53 @@ Déclarer le service au plan, ou forcer client_artefacts_actif=false. when: client_artefacts_actif | bool +# --- LE FILET, COTE CLIENT : NE PAS VISER UN CACHE QUI NE SERT PAS ------------ +# +# Symetrique de la garde de `serveur_artefacts` (mesure du 2026-08-31). Celle-la protege +# le CACHE d'un amont mort ; celle-ci protege LA FLOTTE d'un cache mort. +# +# Ce fichier ECRASE le plancher d'amorcage pose par le socle — il trie apres. Le poser +# alors que le cache du tenant ne repond pas prive donc la machine du cache du SITE, qui +# lui fonctionnait : on remplace une source qui marche par une qui ne marche pas. +# +# Quinze machines ont ainsi perdu `apt` d'un coup, et le deploiement ne pouvait plus +# atteindre la couche qui aurait repare le cache. En laissant le plancher en place, elles +# restent servies par le site — dégradé, mais debout, et reparable par un deploiement. +- name: La source de l'écosystème répond-elle vraiment ? + ansible.builtin.wait_for: + host: "{{ client_artefacts_hote }}.{{ domaine_interne }}" + port: "{{ client_artefacts_port }}" + timeout: 10 + register: client_artefacts_sonde + changed_when: false + # `ignore_errors` ET NON `failed_when: false` — LA DIFFERENCE EST TOUTE LA GARDE. + # + # `failed_when: false` REECRIT le verdict : la tache n'est plus jamais `failed`, donc + # `is not failed` est toujours vrai, donc l'assertion qui suit ne peut PAS tirer. Ecrite + # ainsi, elle a declare « joignable » un amont mesure MUET la seconde d'avant + # (2026-08-31). Une garde qui ne peut pas echouer ne garde rien. + # + # `ignore_errors` laisse le verdict intact et se contente de ne pas arreter le play : + # c'est ce qu'il faut pour SONDER puis DECIDER. + ignore_errors: true + when: + - client_artefacts_actif | bool + - not ansible_check_mode + +- name: Refuser de detourner apt vers une source muette + ansible.builtin.assert: + that: + - client_artefacts_sonde is not failed + fail_msg: >- + {{ client_artefacts_hote }}:{{ client_artefacts_port }} ne repond pas. Diriger `apt` + vers elle retirerait a cette machine le cache d'amorcage du SITE, qui fonctionne — + et la flotte perdrait `apt` sans qu'un deploiement puisse la reparer. + Deployer `serveur_artefacts` sur {{ client_artefacts_hote }} d'abord. + success_msg: "Source d'artefacts {{ client_artefacts_hote }} joignable." + when: + - client_artefacts_actif | bool + - not ansible_check_mode + - name: Diriger apt vers la source de l'écosystème ansible.builtin.copy: dest: "{{ client_artefacts_fichier }}" diff --git a/roles/serveur_artefacts/tasks/main.yml b/roles/serveur_artefacts/tasks/main.yml index 3a8cb20..80a8303 100644 --- a/roles/serveur_artefacts/tasks/main.yml +++ b/roles/serveur_artefacts/tasks/main.yml @@ -36,6 +36,60 @@ # dernier lu gagne. Le préfixe `zzz-` place donc notre configuration après celle du # paquet Debian et celle de debconf, sans avoir à les éditer — on ajoute, on ne remplace # pas. Une valeur posée ici l'emporte, et `apt purge` rend la machine à son état d'origine. +# --- LE FILET : NE PAS POSER UN AMONT INJOIGNABLE ---------------------------- +# +# CE CACHE EST UN MAILLON DONT DEPEND SA PROPRE RECONSTRUCTION (mesure du 2026-08-31). +# +# Toute la flotte du tenant prend ses paquets ici. Si ce cache pointe vers un amont mort, +# `apt` echoue sur les QUINZE machines — donc le socle echoue, donc le deploiement +# n'atteint jamais la couche qui poserait le bon amont. Le correctif se retrouve dans la +# couche que la panne empeche d'atteindre, et il faut une main pour en sortir. +# +# C'est exactement ce qui est arrive : l'amont pointait sur le cache de patient 0, eteint +# la veille pour liberer de la RAM. `apt` rendait `503 Connection timeout` EN CITANT +# L'ADRESSE DU CACHE LOCAL, jamais celle de l'amont manquant — la panne accusait le +# maillon visible. +# +# On mesure donc AVANT d'ecrire. Un amont injoignable est refuse : mieux vaut un cache +# qui garde sa configuration precedente et un message clair, qu'un cache qu'on vient de +# rendre inutilisable pour toute la flotte. +- name: L'amont declare repond-il ? + ansible.builtin.wait_for: + host: "{{ serveur_artefacts_amont | urlsplit('hostname') }}" + port: "{{ serveur_artefacts_amont | urlsplit('port') | default(3142, true) | int }}" + timeout: 10 + register: serveur_artefacts_amont_sonde + changed_when: false + # `ignore_errors` ET NON `failed_when: false` — LA DIFFERENCE EST TOUTE LA GARDE. + # + # `failed_when: false` REECRIT le verdict : la tache n'est plus jamais `failed`, donc + # `is not failed` est toujours vrai, donc l'assertion qui suit ne peut PAS tirer. Ecrite + # ainsi, elle a declare « joignable » un amont mesure MUET la seconde d'avant + # (2026-08-31). Une garde qui ne peut pas echouer ne garde rien. + # + # `ignore_errors` laisse le verdict intact et se contente de ne pas arreter le play : + # c'est ce qu'il faut pour SONDER puis DECIDER. + ignore_errors: true + when: + - serveur_artefacts_amont | length > 0 + - not ansible_check_mode + +- name: Refuser un amont injoignable plutot que de rendre ce cache inutilisable + ansible.builtin.assert: + that: + - serveur_artefacts_amont_sonde is not failed + fail_msg: >- + L'amont declare ({{ serveur_artefacts_amont }}) ne repond pas. Le poser rendrait ce + cache — et donc `apt` sur TOUTE la flotte de cet ecosysteme — inutilisable, et le + deploiement ne pourrait plus atteindre la couche qui le corrigerait. + Verifier que la machine d'amont tourne, ou vider `serveur_artefacts_amont` pour + aller directement chez Debian (c'est une emancipation : voir + docs/filiation-emancipation.md). + success_msg: "Amont {{ serveur_artefacts_amont }} joignable." + when: + - serveur_artefacts_amont | length > 0 + - not ansible_check_mode + - name: Déployer la configuration Set-OPS ansible.builtin.template: src: zzz-setops.conf.j2