supervision : les huit derniers roles, et deux defauts que l epreuve a trouves
Les 33 roles serveur_* declarent maintenant une sonde. Les huit qui manquaient
sont ceux dont la verite ne ressemble pas a « ce service repond-il ».
Quatre marqueurs du site : ce que tasks/main.yml verifie UNE FOIS au deploiement
cesse d etre vrai sans que rien ne tombe. La racine du cache se retrouve chainee,
la forge du genome repond en n ayant plus rien dedans, un locataire n est plus
admis a resoudre, l isolation d un depot glisse.
serveur_ops_site ne sert rien : il detient un pouvoir. La sonde verifie que la
carte est la, que la voute du site est chiffree et que sa cle est en 0600 — sans
lire le contenu d aucun des trois.
serveur_icingaweb2 surveille la vitrine de la supervision elle-meme : si la
console meurt, tout reste vert et l exploitant est aveugle.
DEFAUT 1 — quatre gabarits qu Ansible aurait refuse de rendre. Jinja lit le
{# de ${#tableau[@]} comme un debut de commentaire. Le depot connaissait le
remede et l appliquait la ou quelqu un s etait fait prendre, nulle part ailleurs.
P76 rend desormais chaque gabarit de role, avec les delimiteurs qu Ansible en
tirerait — pas une recherche de motif.
DEFAUT 2 — la doctrine promettait 54 greffons et citait check_pgsql. La flotte a
monitoring-plugins-basic : 53, sans check_pgsql ni check_dns ni check_ldap. Le
paquet qui les porte traine samba et snmp sur chaque machine. Un greffon absent
sort en 127, qui n est pas un code Nagios.
21 controles negatifs sur les machines reelles du site. ansible-lint production
0/91, harnais 75 OK.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
This commit is contained in:
parent
4290b566b4
commit
aac74f6043
45 changed files with 1220 additions and 26 deletions
|
|
@ -188,7 +188,7 @@ Si `ansible-lint` n’est pas disponible, le signaler clairement. Ne pas invente
|
||||||
## Écrire, puis relire (D-68)
|
## Écrire, puis relire (D-68)
|
||||||
|
|
||||||
`--syntax-check` et `ansible-lint` prouvent que le dépôt est cohérent **avec lui-même**.
|
`--syntax-check` et `ansible-lint` prouvent que le dépôt est cohérent **avec lui-même**.
|
||||||
C'est aussi ce que font les 75 preuves de `make prouver` : elles lisent le dépôt, sans le
|
C'est aussi ce que font les 76 preuves de `make prouver` : elles lisent le dépôt, sans le
|
||||||
moindre appel réseau. **Aucune ne demande au système déployé s'il ressemble à ce que le
|
moindre appel réseau. **Aucune ne demande au système déployé s'il ressemble à ce que le
|
||||||
dépôt annonce.**
|
dépôt annonce.**
|
||||||
|
|
||||||
|
|
|
||||||
87
CHANGELOG.md
87
CHANGELOG.md
|
|
@ -1,5 +1,92 @@
|
||||||
# CHANGELOG — Set-OPS
|
# CHANGELOG — Set-OPS
|
||||||
|
|
||||||
|
## 2026-09-14 (13) — Les huit derniers roles sans sonde, et deux defauts que l'epreuve a trouves
|
||||||
|
|
||||||
|
Les 33 roles `serveur_*` declarent desormais une supervision. Les huit qui manquaient
|
||||||
|
n'avaient pas ete oublies par hasard : ce sont ceux dont la verite ne ressemble pas a
|
||||||
|
« ce service repond-il ».
|
||||||
|
|
||||||
|
### Quatre marqueurs du site, quatre verites qu'aucun installateur ne voit
|
||||||
|
|
||||||
|
Un marqueur n'installe rien. Il dit qu'une machine rend un service AUX LOCATAIRES, et
|
||||||
|
`tasks/main.yml` le verifie — une fois, au deploiement. Ce qu'il verifie cesse d'etre vrai
|
||||||
|
sans que rien ne tombe.
|
||||||
|
|
||||||
|
| role | sonde | ce qui se degrade en silence |
|
||||||
|
|---|---|---|
|
||||||
|
| `serveur_cache_site` | `cache-site-racine` | la racine se retrouve chainee, ou ne remplit plus |
|
||||||
|
| `serveur_forge_site` | `genome-servi` | la forge repond et n'a plus rien dedans |
|
||||||
|
| `serveur_resolveur_site` | `resolution-locataires` | un locataire n'est plus admis a resoudre |
|
||||||
|
| `serveur_backup_site` | `depot-locataires` | l'isolation glisse, ou la place manque |
|
||||||
|
|
||||||
|
`genome-servi` mesure exactement la panne du 2026-09-14 : le wiki du site avait **zero
|
||||||
|
commit** apres une reconstruction, et la forge etait verte tout du long. Un depot vide ne
|
||||||
|
fait pas echouer un clone — il rend un arbre sans fichiers.
|
||||||
|
|
||||||
|
### Les quatre autres
|
||||||
|
|
||||||
|
`serveur_ops_site` ne sert rien : il DETIENT un pouvoir. `pouvoir-materialiser` verifie que
|
||||||
|
la carte de la fabric est la, que la voute du site est **chiffree** et que sa cle est en
|
||||||
|
0600 — sans jamais lire le contenu d'aucun des trois. Une voute dechiffree en transit ne
|
||||||
|
fait echouer aucun deploiement : Ansible la lit tres bien, elle expose simplement tout.
|
||||||
|
|
||||||
|
`serveur_icingaweb2` porte la sonde la plus retorse du dispositif : **la supervision
|
||||||
|
surveille sa propre vitrine**. Si la console meurt, le moteur collecte toujours, les
|
||||||
|
verdicts restent verts, et l'exploitant est aveugle.
|
||||||
|
|
||||||
|
`serveur_web_frontal` et `serveur_web_dorsal` frappent chaque site et chaque app declares.
|
||||||
|
Une webapp ne tombe presque jamais en `failed` — elle se coince : le processus vit, systemd
|
||||||
|
la dit active, et plus une requete n'aboutit. Le rapport d'unites en echec ne verra jamais
|
||||||
|
ca.
|
||||||
|
|
||||||
|
### DEFAUT 1 — quatre gabarits qu'Ansible aurait refuse de rendre
|
||||||
|
|
||||||
|
`${#tableau[@]}` est la seule facon d'obtenir la longueur d'un tableau en bash. Elle
|
||||||
|
contient `{#`, que Jinja lit comme un **debut de commentaire** :
|
||||||
|
|
||||||
|
TemplateSyntaxError: Missing end of comment tag
|
||||||
|
|
||||||
|
Rien ne le signalait : le fichier est valide en shell, valide a la lecture, et
|
||||||
|
`--syntax-check` ne rend pas les gabarits. La panne serait arrivee sur la machine, pendant
|
||||||
|
un deploiement.
|
||||||
|
|
||||||
|
Le depot connaissait deja le remede — une en-tete `#jinja2:` qui deplace le delimiteur — et
|
||||||
|
l'appliquait **trois fois**, exactement la ou quelqu'un s'etait fait prendre. Nulle part
|
||||||
|
ailleurs. Quatre sondes neuves l'ont refait d'un coup.
|
||||||
|
|
||||||
|
**P76 est la garde qui manquait** : elle REND chaque gabarit de role, avec les memes
|
||||||
|
delimiteurs qu'Ansible en tirerait. Pas une recherche de motif — un rendu, qui attrapera
|
||||||
|
aussi le prochain piege, quel qu'il soit.
|
||||||
|
|
||||||
|
### DEFAUT 2 — la doctrine promettait des greffons que la flotte n'a pas
|
||||||
|
|
||||||
|
`docs/supervision-conception.md` annoncait « le paquet en fournit **54** » et citait
|
||||||
|
`check_pgsql`. Mesure du 2026-09-14 : `client_sante` installe
|
||||||
|
`monitoring-plugins-basic` — **53** greffons, et ni `check_pgsql`, ni `check_dns`, ni
|
||||||
|
`check_ldap` n'en font partie. Ils sont dans `monitoring-plugins-standard`, qui traine
|
||||||
|
**samba, smbclient et une pile SNMP** sur chaque machine de la flotte.
|
||||||
|
|
||||||
|
La premiere ecriture de `resolution-locataires` appelait `check_dns`. Elle sortait en
|
||||||
|
**127** — qui n'est pas un code Nagios : la sonde serait devenue illisible au lieu
|
||||||
|
d'echouer proprement. Elle emploie `dig`, comme la sonde voisine le faisait deja.
|
||||||
|
|
||||||
|
Le paquet n'est pas ajoute : le durcissement dit le contraire d'installer Samba partout
|
||||||
|
pour trois greffons. C'est le document qui est corrige.
|
||||||
|
|
||||||
|
### Un defaut de plus, trouve en eprouvant
|
||||||
|
|
||||||
|
`genome-servi` interrogeait d'abord `/api/v1/repos/search?owner=<org>`. Avec
|
||||||
|
`owner=organisation-qui-nexiste-pas`, elle rendait **les six depots de la forge** et sortait
|
||||||
|
VERTE : le parametre est ignore. Elle n'aurait jamais mesure le genome, seulement « cette
|
||||||
|
forge a des depots ». La route `/api/v1/orgs/<org>/repos`, elle, rend 404.
|
||||||
|
|
||||||
|
### Controles negatifs
|
||||||
|
|
||||||
|
Chaque sonde a ete mise en defaut sur la machine reelle avant d'etre ecrite dans son role —
|
||||||
|
21 cas au total, sur `site-cache-01`, `site-forge-01`, `site-dns-01`, `site-backup-01` et
|
||||||
|
`site-ops-01`. Les huit gabarits rendent et passent `bash -n`. `ansible-lint` profil
|
||||||
|
production : 0 sur 91 fichiers. Harnais : **75 OK, 0 echec**.
|
||||||
|
|
||||||
## 2026-09-14 (12) — La decision appliquee a TOUTE la flotte : une machine de moins par ecosysteme
|
## 2026-09-14 (12) — La decision appliquee a TOUTE la flotte : une machine de moins par ecosysteme
|
||||||
|
|
||||||
L'entree precedente prend la decision. Celle-ci la pose partout, sur ordre de
|
L'entree precedente prend la decision. Celle-ci la pose partout, sur ordre de
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
> [`docs/audit/affirmations.md`](affirmations.md).
|
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||||
|
|
||||||
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
|
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
|
||||||
- **Verdict** : ✅ CONFORME (74 OK · 0 echec · 1 saute)
|
- **Verdict** : ✅ CONFORME (75 OK · 0 echec · 1 saute)
|
||||||
|
|
||||||
## Preuves
|
## Preuves
|
||||||
|
|
||||||
|
|
@ -69,14 +69,14 @@
|
||||||
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. |
|
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. |
|
||||||
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 13 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 13. |
|
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 13 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 13. |
|
||||||
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
|
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
|
||||||
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (75 preuves, 68 roles, 41 groupes). |
|
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (76 preuves, 68 roles, 41 groupes). |
|
||||||
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
|
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
|
||||||
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
|
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
|
||||||
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `d76ce57` (publie le 2026-09-14). |
|
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `d76ce57` (publie le 2026-09-14). |
|
||||||
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 47 champ(s) sur 6 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp |
|
| P61 | Schema du plan : il decrit tout ce que les plans contiennent | AFF-033 | ✅ OK | Le schema decrit 47 champ(s) sur 6 registres ; il couvre tout ce que les plans reels contiennent, et la FORME de chaque champ (scalaire / objet / table) corresp |
|
||||||
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:5, serveurs:3 champ(s) lus par validateur). |
|
| P62 | Schema du plan : il decrit tout ce que le MOTEUR accepte | AFF-033 | ✅ OK | Les 4 validateurs n'acceptent aucun champ que le schema ignore (applications:8, bases_donnees:4, domaines_publics:5, serveurs:3 champ(s) lus par validateur). |
|
||||||
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
|
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
|
||||||
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 31 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, serveur_ |
|
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 39 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, serveur_ |
|
||||||
| P65 | Depots tiers : demandes au cache, jamais en HTTPS direct | — | ✅ OK | 4 depot(s) tiers relaye(s) par le cache, aucun role ne les vise en https:// ecrit en dur. |
|
| P65 | Depots tiers : demandes au cache, jamais en HTTPS direct | — | ✅ OK | 4 depot(s) tiers relaye(s) par le cache, aucun role ne les vise en https:// ecrit en dur. |
|
||||||
| P66 | Clients OIDC : chaque URI vise un nom que le plan expose | — | ✅ OK | 3 client(s) OIDC, toutes leurs URI visent un FQDN que le plan expose (5 exposition(s)). |
|
| P66 | Clients OIDC : chaque URI vise un nom que le plan expose | — | ✅ OK | 3 client(s) OIDC, toutes leurs URI visent un FQDN que le plan expose (5 exposition(s)). |
|
||||||
| P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 5 service(s) expose(s) portent le nom du plan (5 groupe(s) derive(s)). |
|
| P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 5 service(s) expose(s) portent le nom du plan (5 groupe(s) derive(s)). |
|
||||||
|
|
@ -88,6 +88,7 @@
|
||||||
| P73 | Le locataire designe les services de son site REEL | — | ✅ OK | CONFORME : 6 intrant(s) du locataire concordent avec ce que le site expose (1 non declare(s), donc derive(s) ou non utilise(s)). |
|
| P73 | Le locataire designe les services de son site REEL | — | ✅ OK | CONFORME : 6 intrant(s) du locataire concordent avec ce que le site expose (1 non declare(s), donc derive(s) ou non utilise(s)). |
|
||||||
| P74 | Gabarit dore : une seule declaration, au plan du site | — | ✅ OK | Gabarit declare une seule fois : VMID 9006 « modeleSetOPS-minimal », precedent 99998. |
|
| P74 | Gabarit dore : une seule declaration, au plan du site | — | ✅ OK | Gabarit declare une seule fois : VMID 9006 « modeleSetOPS-minimal », precedent 99998. |
|
||||||
| P75 | Les parametres de clonage traversent les trois maillons | — | ✅ OK | 14 parametre(s) de clonage, tous emis par l'inventaire. |
|
| P75 | Les parametres de clonage traversent les trois maillons | — | ✅ OK | 14 parametre(s) de clonage, tous emis par l'inventaire. |
|
||||||
|
| P76 | Tout gabarit de role se rend vraiment | — | ✅ OK | 151 gabarits de role : tous se rendent. |
|
||||||
|
|
||||||
## Couverture des affirmations ✅ du registre
|
## Couverture des affirmations ✅ du registre
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ make placement-plan # chaque VM est-elle là où le plan la met
|
||||||
|
|
||||||
## Le trou qu'il comble
|
## Le trou qu'il comble
|
||||||
|
|
||||||
`scripts/prouver.py` porte 75 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le
|
`scripts/prouver.py` porte 76 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le
|
||||||
dépôt. Zéro appel réseau, zéro SSH, zéro `ansible`. Elles établissent que le dépôt est
|
dépôt. Zéro appel réseau, zéro SSH, zéro `ansible`. Elles établissent que le dépôt est
|
||||||
cohérent **avec lui-même** — que les handlers existent, que les intrants ont un
|
cohérent **avec lui-même** — que les handlers existent, que les intrants ont un
|
||||||
propriétaire, que rien n'est codé en dur.
|
propriétaire, que rien n'est codé en dur.
|
||||||
|
|
|
||||||
|
|
@ -48,10 +48,30 @@ Une sonde est un **script local**, déposé par le rôle qui la possède, dans
|
||||||
|
|
||||||
**La conséquence pratique est grande.** Un greffon standard **est** une sonde valide, sans
|
**La conséquence pratique est grande.** Un greffon standard **est** une sonde valide, sans
|
||||||
la moindre colle : `check_disk`, `check_load`, `check_procs`, `check_ntp_time`,
|
la moindre colle : `check_disk`, `check_load`, `check_procs`, `check_ntp_time`,
|
||||||
`check_file_age`, `check_smtp`, `check_pgsql`… Le paquet en fournit **54**. Une sonde ne
|
`check_file_age`, `check_smtp`… Une sonde ne s'écrit à la main que lorsque la vérité à
|
||||||
s'écrit à la main que lorsque la vérité à mesurer est propre à Set-OPS — et c'était le cas
|
mesurer est propre à Set-OPS — et c'était le cas pour `client_pki/certificat`, qui compare
|
||||||
pour `client_pki/certificat`, qui compare l'empreinte *servie* à celle du disque : aucun
|
l'empreinte *servie* à celle du disque : aucun greffon ne sait ça.
|
||||||
greffon ne sait ça.
|
|
||||||
|
### Ce que la flotte a vraiment sous la main — et ce qu'elle n'a pas
|
||||||
|
|
||||||
|
`client_sante` installe **`monitoring-plugins-basic`** : **53** greffons, mesurés le
|
||||||
|
2026-09-14 sur la flotte. Ce document a d'abord annoncé « 54 » et cité `check_pgsql` en
|
||||||
|
exemple. **`check_pgsql` n'en fait pas partie**, ni `check_dns`, ni `check_ldap` : ils sont
|
||||||
|
dans `monitoring-plugins-standard`.
|
||||||
|
|
||||||
|
Et ce paquet-là ne s'ajoute pas :
|
||||||
|
|
||||||
|
apt-get install -s monitoring-plugins-standard
|
||||||
|
→ samba, python3-samba, smbclient, snmp, tdb-tools…
|
||||||
|
|
||||||
|
Une pile Samba et une pile SNMP sur **chaque machine** de la flotte, pour obtenir trois
|
||||||
|
greffons. Le durcissement dit le contraire de ça.
|
||||||
|
|
||||||
|
**La conséquence pour qui écrit une sonde :** vérifier que le greffon appelé est dans
|
||||||
|
`-basic` avant de s'appuyer dessus. **Un greffon absent sort en 127** — qui n'est pas un
|
||||||
|
code Nagios (0/1/2/3) : la sonde devient illisible au lieu d'échouer proprement. Quand le
|
||||||
|
greffon manque, prendre l'outil natif du service (`dig` sur un résolveur, `psql` sur une
|
||||||
|
base) ; c'est déjà ce que fait `serveur_resolveur/resolution`.
|
||||||
|
|
||||||
Écrire du shell là où un greffon existe, c'est se donner du code à maintenir *et* se priver
|
Écrire du shell là où un greffon existe, c'est se donner du code à maintenir *et* se priver
|
||||||
de vingt ans de cas limites déjà rencontrés par d'autres.
|
de vingt ans de cas limites déjà rencontrés par d'autres.
|
||||||
|
|
|
||||||
35
roles/serveur_backup_site/meta/supervision.yml
Normal file
35
roles/serveur_backup_site/meta/supervision.yml
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
---
|
||||||
|
# Supervision derivee du role. Voir docs/supervision-conception.md.
|
||||||
|
#
|
||||||
|
# CE QUE `serveur_backup` SURVEILLE DEJA : `depot` — le depot accepte-t-il vraiment une
|
||||||
|
# ecriture ? Vrai pour n'importe quel depot restic.
|
||||||
|
#
|
||||||
|
# CE ROLE PORTE DEUX VERITES QUE L'AUTRE NE PEUT PAS VOIR, parce qu'elles n'existent que
|
||||||
|
# la ou PLUSIEURS locataires deposent dans la meme machine.
|
||||||
|
#
|
||||||
|
# 1. L'ISOLATION. Chaque locataire a son compte Unix et son repertoire en 0700. Si un
|
||||||
|
# seul de ces modes glisse, un locataire peut lire — ou effacer — les instantanes
|
||||||
|
# d'un autre. Et RIEN ne se plaint : les sauvegardes continuent de reussir, des deux
|
||||||
|
# cotes. C'est une panne qui n'a aucun symptome jusqu'au jour ou elle en a un seul,
|
||||||
|
# definitif.
|
||||||
|
#
|
||||||
|
# 2. LA PLACE. `tasks/main.yml` exige un plancher au deploiement. Un depot qui se
|
||||||
|
# remplit ensuite n'echoue pas a l'ouverture — il echoue AU MILIEU d'un instantane,
|
||||||
|
# CHEZ LE LOCATAIRE, et la panne s'y lit comme une erreur reseau. Le site est le seul
|
||||||
|
# a pouvoir la voir venir ; le locataire, lui, ne peut que la subir.
|
||||||
|
#
|
||||||
|
# CE QUE LA SONDE NE FAIT PAS, ET NE PEUT PAS FAIRE : juger les instantanes des
|
||||||
|
# locataires. Restic chiffre CHEZ EUX, avec un mot de passe qui ne quitte pas leur voute.
|
||||||
|
# Le site heberge des octets opaques. La verification suit la cle — elle appartient a
|
||||||
|
# celui qui detient la cle, et ce n'est pas le site.
|
||||||
|
#
|
||||||
|
# TTL de 5400 s pour un porteur qui passe aux 15 min : trois passages manques avant la
|
||||||
|
# peremption. Le silence alerte autant que l'echec.
|
||||||
|
sondes:
|
||||||
|
- nom: depot-locataires
|
||||||
|
ttl: 5400
|
||||||
|
raison: 'Les depots des locataires sont-ils toujours etanches, et reste-t-il de la
|
||||||
|
place devant eux ? Un mode qui glisse laisse un locataire lire ou effacer les
|
||||||
|
instantanes d''un autre, sans qu''aucune sauvegarde n''echoue. Un depot qui se
|
||||||
|
remplit echoue AU MILIEU d''un instantane, chez le locataire, et sa panne s''y lit
|
||||||
|
comme une erreur reseau.'
|
||||||
|
|
@ -166,3 +166,26 @@
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.item.compte }}"
|
label: "{{ item.item.compte }}"
|
||||||
when: not ansible_check_mode
|
when: not ansible_check_mode
|
||||||
|
|
||||||
|
# LE ROLE QUI POSSEDE LA VERITE DEPOSE SA PROPRE SONDE. `client_sante` la fait tourner et
|
||||||
|
# pousse le resultat.
|
||||||
|
#
|
||||||
|
# LES DEUX ASSERTIONS CI-DESSUS SONT DES PHOTOS. L'espace se consomme tous les jours et
|
||||||
|
# l'etanchéite peut glisser a la premiere main qui passe — or aucune de ces deux derives
|
||||||
|
# ne fait echouer quoi que ce soit avant qu'il ne soit trop tard. La sonde les rejoue
|
||||||
|
# toutes les quinze minutes.
|
||||||
|
- name: Assurer le repertoire des sondes de supervision
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /usr/local/lib/setops/sondes
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Deposer la sonde « depot-locataires »
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: sonde-depot-locataires.sh.j2
|
||||||
|
dest: /usr/local/lib/setops/sondes/depot-locataires.sh
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0750"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
#jinja2: comment_start_string:'{=#', comment_end_string:'#=}'
|
||||||
|
#!/bin/bash
|
||||||
|
# (Le gabarit redefinit le delimiteur de commentaire Jinja : `${#tableau[@]}` contient
|
||||||
|
# la sequence `{#`, que Jinja lirait comme un debut de commentaire. Le shell en a
|
||||||
|
# besoin ; c'est donc Jinja qui cede. Meme geste que `client_sante/setops-sante.sh.j2`
|
||||||
|
# et `serveur_nginx/sonde-edge.sh.j2`.)
|
||||||
|
# GENERE par Set-OPS (role serveur_backup_site). Ne pas editer a la main.
|
||||||
|
#
|
||||||
|
# SONDE « depot-locataires » — les depots sont-ils etanches, et reste-t-il de la place ?
|
||||||
|
#
|
||||||
|
# PAS UNE ENVELOPPE DE GREFFON. `check_disk` saurait la moitie du travail — la place —
|
||||||
|
# mais pas l'autre, qui est la seule proprement Set-OPS : « chaque locataire est-il
|
||||||
|
# toujours seul chez lui ». On garde les deux dans une sonde parce qu'elles appellent le
|
||||||
|
# MEME geste : aller voir le depot du site.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
PARENT={{ serveur_backup_site_parent }}
|
||||||
|
PLANCHER={{ serveur_backup_site_espace_minimal_go }}
|
||||||
|
comptes=({% for l in serveur_backup_site_locataires | default([]) %}'{{ l.compte }}' {% endfor %})
|
||||||
|
noms=({% for l in serveur_backup_site_locataires | default([]) %}'{{ l.nom }}' {% endfor %})
|
||||||
|
|
||||||
|
# 1. L'ETANCHEITE, LOCATAIRE PAR LOCATAIRE.
|
||||||
|
#
|
||||||
|
# ON RELIT CE QUE LE NOYAU APPLIQUE, pas ce qu'on croit avoir pose — `stat` sur le
|
||||||
|
# repertoire reel. C'est la meme lecture que l'assertion du deploiement, rejouee quand
|
||||||
|
# plus personne ne regarde.
|
||||||
|
fautifs=()
|
||||||
|
for i in "${!comptes[@]}"; do
|
||||||
|
compte=${comptes[$i]}
|
||||||
|
nom=${noms[$i]}
|
||||||
|
chemin="${PARENT}/${compte}"
|
||||||
|
|
||||||
|
if [[ ! -d "${chemin}" ]]; then
|
||||||
|
fautifs+=("${nom} : ${chemin} a disparu")
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
lu=$(stat -c '%U %a' "${chemin}" 2>/dev/null) || { fautifs+=("${nom} : ${chemin} illisible"); continue; }
|
||||||
|
proprietaire=${lu%% *}
|
||||||
|
mode=${lu##* }
|
||||||
|
|
||||||
|
[[ "${proprietaire}" == "${compte}" ]] || fautifs+=("${nom} : appartient a « ${proprietaire} », pas a « ${compte} »")
|
||||||
|
[[ "${mode}" == "700" ]] || fautifs+=("${nom} : mode ${mode} au lieu de 700")
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( ${#fautifs[@]} > 0 )); then
|
||||||
|
# SEPARES PAR « ; », pas par une espace : sans separateur, deux constats se collent et
|
||||||
|
# le message rendu par Icinga devient une phrase qu'on ne peut plus decouper.
|
||||||
|
liste=$(printf '%s; ' "${fautifs[@]}"); liste=${liste%; }
|
||||||
|
echo "Isolation rompue entre locataires — ${liste}. Un locataire peut lire ou effacer les instantanes d'un autre, et aucune sauvegarde n'echouera pour le dire."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. LA PLACE DEVANT LES LOCATAIRES.
|
||||||
|
#
|
||||||
|
# EN GIGAOCTETS ENTIERS, comme le plancher du deploiement : on compare deux grandeurs
|
||||||
|
# ecrites dans la meme unite, pour que le message de la sonde et celui de l'assertion
|
||||||
|
# disent exactement la meme chose.
|
||||||
|
libre=$(df -BG --output=avail "${PARENT}" 2>/dev/null | tail -1 | tr -dc '0-9')
|
||||||
|
if [[ -z "${libre}" ]]; then
|
||||||
|
echo "Impossible de mesurer la place restante sous ${PARENT} — le systeme de fichiers repond-il encore ?"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if (( libre < PLANCHER )); then
|
||||||
|
echo "Depot du site : ${libre} Go libres sous le plancher de ${PLANCHER} Go. Le prochain instantane echouera AU MILIEU, chez le locataire, et sa panne s'y lira comme une erreur reseau."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Depot du site : ${#comptes[@]} locataire(s) etanche(s), ${libre} Go libres (plancher ${PLANCHER})."
|
||||||
|
exit 0
|
||||||
|
|
@ -26,3 +26,14 @@
|
||||||
#
|
#
|
||||||
# Le cache du site va DIRECTEMENT chez Debian : il est le bout de la chaîne.
|
# Le cache du site va DIRECTEMENT chez Debian : il est le bout de la chaîne.
|
||||||
serveur_cache_site_amont_attendu: ""
|
serveur_cache_site_amont_attendu: ""
|
||||||
|
|
||||||
|
# LA SONDE « cache-site-racine » : ce qu'elle demande au cache pour prouver qu'il remplit.
|
||||||
|
#
|
||||||
|
# UN INDEX, PAS UN PAQUET. Un `Release` pese 135 ko et change tous les jours — le cache le
|
||||||
|
# traite comme volatil, donc il represente vraiment le chemin de sortie. Un paquet, lui,
|
||||||
|
# serait servi depuis le disque pour toujours et ne prouverait rien.
|
||||||
|
#
|
||||||
|
# PAR LE MODE PASSE-PLAT (`/<hote>/<chemin>`), et pas par un `Remap` : les remaps sont
|
||||||
|
# locaux a chaque installation, le passe-plat est la seule forme qui vaille partout.
|
||||||
|
serveur_cache_site_sonde_index: "/deb.debian.org/debian/dists/{{ ansible_distribution_release | default('trixie') }}/Release"
|
||||||
|
serveur_cache_site_sonde_config: "/etc/apt-cacher-ng"
|
||||||
|
|
|
||||||
35
roles/serveur_cache_site/meta/supervision.yml
Normal file
35
roles/serveur_cache_site/meta/supervision.yml
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
---
|
||||||
|
# Supervision derivee du role. Voir docs/supervision-conception.md.
|
||||||
|
#
|
||||||
|
# CE QUE `serveur_artefacts` SURVEILLE DEJA, ET QU'ON NE REFAIT PAS : `cache-apt` (le
|
||||||
|
# cache repond sur son port) et `cache-apt-volume` (il lui reste de la place). Ces deux
|
||||||
|
# verdicts valent pour N'IMPORTE QUEL cache de la fabric.
|
||||||
|
#
|
||||||
|
# CE ROLE PORTE UNE AUTRE VERITE, ET ELLE N'EST VRAIE QUE POUR LUI : ce cache est la
|
||||||
|
# RACINE de la chaine. Aucun autre n'a Debian comme amont direct.
|
||||||
|
#
|
||||||
|
# VM du tenant -> cache du tenant -> cache du SITE -> Debian
|
||||||
|
#
|
||||||
|
# Deux choses peuvent cesser d'etre vraies sans que rien ne tombe :
|
||||||
|
#
|
||||||
|
# 1. LA RACINE SE RETROUVE CHAINEE. Quelqu'un configure un mandataire amont sur ce
|
||||||
|
# cache — et la chaine se referme sur elle-meme ou sur un cache qui n'existe plus.
|
||||||
|
# `tasks/main.yml` l'exige au deploiement ; plus personne ne le verifie ensuite.
|
||||||
|
#
|
||||||
|
# 2. LA RACINE NE REMPLIT PLUS. Le cache repond parfaitement — il sert ce qu'il a — et
|
||||||
|
# ne peut plus rien chercher chez Debian. `cache-apt` reste VERT.
|
||||||
|
#
|
||||||
|
# CE QUE LE FLUX DE CE ROLE DIT DEJA, MOT POUR MOT : sans sortie, « la chaine entiere se
|
||||||
|
# termine sur un cache vide [...] et ca ne se serait vu qu'au premier `apt update` d'un
|
||||||
|
# ecosysteme neuf — c'est-a-dire au pire moment ». La sonde deplace ce constat AVANT
|
||||||
|
# l'installation d'un locataire, au lieu de le laisser arriver pendant.
|
||||||
|
#
|
||||||
|
# TTL de 5400 s pour un porteur qui passe aux 15 min : trois passages manques avant la
|
||||||
|
# peremption. Le silence alerte autant que l'echec.
|
||||||
|
sondes:
|
||||||
|
- nom: cache-site-racine
|
||||||
|
ttl: 5400
|
||||||
|
raison: 'Ce cache est-il toujours la RACINE de la chaine, et peut-il encore remplir ?
|
||||||
|
Un cache chaine sur lui-meme ou coupe de Debian repond parfaitement — il sert ce
|
||||||
|
qu''il a deja. La panne n''apparait qu''au premier `apt update` d''un ecosysteme
|
||||||
|
neuf, c''est-a-dire au pire moment.'
|
||||||
|
|
@ -40,3 +40,25 @@
|
||||||
port: "{{ serveur_artefacts_port }}"
|
port: "{{ serveur_artefacts_port }}"
|
||||||
timeout: 30
|
timeout: 30
|
||||||
when: not ansible_check_mode
|
when: not ansible_check_mode
|
||||||
|
|
||||||
|
# LE ROLE QUI POSSEDE LA VERITE DEPOSE SA PROPRE SONDE. `client_sante` la fait tourner et
|
||||||
|
# pousse le resultat ; les greffons standard, eux, sont poses par le porteur.
|
||||||
|
#
|
||||||
|
# LES TROIS CONTROLES CI-DESSUS NE VIVENT QUE LE TEMPS D'UN DEPLOIEMENT. La sonde les
|
||||||
|
# rejoue toutes les quinze minutes — c'est la seule difference, et c'est toute la
|
||||||
|
# difference : un cache chaine ou coupe de son amont ne se signale pas autrement.
|
||||||
|
- name: Assurer le repertoire des sondes de supervision
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /usr/local/lib/setops/sondes
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Deposer la sonde « cache-site-racine »
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: sonde-cache-site-racine.sh.j2
|
||||||
|
dest: /usr/local/lib/setops/sondes/cache-site-racine.sh
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0750"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# GENERE par Set-OPS (role serveur_cache_site). Ne pas editer a la main.
|
||||||
|
#
|
||||||
|
# SONDE « cache-site-racine » — ce cache est-il toujours la racine, et remplit-il ?
|
||||||
|
#
|
||||||
|
# PAS UNE ENVELOPPE DE GREFFON, ET C'EST ASSUME. `check_http` sait dire « ce service
|
||||||
|
# repond » — c'est deja la sonde `cache-apt` de `serveur_artefacts`. La verite d'ici est
|
||||||
|
# propre a Set-OPS : « ce cache-la est la RACINE de la chaine du site ». Aucun greffon
|
||||||
|
# standard ne connait cette notion.
|
||||||
|
#
|
||||||
|
# DEUX CONTROLES, DEUX ECHECS DISTINCTS.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
PORT={{ serveur_artefacts_port | default(3142) }}
|
||||||
|
INDEX='{{ serveur_cache_site_sonde_index }}'
|
||||||
|
CONF={{ serveur_cache_site_sonde_config }}
|
||||||
|
|
||||||
|
# 1. LA RACINE N'A PAS D'AMONT. `tasks/main.yml` l'exige au deploiement et personne ne le
|
||||||
|
# revoit ensuite. Un `Proxy:` ajoute a la main referme la chaine sur elle-meme, et le
|
||||||
|
# cache continue de servir ce qu'il a — donc rien ne casse avant le premier paquet
|
||||||
|
# qu'il n'a pas.
|
||||||
|
amont=$(grep -rhiE '^[[:space:]]*Proxy:[[:space:]]*\S' "${CONF}" 2>/dev/null | head -1)
|
||||||
|
if [[ -n "${amont}" ]]; then
|
||||||
|
echo "Ce cache est declare cache du SITE, mais il a un amont : ${amont}. La chaine se referme sur elle-meme."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. LA RACINE REMPLIT ENCORE. On demande au cache un index amont REEL a travers son
|
||||||
|
# mode passe-plat.
|
||||||
|
#
|
||||||
|
# CE QUE CE CONTROLE PROUVE, ET CE QU'IL NE PROUVE PAS — a lire avant de s'y fier.
|
||||||
|
# Un 200 peut venir du cache lui-meme : apt-cacher-ng sert ce qu'il a deja, et refuse
|
||||||
|
# qu'on le force a revalider (mesure du 2026-09-14 : une chaine de contournement en
|
||||||
|
# parametre est rejetee en 403, `Cache-Control: no-cache` est ignore).
|
||||||
|
#
|
||||||
|
# CE QU'IL ECARTE VRAIMENT, c'est le 503 — celui que rend un amont injoignable, une
|
||||||
|
# resolution morte ou un `Remap` casse. C'est exactement la panne du 2026-08-28, ou
|
||||||
|
# `ops-01` atteignait le cache et restait bloquee sur ce qu'il devait aller chercher.
|
||||||
|
# Un temoin dit ce qui est PARTI, jamais ce qui est ARRIVE : celui-ci dit que la porte
|
||||||
|
# de sortie existe encore, pas que Debian a repondu a l'instant.
|
||||||
|
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 20 "http://127.0.0.1:${PORT}${INDEX}" 2>/dev/null)
|
||||||
|
case "${code}" in
|
||||||
|
200)
|
||||||
|
echo "Cache du site : racine sans amont, et l'index ${INDEX} se sert (HTTP 200)."
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
5*)
|
||||||
|
echo "La racine ne remplit plus : ${INDEX} rend HTTP ${code}. Le premier apt update d'un ecosysteme neuf echouera."
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Reponse inattendue du cache pour ${INDEX} : HTTP ${code:-aucune}."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
@ -7,3 +7,12 @@
|
||||||
# Le port est celui que le plan du site donne à la forge. On ne le redéclare pas ici :
|
# Le port est celui que le plan du site donne à la forge. On ne le redéclare pas ici :
|
||||||
# une seconde source finirait par diverger de la première.
|
# une seconde source finirait par diverger de la première.
|
||||||
serveur_forge_site_port: "{{ serveur_forgejo_port | default(443) }}"
|
serveur_forge_site_port: "{{ serveur_forgejo_port | default(443) }}"
|
||||||
|
|
||||||
|
# L'ORGANISATION QUI PORTE LE GENOME. C'est elle que clonent les runners des locataires —
|
||||||
|
# `serveur_ops_depots` nomme des depots sous ce proprietaire, pas ailleurs.
|
||||||
|
serveur_forge_site_organisation: "genome"
|
||||||
|
|
||||||
|
# COMBIEN DE DEPOTS AU MINIMUM. Un site sert au moins le moteur et sa propre carte ; en
|
||||||
|
# dessous de deux, la forge n'a pas de quoi faire naitre un ecosysteme. On ne fixe pas un
|
||||||
|
# compte exact : le nombre de locataires change, la sonde ne doit pas changer avec.
|
||||||
|
serveur_forge_site_sonde_depots_minimum: 2
|
||||||
|
|
|
||||||
35
roles/serveur_forge_site/meta/supervision.yml
Normal file
35
roles/serveur_forge_site/meta/supervision.yml
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
---
|
||||||
|
# Supervision derivee du role. Voir docs/supervision-conception.md.
|
||||||
|
#
|
||||||
|
# CE QUE `serveur_forgejo` SURVEILLE DEJA : la sonde `forge` — « la forge repond-elle a
|
||||||
|
# son API, et son depot Git est-il lisible ? ». Vrai pour n'importe quelle forge.
|
||||||
|
#
|
||||||
|
# CE ROLE PORTE UNE AUTRE VERITE : cette forge-la est la RACINE DU GENOME du site (D-81).
|
||||||
|
# C'est d'elle que tout ecosysteme se reproduit. Une forge peut parfaitement repondre en
|
||||||
|
# n'ayant RIEN a servir.
|
||||||
|
#
|
||||||
|
# CE N'EST PAS UNE HYPOTHESE — C'EST ARRIVE LE 2026-09-14. `make wiki-publier` a refuse
|
||||||
|
# de publier : le wiki de cette forge n'avait AUCUN commit. Son depot n'avait pas survecu
|
||||||
|
# a une reconstruction, et rien ne l'avait dit. Le temoin, lui, affirmait depuis deux
|
||||||
|
# jours avoir publie a cette adresse.
|
||||||
|
#
|
||||||
|
# forge.genese.internal 0 page <- l'adresse que le temoin nommait
|
||||||
|
# eregion.chezlepro.ca 26 pages <- la ou vivait vraiment la documentation
|
||||||
|
#
|
||||||
|
# La forge etait VERTE pendant tout ce temps. Elle repondait, son API marchait, son Git
|
||||||
|
# etait lisible — elle n'avait simplement plus rien dedans.
|
||||||
|
#
|
||||||
|
# CE QUE LA SONDE AJOUTE : l'organisation qui porte le genome existe, elle a des depots,
|
||||||
|
# et AUCUN n'est vide. Un depot vide ne fait pas echouer un clone : il rend un arbre sans
|
||||||
|
# fichiers. L'ecosysteme neuf se deploie alors sur un moteur absent.
|
||||||
|
#
|
||||||
|
# TTL de 5400 s pour un porteur qui passe aux 15 min : trois passages manques avant la
|
||||||
|
# peremption. Le silence alerte autant que l'echec.
|
||||||
|
sondes:
|
||||||
|
- nom: genome-servi
|
||||||
|
ttl: 5400
|
||||||
|
raison: 'Cette forge a-t-elle encore un genome a servir ? Une forge vide repond
|
||||||
|
parfaitement : son API marche, son Git est lisible, elle n''a simplement plus rien
|
||||||
|
dedans. Mesure du 2026-09-14 — le wiki du site avait zero commit depuis une
|
||||||
|
reconstruction, et rien ne l''avait signale. Un ecosysteme neuf clone alors un arbre
|
||||||
|
sans fichiers.'
|
||||||
|
|
@ -27,3 +27,25 @@
|
||||||
port: "{{ serveur_forge_site_port }}"
|
port: "{{ serveur_forge_site_port }}"
|
||||||
timeout: 30
|
timeout: 30
|
||||||
when: not ansible_check_mode
|
when: not ansible_check_mode
|
||||||
|
|
||||||
|
# LE ROLE QUI POSSEDE LA VERITE DEPOSE SA PROPRE SONDE. `client_sante` la fait tourner et
|
||||||
|
# pousse le resultat.
|
||||||
|
#
|
||||||
|
# `wait_for` ci-dessus verifie que la forge ECOUTE, une fois, au deploiement. La sonde
|
||||||
|
# verifie qu'elle a QUELQUE CHOSE A SERVIR, toutes les quinze minutes. Le 2026-09-14 a
|
||||||
|
# montre que la premiere n'implique pas la seconde.
|
||||||
|
- name: Assurer le repertoire des sondes de supervision
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /usr/local/lib/setops/sondes
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Deposer la sonde « genome-servi »
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: sonde-genome-servi.sh.j2
|
||||||
|
dest: /usr/local/lib/setops/sondes/genome-servi.sh
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0750"
|
||||||
|
|
|
||||||
91
roles/serveur_forge_site/templates/sonde-genome-servi.sh.j2
Normal file
91
roles/serveur_forge_site/templates/sonde-genome-servi.sh.j2
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# GENERE par Set-OPS (role serveur_forge_site). Ne pas editer a la main.
|
||||||
|
#
|
||||||
|
# SONDE « genome-servi » — cette forge a-t-elle encore un genome a servir ?
|
||||||
|
#
|
||||||
|
# PAS UNE ENVELOPPE DE GREFFON. `check_http` dirait « 443 repond » — c'est deja ce que
|
||||||
|
# fait la sonde `forge` de `serveur_forgejo`. Ce qu'on mesure ici n'a pas d'equivalent
|
||||||
|
# standard : le CONTENU que cette forge est censee porter pour tous les locataires du site.
|
||||||
|
#
|
||||||
|
# SANS AUCUN JUSTIFICATIF. Cette route repond en anonyme (mesure du 2026-09-14) : la
|
||||||
|
# sonde n'a besoin d'aucun jeton, donc aucun secret ne dort sur disque pour elle. Elle ne
|
||||||
|
# voit que ce que voit un visiteur — ce qui suffit exactement.
|
||||||
|
#
|
||||||
|
# `/api/v1/orgs/<org>/repos` ET PAS `/api/v1/repos/search?owner=<org>`. La premiere
|
||||||
|
# ecriture de cette sonde utilisait la recherche, et l'epreuve du 2026-09-14 l'a mise en
|
||||||
|
# defaut : avec `owner=organisation-qui-nexiste-pas`, elle rendait quand meme les six
|
||||||
|
# depots de la forge et sortait VERTE. Le parametre etait simplement ignore. La sonde
|
||||||
|
# n'aurait donc jamais mesure le genome — seulement « cette forge a des depots ».
|
||||||
|
# La route par organisation, elle, rend 404 pour une organisation absente.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
ORG='{{ serveur_forge_site_organisation }}'
|
||||||
|
PORT={{ serveur_forge_site_port }}
|
||||||
|
MINIMUM={{ serveur_forge_site_sonde_depots_minimum }}
|
||||||
|
|
||||||
|
reponse=$(curl -sk --max-time 15 -w '\n%{http_code}' \
|
||||||
|
"https://127.0.0.1:${PORT}/api/v1/orgs/${ORG}/repos?limit=100" 2>/dev/null)
|
||||||
|
code=${reponse##*$'\n'}
|
||||||
|
corps=${reponse%$'\n'*}
|
||||||
|
|
||||||
|
case "${code}" in
|
||||||
|
200) : ;;
|
||||||
|
404)
|
||||||
|
echo "L'organisation « ${ORG} » n'existe pas sur cette forge : le genome du site n'a plus de proprietaire."
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
# « 000 » EST LE CODE DE CURL QUAND IL N'Y A PAS EU DE REPONSE DU TOUT — connexion
|
||||||
|
# refusee, delai depasse, TLS rompu. Ce n'est pas un code HTTP, et le confondre avec
|
||||||
|
# une erreur du serveur ferait dire « la forge rend 000 » la ou il faut dire « la forge
|
||||||
|
# ne repond pas ». La branche vide, elle, ne peut pas se produire : `-w` ecrit toujours
|
||||||
|
# un code.
|
||||||
|
000)
|
||||||
|
echo "L'API de la forge ne repond pas sur ${PORT} — le genome est injoignable pour tous les locataires du site."
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "L'API de la forge rend HTTP ${code} pour « ${ORG} » — le genome n'est pas servi."
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# `empty` EST UN CHAMP DE L'API, pas une deduction. Forgejo sait qu'un depot n'a aucun
|
||||||
|
# commit et le dit. On ne devine rien, on lit.
|
||||||
|
lecture=$(printf '%s' "${corps}" | python3 -c '
|
||||||
|
import json, sys
|
||||||
|
try:
|
||||||
|
depots = json.load(sys.stdin)
|
||||||
|
except Exception:
|
||||||
|
print("ILLISIBLE"); raise SystemExit(0)
|
||||||
|
if not isinstance(depots, list):
|
||||||
|
print("ILLISIBLE"); raise SystemExit(0)
|
||||||
|
vides = sorted(r["full_name"] for r in depots if r.get("empty"))
|
||||||
|
# LE « | » N EST PAS DECORATIF : sans separateur toujours present, une liste
|
||||||
|
# vide ferait rendre a `${lecture#*|}` le NOMBRE lui-meme, et la sonde
|
||||||
|
# annoncerait « 6 » comme nom de depot vide.
|
||||||
|
print(len(depots), "|" + ",".join(vides))
|
||||||
|
' 2>/dev/null)
|
||||||
|
|
||||||
|
if [[ "${lecture}" == "ILLISIBLE" || -z "${lecture}" ]]; then
|
||||||
|
echo "L'API de la forge repond, mais sa reponse est illisible — la forge sert-elle encore Forgejo ?"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
nombre=${lecture%% *}
|
||||||
|
vides=${lecture#*|}
|
||||||
|
|
||||||
|
if (( nombre < MINIMUM )); then
|
||||||
|
echo "La forge du genome ne porte que ${nombre} depot(s) pour « ${ORG} » (minimum ${MINIMUM}) : un ecosysteme neuf n'a rien a cloner."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# UN DEPOT VIDE NE FAIT PAS ECHOUER UN CLONE — il rend un arbre sans fichiers, et le
|
||||||
|
# deploiement qui suit se fait sur un moteur absent. C'est la panne la plus couteuse ici,
|
||||||
|
# parce qu'elle ne ressemble a rien.
|
||||||
|
if [[ -n "${vides}" ]]; then
|
||||||
|
echo "Depot(s) VIDE(s) dans le genome du site : ${vides}. Un clone y rendrait un arbre sans fichiers, sans echouer."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Genome du site : ${nombre} depots servis sous « ${ORG} », aucun vide."
|
||||||
|
exit 0
|
||||||
|
|
@ -77,3 +77,11 @@ serveur_icingaweb2_modules:
|
||||||
# Processus métier (BPM) à semer, en IaC (nom -> contenu du .conf). Vide par défaut :
|
# Processus métier (BPM) à semer, en IaC (nom -> contenu du .conf). Vide par défaut :
|
||||||
# les processus sont normalement édités via l'UI. Un contenu ici est géré par Ansible.
|
# les processus sont normalement édités via l'UI. Un contenu ici est géré par Ansible.
|
||||||
serveur_icingaweb2_bpm_processes: {}
|
serveur_icingaweb2_bpm_processes: {}
|
||||||
|
|
||||||
|
# LES CODES QU'UNE CONSOLE SAINE PEUT RENDRE A UNE REQUETE NON AUTHENTIFIEE.
|
||||||
|
#
|
||||||
|
# 200 quand la racine se sert, 302 quand elle renvoie vers le formulaire de connexion,
|
||||||
|
# 401/403 quand une passerelle SSO se tient devant. Les quatre disent la meme chose : la
|
||||||
|
# pile PHP est vivante. Ce qu'ils excluent, c'est le 502 et le 504 — php-fpm mort ou
|
||||||
|
# bloque — et l'absence de reponse, qui est le nginx local tombe.
|
||||||
|
serveur_icingaweb2_sonde_codes: "HTTP/1.1 200,HTTP/1.1 302,HTTP/1.1 401,HTTP/1.1 403"
|
||||||
|
|
|
||||||
30
roles/serveur_icingaweb2/meta/supervision.yml
Normal file
30
roles/serveur_icingaweb2/meta/supervision.yml
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
---
|
||||||
|
# Supervision derivee du role. Voir docs/supervision-conception.md.
|
||||||
|
#
|
||||||
|
# LA SONDE QUI SURVEILLE CELUI QUI REGARDE. C'est le cas le plus retors de tout le
|
||||||
|
# dispositif, et il merite d'etre dit franchement.
|
||||||
|
#
|
||||||
|
# `serveur_icinga` — le moteur — a ses propres sondes : `moteur`, `base`, `ingestion`.
|
||||||
|
# Elles disent que la supervision FONCTIONNE. Ce role-ci porte la CONSOLE : l'endroit ou
|
||||||
|
# un humain va lire ce que le moteur a compris.
|
||||||
|
#
|
||||||
|
# SI LA CONSOLE MEURT, TOUT RESTE VERT. Le moteur continue de collecter, les sondes
|
||||||
|
# continuent de pousser, les verdicts continuent de s'ecrire — et personne ne peut plus
|
||||||
|
# les voir. Le systeme est en parfaite sante et l'exploitant est aveugle. Une panne sans
|
||||||
|
# aucun symptome, jusqu'a celui qu'on decouvre en cherchant a diagnostiquer autre chose.
|
||||||
|
#
|
||||||
|
# ET C'EST LE MOTEUR LUI-MEME QUI RAPPORTE CETTE SONDE, depuis la machine ou les deux sont
|
||||||
|
# co-localises. Autrement dit : la supervision sait dire que sa propre vitrine est
|
||||||
|
# tombee — a condition que le moteur, lui, tienne. Elle ne peut pas rapporter sa propre
|
||||||
|
# mort, et c'est pour ca que le TTL existe : un porteur muet devient perime, et le silence
|
||||||
|
# alerte autant que l'echec.
|
||||||
|
#
|
||||||
|
# DEUX PROCESSUS, UNE SEULE CAUSE D'ACTION. php-fpm et le nginx local tombent
|
||||||
|
# separement, mais le geste est le meme — aller voir la console. Une seule sonde.
|
||||||
|
sondes:
|
||||||
|
- nom: console
|
||||||
|
ttl: 5400
|
||||||
|
raison: 'La console de supervision repond-elle encore ? Si elle meurt, le moteur
|
||||||
|
continue de collecter et tous les verdicts restent verts : le systeme est en
|
||||||
|
parfaite sante et l''exploitant est aveugle. C''est une panne sans symptome, qu''on
|
||||||
|
decouvre en cherchant a diagnostiquer autre chose.'
|
||||||
|
|
@ -142,3 +142,25 @@
|
||||||
- "{{ serveur_icingaweb2_php_fpm_service }}"
|
- "{{ serveur_icingaweb2_php_fpm_service }}"
|
||||||
- "{{ serveur_icingaweb2_nginx_service }}"
|
- "{{ serveur_icingaweb2_nginx_service }}"
|
||||||
when: not ansible_check_mode
|
when: not ansible_check_mode
|
||||||
|
|
||||||
|
# LE ROLE QUI POSSEDE LA VERITE DEPOSE SA PROPRE SONDE. `client_sante` la fait tourner et
|
||||||
|
# pousse le resultat ; les greffons standard, eux, sont poses par le porteur.
|
||||||
|
#
|
||||||
|
# LA SUPERVISION SURVEILLE SA PROPRE VITRINE. Sans cette sonde, une console morte laisse
|
||||||
|
# tous les verdicts au vert — le moteur va bien, les sondes poussent, et personne ne peut
|
||||||
|
# plus rien lire.
|
||||||
|
- name: Assurer le repertoire des sondes de supervision
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /usr/local/lib/setops/sondes
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Deposer la sonde « console »
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: sonde-console.sh.j2
|
||||||
|
dest: /usr/local/lib/setops/sondes/console.sh
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0750"
|
||||||
|
|
|
||||||
24
roles/serveur_icingaweb2/templates/sonde-console.sh.j2
Normal file
24
roles/serveur_icingaweb2/templates/sonde-console.sh.j2
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# GENERE par Set-OPS (role serveur_icingaweb2). Ne pas editer a la main.
|
||||||
|
#
|
||||||
|
# SONDE « console » — la console de supervision repond-elle encore ?
|
||||||
|
#
|
||||||
|
# UNE ENVELOPPE, PAS UNE SONDE ECRITE A LA MAIN. `check_http` vient de
|
||||||
|
# `monitoring-plugins-basic` : il parle deja l'API des greffons Nagios, gere les delais et
|
||||||
|
# les redirections. On lui passe l'adresse et on rend SON code de sortie tel quel.
|
||||||
|
#
|
||||||
|
# SUR LA BOUCLE LOCALE, ET SANS S'AUTHENTIFIER. En mode `external`, l'ecoute est restreinte
|
||||||
|
# a 127.0.0.1 et une passerelle SSO se tient devant : la sonde frappe donc la ou
|
||||||
|
# l'application ecoute vraiment, et se contente de la reponse que rend une requete non
|
||||||
|
# authentifiee. On mesure « la pile PHP sert encore », pas « l'annuaire accepte encore ».
|
||||||
|
#
|
||||||
|
# `-e` PLUTOT QU'UN 200 IMPLICITE : Icinga Web 2 renvoie une REDIRECTION vers son
|
||||||
|
# formulaire de connexion quand personne n'est authentifie. Exiger un 200 ferait crier la
|
||||||
|
# sonde sur le comportement NORMAL d'une console saine — la pire sorte de fausse alerte,
|
||||||
|
# celle qu'on finit par ignorer.
|
||||||
|
#
|
||||||
|
# Mise en defaut PAR PARAMETRE : `serveur_icingaweb2_sonde_codes`.
|
||||||
|
exec /usr/lib/nagios/plugins/check_http \
|
||||||
|
-H 127.0.0.1 -p {{ serveur_icingaweb2_http_port }} -u '/' \
|
||||||
|
-e '{{ serveur_icingaweb2_sonde_codes }}' \
|
||||||
|
-t 10
|
||||||
36
roles/serveur_ops_site/meta/supervision.yml
Normal file
36
roles/serveur_ops_site/meta/supervision.yml
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
---
|
||||||
|
# Supervision derivee du role. Voir docs/supervision-conception.md.
|
||||||
|
#
|
||||||
|
# CE ROLE NE SERT RIEN. Il n'ecoute sur aucun port, ne repond a personne, et une sonde de
|
||||||
|
# disponibilite n'aurait ici aucun sens. Ce qu'il detient, c'est un POUVOIR : celui de
|
||||||
|
# creer et de detruire des VM sur toute la fabric — le plus dangereux du systeme.
|
||||||
|
#
|
||||||
|
# UN POUVOIR NE TOMBE PAS EN PANNE : IL S'EVAPORE. Les trois pieces qui le composent
|
||||||
|
# peuvent disparaitre sans qu'aucun service ne bronche, et on ne s'en apercoit qu'au
|
||||||
|
# moment ou l'on veut materialiser — c'est-a-dire quand un ecosysteme doit naitre, ou
|
||||||
|
# quand il faut le reconstruire.
|
||||||
|
#
|
||||||
|
# 1. LA CARTE DE LA FABRIC (`underlay.yml`). Sans elle, le runner ne sait pas sur quoi
|
||||||
|
# il materialise. `tasks/main.yml` refuse de deployer sans — mais rien ne la surveille
|
||||||
|
# apres.
|
||||||
|
#
|
||||||
|
# 2. LA VOUTE DU SITE, ET SON CHIFFREMENT. Elle porte les justificatifs du cluster. Ce
|
||||||
|
# role verifie deja a la pose que l'en-tete `$ANSIBLE_VAULT` est la, parce qu'une
|
||||||
|
# copie mal faite la dechiffre en transit. Un fichier dechiffre ne fait echouer
|
||||||
|
# AUCUN deploiement : Ansible le lit tres bien. Il expose simplement tout.
|
||||||
|
#
|
||||||
|
# 3. LA CLE QUI L'OUVRE, et ses permissions.
|
||||||
|
#
|
||||||
|
# LA SONDE NE LIT JAMAIS LE CONTENU DE CES FICHIERS. Elle regarde un en-tete, un
|
||||||
|
# proprietaire et un mode — rien d'autre. Le secret reste un pointeur qu'on ne suit pas.
|
||||||
|
#
|
||||||
|
# TTL de 5400 s pour un porteur qui passe aux 15 min : trois passages manques avant la
|
||||||
|
# peremption. Le silence alerte autant que l'echec.
|
||||||
|
sondes:
|
||||||
|
- nom: pouvoir-materialiser
|
||||||
|
ttl: 5400
|
||||||
|
raison: 'Le runner du site peut-il encore materialiser, et son secret est-il toujours
|
||||||
|
protege ? La carte de la fabric, la voute du site et sa cle ne font tomber aucun
|
||||||
|
service en disparaissant — on s''en apercoit quand un ecosysteme doit naitre. Et une
|
||||||
|
voute dechiffree en transit ne fait echouer aucun deploiement : Ansible la lit tres
|
||||||
|
bien, elle expose simplement tout.'
|
||||||
|
|
@ -128,3 +128,26 @@
|
||||||
when:
|
when:
|
||||||
- serveur_ops_site_cle_deposer | bool
|
- serveur_ops_site_cle_deposer | bool
|
||||||
- not ansible_check_mode
|
- not ansible_check_mode
|
||||||
|
|
||||||
|
# LE ROLE QUI POSSEDE LA VERITE DEPOSE SA PROPRE SONDE. `client_sante` la fait tourner et
|
||||||
|
# pousse le resultat.
|
||||||
|
#
|
||||||
|
# CE ROLE EST FAIT D'ASSERTIONS, ET UNE ASSERTION NE VIT QUE LE TEMPS D'UN DEPLOIEMENT.
|
||||||
|
# Or ce qu'elles protegent — la carte, la voute et sa cle — ne fait tomber AUCUN service
|
||||||
|
# en disparaissant. On ne s'en apercoit qu'en voulant materialiser, c'est-a-dire au pire
|
||||||
|
# moment. La sonde rejoue les memes questions toutes les quinze minutes.
|
||||||
|
- name: Assurer le repertoire des sondes de supervision
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /usr/local/lib/setops/sondes
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Deposer la sonde « pouvoir-materialiser »
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: sonde-pouvoir-materialiser.sh.j2
|
||||||
|
dest: /usr/local/lib/setops/sondes/pouvoir-materialiser.sh
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0750"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# GENERE par Set-OPS (role serveur_ops_site). Ne pas editer a la main.
|
||||||
|
#
|
||||||
|
# SONDE « pouvoir-materialiser » — le runner du site peut-il encore creer des VM, et son
|
||||||
|
# secret est-il toujours protege ?
|
||||||
|
#
|
||||||
|
# CE QU'ELLE NE FAIT PAS, ET C'EST LE PLUS IMPORTANT : elle ne lit le CONTENU d'aucun de
|
||||||
|
# ces fichiers. Un en-tete de 21 octets, un proprietaire, un mode. Le mot de passe de la
|
||||||
|
# voute du site est un pointeur qu'on ne suit jamais — y compris depuis une sonde qui
|
||||||
|
# tourne en root toutes les quinze minutes.
|
||||||
|
#
|
||||||
|
# AUCUN GREFFON NE SAIT CA. `check_file_age` dirait qu'un fichier est vieux ; aucun ne
|
||||||
|
# sait ce qu'est une voute Ansible, ni pourquoi un fichier en clair est ici une urgence.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
CARTE='{{ serveur_ops_site_racine }}/{{ serveur_ops_site_depot }}/underlay.yml'
|
||||||
|
VOUTE='{{ serveur_ops_site_racine }}/{{ serveur_ops_site_depot }}/underlay.vault.yml'
|
||||||
|
CLE='{{ serveur_ops_site_cle_destination }}'
|
||||||
|
UTILISATEUR='{{ serveur_ops_site_utilisateur }}'
|
||||||
|
|
||||||
|
# 1. LA CARTE DE LA FABRIC.
|
||||||
|
if [[ ! -s "${CARTE}" ]]; then
|
||||||
|
echo "La carte de la fabric manque : ${CARTE}. Le runner du site ne sait plus sur quoi il materialise — aucune VM ne peut naitre."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. LA VOUTE, ET SON CHIFFREMENT.
|
||||||
|
#
|
||||||
|
# L'EN-TETE, PAS LE CONTENU. `$ANSIBLE_VAULT` tient dans les 21 premiers octets ; c'est
|
||||||
|
# exactement ce que relit `tasks/main.yml` apres la copie, et pour la meme raison : une
|
||||||
|
# copie sans `decrypt: false` dechiffre en transit, et le fichier pose est alors en clair.
|
||||||
|
if [[ ! -s "${VOUTE}" ]]; then
|
||||||
|
echo "La voute du SITE manque : ${VOUTE}. Le runner a la carte mais plus les justificatifs du cluster."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
entete=$(head -c 21 "${VOUTE}" 2>/dev/null)
|
||||||
|
if [[ "${entete}" != *'$ANSIBLE_VAULT'* ]]; then
|
||||||
|
echo "LA VOUTE DU SITE N'EST PAS CHIFFREE : ${VOUTE}. Les justificatifs du cluster sont en clair sur disque — les considerer comme exposes."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. LA CLE, ET SES PERMISSIONS.
|
||||||
|
#
|
||||||
|
# `serveur_ops_site_cle_deposer` peut valoir faux : un runner qu'on arme a la main est un
|
||||||
|
# etat legitime. On ne crie donc pas pour une cle absente — on le DIT, ce qui n'est pas la
|
||||||
|
# meme chose qu'un vert muet.
|
||||||
|
if [[ ! -e "${CLE}" ]]; then
|
||||||
|
echo "Carte et voute en place, voute chiffree. Aucune cle deposee a ${CLE} : ce runner s'arme autrement."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
lu=$(stat -c '%U %a' "${CLE}" 2>/dev/null) || { echo "La cle de la voute du site est illisible : ${CLE}."; exit 2; }
|
||||||
|
proprietaire=${lu%% *}
|
||||||
|
mode=${lu##* }
|
||||||
|
|
||||||
|
if [[ "${proprietaire}" != "${UTILISATEUR}" || "${mode}" != "600" ]]; then
|
||||||
|
echo "La cle de la voute du site est exposee : ${CLE} appartient a « ${proprietaire} » en mode ${mode} (attendu ${UTILISATEUR}, 600). Qui la lit ouvre toute la fabric."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Runner du site : carte en place, voute chiffree, cle a ${UTILISATEUR} en 600."
|
||||||
|
exit 0
|
||||||
|
|
@ -9,3 +9,17 @@
|
||||||
# du site ET le registre des tenants. On ne la redéclare pas ici : une seconde source
|
# du site ET le registre des tenants. On ne la redéclare pas ici : une seconde source
|
||||||
# finirait par diverger de la première, et la divergence se lirait « tout va bien ».
|
# finirait par diverger de la première, et la divergence se lirait « tout va bien ».
|
||||||
serveur_resolveur_site_port: "{{ serveur_resolveur_port | default(53) }}"
|
serveur_resolveur_site_port: "{{ serveur_resolveur_port | default(53) }}"
|
||||||
|
|
||||||
|
# LA SONDE « resolution-locataires ».
|
||||||
|
#
|
||||||
|
# LE NOM TEMOIN N'EST PAS UN NOM D'ICI. Demander un nom de la zone souveraine ne prouverait
|
||||||
|
# que l'autoritatif local ; c'est deja ce que fait la sonde `zones`. Un nom PUBLIC exerce
|
||||||
|
# le chemin entier — racine signee comprise — qui est exactement ce qu'un locataire vient
|
||||||
|
# chercher ici.
|
||||||
|
serveur_resolveur_site_sonde_nom: "deb.debian.org"
|
||||||
|
serveur_resolveur_site_sonde_config: "/etc/unbound/unbound.conf.d"
|
||||||
|
|
||||||
|
# L'ADRESSE OU LES LOCATAIRES FRAPPENT — pas 127.0.0.1, qui est deja couvert par la sonde
|
||||||
|
# `resolution` de `serveur_resolveur`. Une ecoute peut se deplacer sans que la boucle
|
||||||
|
# locale cesse de repondre.
|
||||||
|
serveur_resolveur_site_sonde_adresse: "{{ ansible_default_ipv4.address | default('127.0.0.1') }}"
|
||||||
|
|
|
||||||
33
roles/serveur_resolveur_site/meta/supervision.yml
Normal file
33
roles/serveur_resolveur_site/meta/supervision.yml
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
---
|
||||||
|
# Supervision derivee du role. Voir docs/supervision-conception.md.
|
||||||
|
#
|
||||||
|
# CE QUE `serveur_resolveur` SURVEILLE DEJA : `resolution` (le resolveur repond-il ?) et
|
||||||
|
# `zones` (sert-il encore ses zones ?). Vrai pour n'importe quel resolveur.
|
||||||
|
#
|
||||||
|
# CE ROLE PORTE UNE AUTRE VERITE : ce resolveur-la est celui que les LOCATAIRES
|
||||||
|
# interrogent tant qu'ils n'ont pas le leur. Sa liste d'autorisation est une liste qui
|
||||||
|
# SUIT une autre — celle des tenants du site. Et une liste qui suit une autre prend du
|
||||||
|
# retard.
|
||||||
|
#
|
||||||
|
# LA PANNE QU'ELLE PRODUIT NE RESSEMBLE PAS A SA CAUSE. Unbound ne refuse pas bruyamment
|
||||||
|
# un reseau absent de sa liste : il laisse tomber la requete. Chez le locataire, ca se lit
|
||||||
|
#
|
||||||
|
# Echec temporaire dans la resolution du nom
|
||||||
|
#
|
||||||
|
# — le message d'un DNS mort. Le DNS est parfaitement vivant ; il ne parle simplement pas
|
||||||
|
# a celui qui demande. C'est le texte meme du 2026-08-30 sur `infra-pki-01`, premiere
|
||||||
|
# machine de Chezlepro : elle sortait en 443 par adresse et restait aveugle aux noms.
|
||||||
|
#
|
||||||
|
# `tasks/main.yml` l'exige au deploiement. Mais le jour ou un tenant s'ajoute au site, la
|
||||||
|
# liste ne bouge que si quelqu'un redeploie CE role — et c'est precisement au premier
|
||||||
|
# amorcage du nouveau tenant que le manque se paie.
|
||||||
|
#
|
||||||
|
# TTL de 5400 s pour un porteur qui passe aux 15 min : trois passages manques avant la
|
||||||
|
# peremption. Le silence alerte autant que l'echec.
|
||||||
|
sondes:
|
||||||
|
- nom: resolution-locataires
|
||||||
|
ttl: 5400
|
||||||
|
raison: 'Les locataires du site sont-ils toujours admis a resoudre ici ? Un supernet
|
||||||
|
absent de la liste d''autorisation ne provoque pas un refus : Unbound laisse tomber
|
||||||
|
la requete, et le locataire lit « Echec temporaire dans la resolution du nom » — le
|
||||||
|
message d''un DNS mort, alors que le DNS va tres bien.'
|
||||||
|
|
@ -49,3 +49,26 @@
|
||||||
port: "{{ serveur_resolveur_site_port }}"
|
port: "{{ serveur_resolveur_site_port }}"
|
||||||
timeout: 30
|
timeout: 30
|
||||||
when: not ansible_check_mode
|
when: not ansible_check_mode
|
||||||
|
|
||||||
|
# LE ROLE QUI POSSEDE LA VERITE DEPOSE SA PROPRE SONDE. `client_sante` la fait tourner et
|
||||||
|
# pousse le resultat.
|
||||||
|
#
|
||||||
|
# L'ASSERTION CI-DESSUS NE VAUT QUE POUR LE DEPLOIEMENT DE CE ROLE. La liste des
|
||||||
|
# locataires du site, elle, bouge ailleurs — au plan. La sonde rejoue la meme exigence
|
||||||
|
# toutes les quinze minutes, ce qui est le seul moyen de s'en apercevoir AVANT que le
|
||||||
|
# nouveau locataire ne tente son premier amorcage.
|
||||||
|
- name: Assurer le repertoire des sondes de supervision
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /usr/local/lib/setops/sondes
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Deposer la sonde « resolution-locataires »
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: sonde-resolution-locataires.sh.j2
|
||||||
|
dest: /usr/local/lib/setops/sondes/resolution-locataires.sh
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0750"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
#jinja2: comment_start_string:'{=#', comment_end_string:'#=}'
|
||||||
|
#!/bin/bash
|
||||||
|
# (Le gabarit redefinit le delimiteur de commentaire Jinja : `${#tableau[@]}` contient
|
||||||
|
# la sequence `{#`, que Jinja lirait comme un debut de commentaire. Le shell en a
|
||||||
|
# besoin ; c'est donc Jinja qui cede. Meme geste que `client_sante/setops-sante.sh.j2`
|
||||||
|
# et `serveur_nginx/sonde-edge.sh.j2`.)
|
||||||
|
# GENERE par Set-OPS (role serveur_resolveur_site). Ne pas editer a la main.
|
||||||
|
#
|
||||||
|
# SONDE « resolution-locataires » — les locataires peuvent-ils encore resoudre ici ?
|
||||||
|
#
|
||||||
|
# PAS UNE ENVELOPPE DE GREFFON. Ce qu'on mesure ici n'a pas d'equivalent standard : « le
|
||||||
|
# resolveur accepte-t-il encore de repondre AUX RESEAUX DES TENANTS », et « repond-il a
|
||||||
|
# l'adresse ou ils frappent ». La mesurer depuis la machine elle-meme est le seul endroit
|
||||||
|
# ou on peut la lire sans traverser la frontiere.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
# LA LISTE ATTENDUE EST DERIVEE DU PLAN DU SITE, pas ecrite ici. C'est le meme calcul que
|
||||||
|
# l'assertion de `tasks/main.yml` — et c'est voulu : la sonde n'est pas un second avis,
|
||||||
|
# c'est la MEME exigence, rejouee quand plus personne ne regarde.
|
||||||
|
attendus=({% for s in serveur_resolveur_site_supernets_tenants | default([]) %}'{{ s }}' {% endfor %})
|
||||||
|
CONF={{ serveur_resolveur_site_sonde_config }}
|
||||||
|
TEMOIN='{{ serveur_resolveur_site_sonde_nom }}'
|
||||||
|
ADRESSE='{{ serveur_resolveur_site_sonde_adresse }}'
|
||||||
|
|
||||||
|
if (( ${#attendus[@]} == 0 )); then
|
||||||
|
# PAS UN SUCCES DEGUISE. Un site sans locataire declare est un etat legitime — mais le
|
||||||
|
# dire est preferable a rendre un vert qui laisserait croire qu'on a verifie quelque
|
||||||
|
# chose.
|
||||||
|
echo "Aucun supernet de locataire declare au plan du site : rien a verifier ici."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 1. CHAQUE SUPERNET DE LOCATAIRE EST-IL ENCORE ADMIS ?
|
||||||
|
#
|
||||||
|
# On lit la configuration POSEE, pas celle qu'on croit avoir posee. `unbound-control` ne
|
||||||
|
# rend pas la liste d'acces ; les fichiers, si — et ce sont eux qu'Unbound a charges.
|
||||||
|
manquants=()
|
||||||
|
for reseau in "${attendus[@]}"; do
|
||||||
|
grep -rhqE "^[[:space:]]*access-control:[[:space:]]+${reseau//./\\.}[[:space:]]+allow" "${CONF}" 2>/dev/null \
|
||||||
|
|| manquants+=("${reseau}")
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( ${#manquants[@]} > 0 )); then
|
||||||
|
echo "Locataire(s) non admis a resoudre ici : ${manquants[*]}. Leur amorcage lira « Echec temporaire dans la resolution du nom » — le message d'un DNS mort."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. ET REPOND-IL A L'ADRESSE OU LES LOCATAIRES FRAPPENT ?
|
||||||
|
#
|
||||||
|
# CE N'EST PAS LA SONDE `resolution` DE `serveur_resolveur` REJOUEE. Celle-la interroge
|
||||||
|
# 127.0.0.1 : elle dit « unbound traduit ». Un locataire, lui, frappe l'adresse de
|
||||||
|
# l'interface — et une ecoute peut changer sans que la boucle locale bouge. On l'a deja
|
||||||
|
# paye ailleurs : un `reload` ne reapplique PAS un changement d'adresse d'ecoute, et le
|
||||||
|
# service reste vert depuis sa propre machine.
|
||||||
|
#
|
||||||
|
# POURQUOI `dig` ET PAS `check_dns`. Le greffon existe, mais dans
|
||||||
|
# `monitoring-plugins-standard` — et ce paquet traine samba, smbclient et une pile SNMP
|
||||||
|
# sur CHAQUE machine de la flotte (mesure du 2026-09-14). `client_sante` installe
|
||||||
|
# `monitoring-plugins-basic`, 53 greffons, qui ne contient pas `check_dns`. Un greffon
|
||||||
|
# absent sort en 127, ce qui n'est pas un code Nagios : la sonde deviendrait illisible.
|
||||||
|
# `dig` vient de `dnsutils`, deja present sur un resolveur, et c'est ce qu'emploie
|
||||||
|
# `resolution.sh` a cote.
|
||||||
|
if ! dig +short +time=5 +tries=2 "@${ADRESSE}" "${TEMOIN}" A 2>/dev/null | grep -qE '.'; then
|
||||||
|
echo "Les ${#attendus[@]} locataires sont admis, mais rien ne se traduit a ${ADRESSE} — l'adresse ou ils frappent. La boucle locale peut tres bien repondre encore."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Resolveur du site : ${#attendus[@]} supernet(s) de locataire admis, et « ${TEMOIN} » se resout a ${ADRESSE}."
|
||||||
|
exit 0
|
||||||
|
|
@ -21,3 +21,11 @@ serveur_web_dorsal_utilisateur: "webapp"
|
||||||
# env dict de variables d'environnement (secrets via voute)
|
# env dict de variables d'environnement (secrets via voute)
|
||||||
# data_dir repertoire d'etat persistant (optionnel ; cree, possede par webapp)
|
# data_dir repertoire d'etat persistant (optionnel ; cree, possede par webapp)
|
||||||
serveur_web_dorsal_apps: []
|
serveur_web_dorsal_apps: []
|
||||||
|
|
||||||
|
# LES CODES QU'UNE WEBAPP SAINE PEUT RENDRE A LA RACINE, sans etre authentifie.
|
||||||
|
#
|
||||||
|
# 200 quand elle sert, 302 quand elle renvoie vers une page de connexion, 401/403 quand
|
||||||
|
# elle exige une identite, 404 quand la racine n'est pas une route — une API sous
|
||||||
|
# `/api/...` est parfaitement saine sans rien servir a `/`. Ce qui reste exclu : les 5xx
|
||||||
|
# et l'absence de reponse, c'est-a-dire l'app coincee ou morte.
|
||||||
|
serveur_web_dorsal_sonde_codes: "HTTP/1.1 200,HTTP/1.1 302,HTTP/1.1 401,HTTP/1.1 403,HTTP/1.1 404"
|
||||||
|
|
|
||||||
30
roles/serveur_web_dorsal/meta/supervision.yml
Normal file
30
roles/serveur_web_dorsal/meta/supervision.yml
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
---
|
||||||
|
# Supervision derivee du role. Voir docs/supervision-conception.md.
|
||||||
|
#
|
||||||
|
# CE QUE `client_sante` RAPPORTE DEJA : les unites systemd EN ECHEC de la machine. C'est
|
||||||
|
# la base, et elle ne suffit pas ici.
|
||||||
|
#
|
||||||
|
# UNE WEBAPP NE TOMBE PRESQUE JAMAIS EN « FAILED ». Elle se coince. Le processus vit,
|
||||||
|
# systemd la voit `active (running)`, le port est ouvert — et plus une requete n'aboutit :
|
||||||
|
# une boucle d'evenements bloquee, un verrou SQLite jamais relache, un pool d'attente
|
||||||
|
# sature. Pour systemd tout va bien ; pour l'utilisateur, le site ne repond plus.
|
||||||
|
#
|
||||||
|
# C'est pourquoi la sonde ne se contente pas de l'etat de l'unite : elle FRAPPE l'app.
|
||||||
|
#
|
||||||
|
# CE QU'ELLE MESURE, ET OU. Les apps ecoutent en `127.0.0.1:<port>`, derriere un nginx
|
||||||
|
# local, lui-meme derriere l'edge. La sonde frappe le port de l'APP — le maillon le plus
|
||||||
|
# profond. Si elle est verte et que le site ne repond pas, la panne est dans nginx ou a
|
||||||
|
# l'edge, et on l'a appris sans chercher.
|
||||||
|
#
|
||||||
|
# UNE SEULE SONDE POUR TOUTES LES APPS : une seule CAUSE D'ACTION, aller voir cet
|
||||||
|
# hebergeur. Elle nomme celle qui manque.
|
||||||
|
#
|
||||||
|
# TTL de 5400 s pour un porteur qui passe aux 15 min : trois passages manques avant la
|
||||||
|
# peremption. Le silence alerte autant que l'echec.
|
||||||
|
sondes:
|
||||||
|
- nom: apps-servies
|
||||||
|
ttl: 5400
|
||||||
|
raison: 'Chacune des webapps declarees repond-elle encore ? Une app ne tombe presque
|
||||||
|
jamais en « failed » — elle se coince : le processus vit, systemd la dit active, le
|
||||||
|
port est ouvert, et plus une requete n''aboutit. Le rapport d''unites en echec ne
|
||||||
|
verra jamais ca.'
|
||||||
|
|
@ -106,3 +106,24 @@
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.nom }}"
|
label: "{{ item.nom }}"
|
||||||
when: not ansible_check_mode
|
when: not ansible_check_mode
|
||||||
|
|
||||||
|
# LE ROLE QUI POSSEDE LA VERITE DEPOSE SA PROPRE SONDE. `client_sante` la fait tourner et
|
||||||
|
# pousse le resultat ; les greffons standard, eux, sont poses par le porteur.
|
||||||
|
#
|
||||||
|
# UNE WEBAPP NE TOMBE PRESQUE JAMAIS EN « FAILED » — elle se coince, et systemd la voit
|
||||||
|
# active. Le rapport d'unites en echec ne verra jamais ca : il faut frapper l'app.
|
||||||
|
- name: Assurer le repertoire des sondes de supervision
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /usr/local/lib/setops/sondes
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Deposer la sonde « apps-servies »
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: sonde-apps-servies.sh.j2
|
||||||
|
dest: /usr/local/lib/setops/sondes/apps-servies.sh
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0750"
|
||||||
|
|
|
||||||
68
roles/serveur_web_dorsal/templates/sonde-apps-servies.sh.j2
Normal file
68
roles/serveur_web_dorsal/templates/sonde-apps-servies.sh.j2
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
#jinja2: comment_start_string:'{=#', comment_end_string:'#=}'
|
||||||
|
#!/bin/bash
|
||||||
|
# (Le gabarit redefinit le delimiteur de commentaire Jinja : `${#tableau[@]}` contient
|
||||||
|
# la sequence `{#`, que Jinja lirait comme un debut de commentaire. Le shell en a
|
||||||
|
# besoin ; c'est donc Jinja qui cede. Meme geste que `client_sante/setops-sante.sh.j2`
|
||||||
|
# et `serveur_nginx/sonde-edge.sh.j2`.)
|
||||||
|
# GENERE par Set-OPS (role serveur_web_dorsal). Ne pas editer a la main.
|
||||||
|
#
|
||||||
|
# SONDE « apps-servies » — chacune des webapps declarees repond-elle encore ?
|
||||||
|
#
|
||||||
|
# UNE BOUCLE AUTOUR D'UN GREFFON. Chaque app est mesuree par `check_http` ; ce que la
|
||||||
|
# boucle ajoute est la LISTE des apps que cet hebergeur doit servir, qui vient du plan.
|
||||||
|
#
|
||||||
|
# DEUX LECTURES PAR APP, ET ELLES NE DISENT PAS LA MEME CHOSE :
|
||||||
|
# - l'unite systemd est-elle active ? -> l'app a-t-elle ete lancee
|
||||||
|
# - son port repond-il ? -> l'app fait-elle encore son travail
|
||||||
|
# La seconde est celle qui compte, et c'est justement celle qu'aucun rapport d'unites en
|
||||||
|
# echec ne fera jamais. La premiere reste utile pour NOMMER la cause : « pas demarree »
|
||||||
|
# et « demarree mais coincee » n'appellent pas le meme geste.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
noms=({% for a in serveur_web_dorsal_apps | default([]) %}'{{ a.nom }}' {% endfor %})
|
||||||
|
ports=({% for a in serveur_web_dorsal_apps | default([]) %}'{{ a.port }}' {% endfor %})
|
||||||
|
|
||||||
|
if (( ${#noms[@]} == 0 )); then
|
||||||
|
# PAS UN VERT MUET : un hebergeur sans app declaree est un etat legitime, et le dire
|
||||||
|
# vaut mieux qu'un succes qui laisserait croire qu'on a verifie quelque chose.
|
||||||
|
echo "Aucune webapp declaree sur cet hebergeur : rien a servir, rien a verifier."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
fautives=()
|
||||||
|
for i in "${!noms[@]}"; do
|
||||||
|
nom=${noms[$i]}
|
||||||
|
port=${ports[$i]}
|
||||||
|
|
||||||
|
etat=$(systemctl is-active "${nom}.service" 2>/dev/null || true)
|
||||||
|
if [[ "${etat}" != "active" ]]; then
|
||||||
|
fautives+=("${nom} : unite ${etat:-introuvable}")
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# LE CODE ATTENDU N'EST PAS 200. Une webapp peut parfaitement rendre 302 (redirection
|
||||||
|
# vers une page de connexion), 401 ou 403 a la racine sans etre en panne. Ce qu'on
|
||||||
|
# exclut, ce sont les 5xx et l'absence de reponse — c'est-a-dire l'app coincee ou morte.
|
||||||
|
sortie=$(/usr/lib/nagios/plugins/check_http -H 127.0.0.1 -p "${port}" -u / \
|
||||||
|
-e '{{ serveur_web_dorsal_sonde_codes }}' -t 10 2>&1)
|
||||||
|
code=$?
|
||||||
|
# LE GREFFON REND PARFOIS DEUX LIGNES — la cause, puis son verdict. Icinga ne
|
||||||
|
# garde que la premiere pour resume : sans repliage, la moitie du message
|
||||||
|
# disparait de la vue qu'on regarde en premier.
|
||||||
|
resume=${sortie%%|*}
|
||||||
|
resume=${resume//$'\n'/ }
|
||||||
|
if (( code != 0 )); then
|
||||||
|
# LE MESSAGE DU GREFFON EST CONSERVE : c'est lui qui distingue « socket ferme » de
|
||||||
|
# « delai depasse » — l'app morte de l'app coincee, et le geste n'est pas le meme.
|
||||||
|
fautives+=("${nom} : active mais ${resume}")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( ${#fautives[@]} > 0 )); then
|
||||||
|
liste=$(printf '%s; ' "${fautives[@]}"); liste=${liste%; }
|
||||||
|
echo "Webapp(s) qui ne repondent plus — ${liste}"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Webapps : ${#noms[@]} servie(s) — ${noms[*]}."
|
||||||
|
exit 0
|
||||||
|
|
@ -21,3 +21,9 @@ serveur_web_frontal_csp_defaut: >-
|
||||||
# sous_dossier sous-dossier du depot a servir comme racine (ex: public)
|
# sous_dossier sous-dossier du depot a servir comme racine (ex: public)
|
||||||
# csp Content-Security-Policy specifique (optionnel)
|
# csp Content-Security-Policy specifique (optionnel)
|
||||||
serveur_web_frontal_sites: []
|
serveur_web_frontal_sites: []
|
||||||
|
|
||||||
|
# LE PORT DES VHOSTS LOCAUX. Les sites statiques ecoutent en clair sur cette machine ;
|
||||||
|
# c'est l'edge qui termine le TLS devant. La sonde mesure donc ce que l'edge ira chercher,
|
||||||
|
# pas ce que le visiteur voit — et c'est le bon endroit : si l'edge est vert et le site
|
||||||
|
# muet, la panne est ici.
|
||||||
|
serveur_web_frontal_sonde_port: 80
|
||||||
|
|
|
||||||
33
roles/serveur_web_frontal/meta/supervision.yml
Normal file
33
roles/serveur_web_frontal/meta/supervision.yml
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
---
|
||||||
|
# Supervision derivee du role. Voir docs/supervision-conception.md.
|
||||||
|
#
|
||||||
|
# CE QUE `serveur_nginx` NE PEUT PAS DIRE. L'edge a sa sonde, et elle repond « nginx sert
|
||||||
|
# encore ». Ici la question est autre : CHACUN des sites declares se sert-il vraiment ?
|
||||||
|
#
|
||||||
|
# LA PANNE PROPRE A CE ROLE EST SILENCIEUSE, et elle vient du fait que le contenu n'est
|
||||||
|
# pas dans le role : il arrive par `git clone`/`pull` depuis une forge. Trois facons de
|
||||||
|
# perdre un site sans que nginx bronche :
|
||||||
|
#
|
||||||
|
# 1. LE DEPOT A CHANGE DE BRANCHE OU DE NOM — le pull echoue, le contenu reste celui de
|
||||||
|
# la derniere fois qui a marche. Le site est en ligne, et vieux.
|
||||||
|
# 2. LE SOUS-DOSSIER SERVI A BOUGE (`public/` renomme en amont) — nginx rend 404 ou 403
|
||||||
|
# sur un repertoire qui existe mais ne contient plus d'index.
|
||||||
|
# 3. LE CLONE EST VIDE — un depot neuf, ou une reconstruction qui n'a rien ramene. Meme
|
||||||
|
# lecture que la forge du site le 2026-09-14 : tout repond, il n'y a rien dedans.
|
||||||
|
#
|
||||||
|
# Dans les trois cas l'edge est vert, le vhost existe, le port ecoute. Seul un GET sur le
|
||||||
|
# `server_name` du site dit la verite.
|
||||||
|
#
|
||||||
|
# UNE SEULE SONDE POUR TOUS LES SITES, parce qu'il n'y a qu'une CAUSE D'ACTION : aller
|
||||||
|
# voir cet hebergeur. Elle nomme celui qui manque — c'est ce dont on a besoin pour agir,
|
||||||
|
# et une sonde par site ferait N services a definir pour un seul geste.
|
||||||
|
#
|
||||||
|
# TTL de 5400 s pour un porteur qui passe aux 15 min : trois passages manques avant la
|
||||||
|
# peremption. Le silence alerte autant que l'echec.
|
||||||
|
sondes:
|
||||||
|
- nom: sites-servis
|
||||||
|
ttl: 5400
|
||||||
|
raison: 'Chacun des sites statiques declares se sert-il encore ? Le contenu vient
|
||||||
|
d''un depot git : une branche renommee, un sous-dossier deplace ou un clone vide
|
||||||
|
laissent nginx parfaitement vert avec un site perime, vide, ou en 404. Seul un GET
|
||||||
|
sur le `server_name` de chaque site le dit.'
|
||||||
|
|
@ -54,3 +54,25 @@
|
||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.nom }}"
|
label: "{{ item.nom }}"
|
||||||
notify: Recharger nginx serveur_web_frontal
|
notify: Recharger nginx serveur_web_frontal
|
||||||
|
|
||||||
|
# LE ROLE QUI POSSEDE LA VERITE DEPOSE SA PROPRE SONDE. `client_sante` la fait tourner et
|
||||||
|
# pousse le resultat ; les greffons standard, eux, sont poses par le porteur.
|
||||||
|
#
|
||||||
|
# LE CONTENU DE CE ROLE N'EST PAS DANS CE ROLE : il arrive par git depuis une forge. Une
|
||||||
|
# branche renommee, un sous-dossier deplace ou un clone vide laissent nginx parfaitement
|
||||||
|
# vert. Seul un GET par `server_name` le dit.
|
||||||
|
- name: Assurer le repertoire des sondes de supervision
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /usr/local/lib/setops/sondes
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: Deposer la sonde « sites-servis »
|
||||||
|
ansible.builtin.template:
|
||||||
|
src: sonde-sites-servis.sh.j2
|
||||||
|
dest: /usr/local/lib/setops/sondes/sites-servis.sh
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0750"
|
||||||
|
|
|
||||||
57
roles/serveur_web_frontal/templates/sonde-sites-servis.sh.j2
Normal file
57
roles/serveur_web_frontal/templates/sonde-sites-servis.sh.j2
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
#jinja2: comment_start_string:'{=#', comment_end_string:'#=}'
|
||||||
|
#!/bin/bash
|
||||||
|
# (Le gabarit redefinit le delimiteur de commentaire Jinja : `${#tableau[@]}` contient
|
||||||
|
# la sequence `{#`, que Jinja lirait comme un debut de commentaire. Le shell en a
|
||||||
|
# besoin ; c'est donc Jinja qui cede. Meme geste que `client_sante/setops-sante.sh.j2`
|
||||||
|
# et `serveur_nginx/sonde-edge.sh.j2`.)
|
||||||
|
# GENERE par Set-OPS (role serveur_web_frontal). Ne pas editer a la main.
|
||||||
|
#
|
||||||
|
# SONDE « sites-servis » — chacun des sites declares se sert-il encore ?
|
||||||
|
#
|
||||||
|
# UNE BOUCLE AUTOUR D'UN GREFFON, PAS UN GREFFON REECRIT. Chaque site est mesure par
|
||||||
|
# `check_http` — qui gere les delais, les redirections et vingt ans de cas limites. Ce que
|
||||||
|
# la boucle ajoute est la seule chose que le greffon ne sait pas : la LISTE des sites que
|
||||||
|
# cet hebergeur doit servir, qui vient du plan.
|
||||||
|
#
|
||||||
|
# `-H <server_name> -I 127.0.0.1` : on frappe la machine en boucle locale, mais avec
|
||||||
|
# l'en-tete `Host` du site. C'est ainsi que nginx choisit son vhost — sans cet en-tete on
|
||||||
|
# mesurerait le serveur par defaut, c'est-a-dire n'importe quoi d'autre.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
PORT={{ serveur_web_frontal_sonde_port }}
|
||||||
|
noms=({% for s in serveur_web_frontal_sites | default([]) %}'{{ s.server_name }}' {% endfor %})
|
||||||
|
etiquettes=({% for s in serveur_web_frontal_sites | default([]) %}'{{ s.nom }}' {% endfor %})
|
||||||
|
|
||||||
|
if (( ${#noms[@]} == 0 )); then
|
||||||
|
# PAS UN VERT MUET. Un hebergeur sans site declare est un etat legitime — le dire vaut
|
||||||
|
# mieux que rendre un succes qui laisserait croire qu'on a verifie quelque chose.
|
||||||
|
echo "Aucun site declare sur cet hebergeur : rien a servir, rien a verifier."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
muets=()
|
||||||
|
for i in "${!noms[@]}"; do
|
||||||
|
nom=${noms[$i]}
|
||||||
|
etiquette=${etiquettes[$i]}
|
||||||
|
sortie=$(/usr/lib/nagios/plugins/check_http -H "${nom}" -I 127.0.0.1 -p "${PORT}" -u / -t 10 2>&1)
|
||||||
|
code=$?
|
||||||
|
# LE GREFFON REND PARFOIS DEUX LIGNES — la cause, puis son verdict. Icinga ne
|
||||||
|
# garde que la premiere pour resume : sans repliage, la moitie du message
|
||||||
|
# disparait de la vue qu'on regarde en premier.
|
||||||
|
resume=${sortie%%|*}
|
||||||
|
resume=${resume//$'\n'/ }
|
||||||
|
if (( code != 0 )); then
|
||||||
|
# LE MESSAGE DU GREFFON EST CONSERVE, pas resume. C'est lui qui distingue un 404 d'un
|
||||||
|
# delai depasse ou d'un socket ferme — et ces trois-la n'appellent pas le meme geste.
|
||||||
|
muets+=("${etiquette} (${nom}) : ${resume}")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( ${#muets[@]} > 0 )); then
|
||||||
|
liste=$(printf '%s; ' "${muets[@]}"); liste=${liste%; }
|
||||||
|
echo "Site(s) qui ne se servent plus — ${liste}"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Sites statiques : ${#noms[@]} servi(s) — ${etiquettes[*]}."
|
||||||
|
exit 0
|
||||||
|
|
@ -2105,6 +2105,67 @@ def preuve_carte_dit_vrai() -> tuple[bool, str]:
|
||||||
f"{len(_carte_chiffres_mesures())} chiffres correspondent a la mesure.")
|
f"{len(_carte_chiffres_mesures())} chiffres correspondent a la mesure.")
|
||||||
|
|
||||||
|
|
||||||
|
def preuve_gabarits_shell_rendent() -> tuple[bool, str]:
|
||||||
|
"""Aucun gabarit shell ne contient de sequence que Jinja lirait autrement.
|
||||||
|
|
||||||
|
LE DEFAUT EST INVISIBLE JUSQU'AU DEPLOIEMENT, ET IL EST FACILE A REFAIRE.
|
||||||
|
|
||||||
|
`${#tableau[@]}` est la SEULE facon d'obtenir la longueur d'un tableau en bash. Elle
|
||||||
|
contient `{#`, que Jinja lit comme un debut de commentaire — et le rendu s'arrete sur
|
||||||
|
|
||||||
|
TemplateSyntaxError: Missing end of comment tag
|
||||||
|
|
||||||
|
Rien ne le signale avant : le fichier est valide en shell, valide a la lecture, et
|
||||||
|
`--syntax-check` ne rend pas les gabarits. La panne arrive sur la machine, pendant un
|
||||||
|
deploiement, sur une tache `template` qui marchait la veille.
|
||||||
|
|
||||||
|
LE DEPOT CONNAIT DEJA LE REMEDE, et l'applique trois fois : une en-tete
|
||||||
|
`#jinja2: comment_start_string:'{=#', comment_end_string:'#=}'` en toute premiere
|
||||||
|
ligne. Le shell garde `${#...}`, c'est Jinja qui cede.
|
||||||
|
|
||||||
|
CE QUI MANQUAIT, C'EST LA GARDE. Le remede etait pose la ou quelqu'un s'etait fait
|
||||||
|
prendre, et nulle part ailleurs. Le 2026-09-14, quatre sondes neuves l'ont refait
|
||||||
|
d'un coup — quatre gabarits que le harnais declarait conformes et qu'Ansible aurait
|
||||||
|
refuse de rendre.
|
||||||
|
|
||||||
|
ELLE MESURE LE RENDU, PAS LE MOTIF. Chercher `${#` suffirait a attraper ce cas-la,
|
||||||
|
mais pas le suivant : Jinja a d'autres sequences (`{%`, `{{`) qu'un script peut
|
||||||
|
produire par accident. On rend donc chaque gabarit pour de vrai, avec les memes
|
||||||
|
delimiteurs qu'Ansible en tirerait — et on ne juge pas les VARIABLES manquantes, qui
|
||||||
|
sont normales hors d'un play.
|
||||||
|
"""
|
||||||
|
import re as _re
|
||||||
|
try:
|
||||||
|
import jinja2
|
||||||
|
except ImportError:
|
||||||
|
return True, "jinja2 absent du controleur — rendu des gabarits non mesure."
|
||||||
|
|
||||||
|
fautes, mesures = [], 0
|
||||||
|
for gabarit in sorted((RACINE / "roles").glob("*/templates/*.j2")):
|
||||||
|
brut = gabarit.read_text(encoding="utf-8", errors="replace")
|
||||||
|
# ANSIBLE LIT L'EN-TETE `#jinja2:` ET L'APPLIQUE. On fait pareil, sinon la garde
|
||||||
|
# refuserait precisement les gabarits qui portent deja le remede.
|
||||||
|
options: dict = {}
|
||||||
|
entete = _re.match(r"#jinja2:\s*(.*)\n", brut)
|
||||||
|
if entete:
|
||||||
|
for cle, val in _re.findall(r"(\w+):\s*'([^']*)'", entete.group(1)):
|
||||||
|
options[cle] = val
|
||||||
|
brut = brut[entete.end():]
|
||||||
|
try:
|
||||||
|
jinja2.Environment(**options).parse(brut)
|
||||||
|
except jinja2.TemplateSyntaxError as e:
|
||||||
|
role = gabarit.parent.parent.name
|
||||||
|
fautes.append(f"{role}/{gabarit.name} l.{e.lineno} : {e.message}")
|
||||||
|
mesures += 1
|
||||||
|
|
||||||
|
if fautes:
|
||||||
|
return False, ("Gabarits qu'Ansible refuserait de rendre :\n - "
|
||||||
|
+ "\n - ".join(fautes)
|
||||||
|
+ "\n Remede : poser en PREMIERE ligne du gabarit"
|
||||||
|
"\n #jinja2: comment_start_string:'{=#', comment_end_string:'#=}'")
|
||||||
|
return True, f"{mesures} gabarits de role : tous se rendent."
|
||||||
|
|
||||||
|
|
||||||
def preuve_parametres_clone_traversent() -> tuple[bool, str]:
|
def preuve_parametres_clone_traversent() -> tuple[bool, str]:
|
||||||
"""Tout `SETOPS_*` que le Makefile transmet au clonage a bien une SOURCE.
|
"""Tout `SETOPS_*` que le Makefile transmet au clonage a bien une SOURCE.
|
||||||
|
|
||||||
|
|
@ -3812,6 +3873,8 @@ PREUVES: list[dict] = [
|
||||||
"refs": [], "func": preuve_gabarit_une_seule_declaration},
|
"refs": [], "func": preuve_gabarit_une_seule_declaration},
|
||||||
{"id": "P75", "titre": "Les parametres de clonage traversent les trois maillons",
|
{"id": "P75", "titre": "Les parametres de clonage traversent les trois maillons",
|
||||||
"refs": [], "func": preuve_parametres_clone_traversent},
|
"refs": [], "func": preuve_parametres_clone_traversent},
|
||||||
|
{"id": "P76", "titre": "Tout gabarit de role se rend vraiment",
|
||||||
|
"refs": [], "func": preuve_gabarits_shell_rendent},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
graph LR
|
graph LR
|
||||||
R["<b>backup_site</b>"]
|
R["<b>backup_site</b>"]
|
||||||
E0["site"] -->|"22 · ssh"| R
|
E0["site"] -->|"22 · ssh"| R
|
||||||
|
R -.->|"sonde « depot-locataires »"| ICINGA[["Icinga"]]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Qui lui parle
|
## Qui lui parle
|
||||||
|
|
@ -19,7 +20,9 @@ graph LR
|
||||||
|
|
||||||
## Ce qu'il rend à la supervision
|
## Ce qu'il rend à la supervision
|
||||||
|
|
||||||
*Aucune sonde déclarée. Un service qui porte de l'état et n'en déclare aucune mérite qu'on se demande pourquoi — voir `docs/supervision-conception.md`.*
|
| sonde | TTL | ce qu'elle voit |
|
||||||
|
|---|---|---|
|
||||||
|
| `depot-locataires` | 5400 s | Les depots des locataires sont-ils toujours etanches, et reste-t-il de la place devant eux ? Un mode qui glisse laisse un locataire lire ou effacer les instantanes d'un autre, sans qu'aucune sauvegarde n'echoue. Un depot qui se remplit echoue AU MILIEU d'un instantane, chez le locataire, et sa panne s'y lit comme une erreur reseau. |
|
||||||
|
|
||||||
## Ce qu'il expose en séries
|
## Ce qu'il expose en séries
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
graph LR
|
graph LR
|
||||||
R["<b>cache_site</b>"]
|
R["<b>cache_site</b>"]
|
||||||
E0["site"] -->|"3142 · clair"| R
|
E0["site"] -->|"3142 · clair"| R
|
||||||
|
R -.->|"sonde « cache-site-racine »"| ICINGA[["Icinga"]]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Qui lui parle
|
## Qui lui parle
|
||||||
|
|
@ -19,7 +20,9 @@ graph LR
|
||||||
|
|
||||||
## Ce qu'il rend à la supervision
|
## Ce qu'il rend à la supervision
|
||||||
|
|
||||||
*Aucune sonde déclarée. Un service qui porte de l'état et n'en déclare aucune mérite qu'on se demande pourquoi — voir `docs/supervision-conception.md`.*
|
| sonde | TTL | ce qu'elle voit |
|
||||||
|
|---|---|---|
|
||||||
|
| `cache-site-racine` | 5400 s | Ce cache est-il toujours la RACINE de la chaine, et peut-il encore remplir ? Un cache chaine sur lui-meme ou coupe de Debian repond parfaitement — il sert ce qu'il a deja. La panne n'apparait qu'au premier `apt update` d'un ecosysteme neuf, c'est-a-dire au pire moment. |
|
||||||
|
|
||||||
## Ce qu'il expose en séries
|
## Ce qu'il expose en séries
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
graph LR
|
graph LR
|
||||||
R["<b>forge_site</b>"]
|
R["<b>forge_site</b>"]
|
||||||
E0["site"] -->|"443 · tls-requis"| R
|
E0["site"] -->|"443 · tls-requis"| R
|
||||||
|
R -.->|"sonde « genome-servi »"| ICINGA[["Icinga"]]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Qui lui parle
|
## Qui lui parle
|
||||||
|
|
@ -19,7 +20,9 @@ graph LR
|
||||||
|
|
||||||
## Ce qu'il rend à la supervision
|
## Ce qu'il rend à la supervision
|
||||||
|
|
||||||
*Aucune sonde déclarée. Un service qui porte de l'état et n'en déclare aucune mérite qu'on se demande pourquoi — voir `docs/supervision-conception.md`.*
|
| sonde | TTL | ce qu'elle voit |
|
||||||
|
|---|---|---|
|
||||||
|
| `genome-servi` | 5400 s | Cette forge a-t-elle encore un genome a servir ? Une forge vide repond parfaitement : son API marche, son Git est lisible, elle n'a simplement plus rien dedans. Mesure du 2026-09-14 — le wiki du site avait zero commit depuis une reconstruction, et rien ne l'avait signale. Un ecosysteme neuf clone alors un arbre sans fichiers. |
|
||||||
|
|
||||||
## Ce qu'il expose en séries
|
## Ce qu'il expose en séries
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
graph LR
|
graph LR
|
||||||
R["<b>icingaweb2</b>"]
|
R["<b>icingaweb2</b>"]
|
||||||
E0["edge"] -->|"8080 · clair"| R
|
E0["edge"] -->|"8080 · clair"| R
|
||||||
|
R -.->|"sonde « console »"| ICINGA[["Icinga"]]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Qui lui parle
|
## Qui lui parle
|
||||||
|
|
@ -19,7 +20,9 @@ graph LR
|
||||||
|
|
||||||
## Ce qu'il rend à la supervision
|
## Ce qu'il rend à la supervision
|
||||||
|
|
||||||
*Aucune sonde déclarée. Un service qui porte de l'état et n'en déclare aucune mérite qu'on se demande pourquoi — voir `docs/supervision-conception.md`.*
|
| sonde | TTL | ce qu'elle voit |
|
||||||
|
|---|---|---|
|
||||||
|
| `console` | 5400 s | La console de supervision repond-elle encore ? Si elle meurt, le moteur continue de collecter et tous les verdicts restent verts : le systeme est en parfaite sante et l'exploitant est aveugle. C'est une panne sans symptome, qu'on decouvre en cherchant a diagnostiquer autre chose. |
|
||||||
|
|
||||||
## Ce qu'il expose en séries
|
## Ce qu'il expose en séries
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
```mermaid
|
```mermaid
|
||||||
graph LR
|
graph LR
|
||||||
R["<b>ops_site</b>"]
|
R["<b>ops_site</b>"]
|
||||||
|
R -.->|"sonde « pouvoir-materialiser »"| ICINGA[["Icinga"]]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Qui lui parle
|
## Qui lui parle
|
||||||
|
|
@ -16,7 +17,9 @@ graph LR
|
||||||
|
|
||||||
## Ce qu'il rend à la supervision
|
## Ce qu'il rend à la supervision
|
||||||
|
|
||||||
*Aucune sonde déclarée. Un service qui porte de l'état et n'en déclare aucune mérite qu'on se demande pourquoi — voir `docs/supervision-conception.md`.*
|
| sonde | TTL | ce qu'elle voit |
|
||||||
|
|---|---|---|
|
||||||
|
| `pouvoir-materialiser` | 5400 s | Le runner du site peut-il encore materialiser, et son secret est-il toujours protege ? La carte de la fabric, la voute du site et sa cle ne font tomber aucun service en disparaissant — on s'en apercoit quand un ecosysteme doit naitre. Et une voute dechiffree en transit ne fait echouer aucun deploiement : Ansible la lit tres bien, elle expose simplement tout. |
|
||||||
|
|
||||||
## Ce qu'il expose en séries
|
## Ce qu'il expose en séries
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
graph LR
|
graph LR
|
||||||
R["<b>resolveur_site</b>"]
|
R["<b>resolveur_site</b>"]
|
||||||
E0["site"] -->|"53 · clair"| R
|
E0["site"] -->|"53 · clair"| R
|
||||||
|
R -.->|"sonde « resolution-locataires »"| ICINGA[["Icinga"]]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Qui lui parle
|
## Qui lui parle
|
||||||
|
|
@ -20,7 +21,9 @@ graph LR
|
||||||
|
|
||||||
## Ce qu'il rend à la supervision
|
## Ce qu'il rend à la supervision
|
||||||
|
|
||||||
*Aucune sonde déclarée. Un service qui porte de l'état et n'en déclare aucune mérite qu'on se demande pourquoi — voir `docs/supervision-conception.md`.*
|
| sonde | TTL | ce qu'elle voit |
|
||||||
|
|---|---|---|
|
||||||
|
| `resolution-locataires` | 5400 s | Les locataires du site sont-ils toujours admis a resoudre ici ? Un supernet absent de la liste d'autorisation ne provoque pas un refus : Unbound laisse tomber la requete, et le locataire lit « Echec temporaire dans la resolution du nom » — le message d'un DNS mort, alors que le DNS va tres bien. |
|
||||||
|
|
||||||
## Ce qu'il expose en séries
|
## Ce qu'il expose en séries
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
graph LR
|
graph LR
|
||||||
R["<b>web_dorsal</b>"]
|
R["<b>web_dorsal</b>"]
|
||||||
E0["edge"] -->|"80 · clair"| R
|
E0["edge"] -->|"80 · clair"| R
|
||||||
|
R -.->|"sonde « apps-servies »"| ICINGA[["Icinga"]]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Qui lui parle
|
## Qui lui parle
|
||||||
|
|
@ -19,7 +20,9 @@ graph LR
|
||||||
|
|
||||||
## Ce qu'il rend à la supervision
|
## Ce qu'il rend à la supervision
|
||||||
|
|
||||||
*Aucune sonde déclarée. Un service qui porte de l'état et n'en déclare aucune mérite qu'on se demande pourquoi — voir `docs/supervision-conception.md`.*
|
| sonde | TTL | ce qu'elle voit |
|
||||||
|
|---|---|---|
|
||||||
|
| `apps-servies` | 5400 s | Chacune des webapps declarees repond-elle encore ? Une app ne tombe presque jamais en « failed » — elle se coince : le processus vit, systemd la dit active, le port est ouvert, et plus une requete n'aboutit. Le rapport d'unites en echec ne verra jamais ca. |
|
||||||
|
|
||||||
## Ce qu'il expose en séries
|
## Ce qu'il expose en séries
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
graph LR
|
graph LR
|
||||||
R["<b>web_frontal</b>"]
|
R["<b>web_frontal</b>"]
|
||||||
E0["edge"] -->|"80 · clair"| R
|
E0["edge"] -->|"80 · clair"| R
|
||||||
|
R -.->|"sonde « sites-servis »"| ICINGA[["Icinga"]]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Qui lui parle
|
## Qui lui parle
|
||||||
|
|
@ -19,7 +20,9 @@ graph LR
|
||||||
|
|
||||||
## Ce qu'il rend à la supervision
|
## Ce qu'il rend à la supervision
|
||||||
|
|
||||||
*Aucune sonde déclarée. Un service qui porte de l'état et n'en déclare aucune mérite qu'on se demande pourquoi — voir `docs/supervision-conception.md`.*
|
| sonde | TTL | ce qu'elle voit |
|
||||||
|
|---|---|---|
|
||||||
|
| `sites-servis` | 5400 s | Chacun des sites statiques declares se sert-il encore ? Le contenu vient d'un depot git : une branche renommee, un sous-dossier deplace ou un clone vide laissent nginx parfaitement vert avec un site perime, vide, ou en 404. Seul un GET sur le `server_name` de chaque site le dit. |
|
||||||
|
|
||||||
## Ce qu'il expose en séries
|
## Ce qu'il expose en séries
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,17 +40,17 @@ Chaque fiche est construite depuis ce que le rôle **déclare lui-même** — `m
|
||||||
| [`resoudre_politique_mdp`](Rôle-resoudre_politique_mdp) | — | — | — |
|
| [`resoudre_politique_mdp`](Rôle-resoudre_politique_mdp) | — | — | — |
|
||||||
| [`serveur_artefacts`](Rôle-serveur_artefacts) | 1 | 2 | — |
|
| [`serveur_artefacts`](Rôle-serveur_artefacts) | 1 | 2 | — |
|
||||||
| [`serveur_backup`](Rôle-serveur_backup) | 1 | 1 | — |
|
| [`serveur_backup`](Rôle-serveur_backup) | 1 | 1 | — |
|
||||||
| [`serveur_backup_site`](Rôle-serveur_backup_site) | 1 | — | — |
|
| [`serveur_backup_site`](Rôle-serveur_backup_site) | 1 | 1 | — |
|
||||||
| [`serveur_cache_site`](Rôle-serveur_cache_site) | 1 | — | — |
|
| [`serveur_cache_site`](Rôle-serveur_cache_site) | 1 | 1 | — |
|
||||||
| [`serveur_collabora`](Rôle-serveur_collabora) | 2 | 1 | — |
|
| [`serveur_collabora`](Rôle-serveur_collabora) | 2 | 1 | — |
|
||||||
| [`serveur_debian`](Rôle-serveur_debian) | 3 | 2 | — |
|
| [`serveur_debian`](Rôle-serveur_debian) | 3 | 2 | — |
|
||||||
| [`serveur_dovecot`](Rôle-serveur_dovecot) | 3 | 1 | — |
|
| [`serveur_dovecot`](Rôle-serveur_dovecot) | 3 | 1 | — |
|
||||||
| [`serveur_durci`](Rôle-serveur_durci) | — | 1 | — |
|
| [`serveur_durci`](Rôle-serveur_durci) | — | 1 | — |
|
||||||
| [`serveur_forge_site`](Rôle-serveur_forge_site) | 1 | — | — |
|
| [`serveur_forge_site`](Rôle-serveur_forge_site) | 1 | 1 | — |
|
||||||
| [`serveur_forgejo`](Rôle-serveur_forgejo) | 2 | 1 | — |
|
| [`serveur_forgejo`](Rôle-serveur_forgejo) | 2 | 1 | — |
|
||||||
| [`serveur_grafana`](Rôle-serveur_grafana) | 1 | 1 | — |
|
| [`serveur_grafana`](Rôle-serveur_grafana) | 1 | 1 | — |
|
||||||
| [`serveur_icinga`](Rôle-serveur_icinga) | 4 | 1 | — |
|
| [`serveur_icinga`](Rôle-serveur_icinga) | 4 | 1 | — |
|
||||||
| [`serveur_icingaweb2`](Rôle-serveur_icingaweb2) | 1 | — | — |
|
| [`serveur_icingaweb2`](Rôle-serveur_icingaweb2) | 1 | 1 | — |
|
||||||
| [`serveur_keycloak`](Rôle-serveur_keycloak) | 1 | 1 | — |
|
| [`serveur_keycloak`](Rôle-serveur_keycloak) | 1 | 1 | — |
|
||||||
| [`serveur_loki`](Rôle-serveur_loki) | 4 | 2 | — |
|
| [`serveur_loki`](Rôle-serveur_loki) | 4 | 2 | — |
|
||||||
| [`serveur_nextcloud`](Rôle-serveur_nextcloud) | 1 | 1 | — |
|
| [`serveur_nextcloud`](Rôle-serveur_nextcloud) | 1 | 1 | — |
|
||||||
|
|
@ -58,7 +58,7 @@ Chaque fiche est construite depuis ce que le rôle **déclare lui-même** — `m
|
||||||
| [`serveur_oauth2_proxy`](Rôle-serveur_oauth2_proxy) | 1 | 1 | — |
|
| [`serveur_oauth2_proxy`](Rôle-serveur_oauth2_proxy) | 1 | 1 | — |
|
||||||
| [`serveur_openldap`](Rôle-serveur_openldap) | 2 | 1 | — |
|
| [`serveur_openldap`](Rôle-serveur_openldap) | 2 | 1 | — |
|
||||||
| [`serveur_ops`](Rôle-serveur_ops) | — | 1 | — |
|
| [`serveur_ops`](Rôle-serveur_ops) | — | 1 | — |
|
||||||
| [`serveur_ops_site`](Rôle-serveur_ops_site) | — | — | — |
|
| [`serveur_ops_site`](Rôle-serveur_ops_site) | — | 1 | — |
|
||||||
| [`serveur_ops_tenant`](Rôle-serveur_ops_tenant) | 1 | 1 | — |
|
| [`serveur_ops_tenant`](Rôle-serveur_ops_tenant) | 1 | 1 | — |
|
||||||
| [`serveur_postfix`](Rôle-serveur_postfix) | 2 | 1 | — |
|
| [`serveur_postfix`](Rôle-serveur_postfix) | 2 | 1 | — |
|
||||||
| [`serveur_postgresql`](Rôle-serveur_postgresql) | 2 | 1 | oui |
|
| [`serveur_postgresql`](Rôle-serveur_postgresql) | 2 | 1 | oui |
|
||||||
|
|
@ -66,11 +66,11 @@ Chaque fiche est construite depuis ce que le rôle **déclare lui-même** — `m
|
||||||
| [`serveur_prometheus`](Rôle-serveur_prometheus) | 1 | 1 | — |
|
| [`serveur_prometheus`](Rôle-serveur_prometheus) | 1 | 1 | — |
|
||||||
| [`serveur_redis`](Rôle-serveur_redis) | 1 | 1 | — |
|
| [`serveur_redis`](Rôle-serveur_redis) | 1 | 1 | — |
|
||||||
| [`serveur_resolveur`](Rôle-serveur_resolveur) | 2 | 1 | — |
|
| [`serveur_resolveur`](Rôle-serveur_resolveur) | 2 | 1 | — |
|
||||||
| [`serveur_resolveur_site`](Rôle-serveur_resolveur_site) | 2 | — | — |
|
| [`serveur_resolveur_site`](Rôle-serveur_resolveur_site) | 2 | 1 | — |
|
||||||
| [`serveur_rspamd`](Rôle-serveur_rspamd) | 2 | 1 | — |
|
| [`serveur_rspamd`](Rôle-serveur_rspamd) | 2 | 1 | — |
|
||||||
| [`serveur_step_ca`](Rôle-serveur_step_ca) | 1 | 1 | — |
|
| [`serveur_step_ca`](Rôle-serveur_step_ca) | 1 | 1 | — |
|
||||||
| [`serveur_web_dorsal`](Rôle-serveur_web_dorsal) | 1 | — | — |
|
| [`serveur_web_dorsal`](Rôle-serveur_web_dorsal) | 1 | 1 | — |
|
||||||
| [`serveur_web_frontal`](Rôle-serveur_web_frontal) | 1 | — | — |
|
| [`serveur_web_frontal`](Rôle-serveur_web_frontal) | 1 | 1 | — |
|
||||||
| [`ssh_baseline`](Rôle-ssh_baseline) | — | — | — |
|
| [`ssh_baseline`](Rôle-ssh_baseline) | — | — | — |
|
||||||
| [`ssh_hardening`](Rôle-ssh_hardening) | — | — | — |
|
| [`ssh_hardening`](Rôle-ssh_hardening) | — | — | — |
|
||||||
| [`sudo_ansible`](Rôle-sudo_ansible) | — | — | — |
|
| [`sudo_ansible`](Rôle-sudo_ansible) | — | — | — |
|
||||||
|
|
@ -84,7 +84,7 @@ Chaque fiche est construite depuis ce que le rôle **déclare lui-même** — `m
|
||||||
Sur **68** rôles :
|
Sur **68** rôles :
|
||||||
|
|
||||||
- **36** ne déclarent aucun flux entrant
|
- **36** ne déclarent aucun flux entrant
|
||||||
- **40** ne déclarent aucune sonde
|
- **32** ne déclarent aucune sonde
|
||||||
- **67** ne déclarent aucune métrique
|
- **67** ne déclarent aucune métrique
|
||||||
|
|
||||||
Ces nombres ne sont pas tous des dettes : un rôle de durcissement n'a rien à exposer, et beaucoup de `client_*` n'ont aucun verdict à rendre. Mais un service qui porte de l'état et ne déclare rien mérite qu'on se demande pourquoi.
|
Ces nombres ne sont pas tous des dettes : un rôle de durcissement n'a rien à exposer, et beaucoup de `client_*` n'ont aucun verdict à rendre. Mais un service qui porte de l'état et ne déclare rien mérite qu'on se demande pourquoi.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue