feat(monitoring) : les journaux des conteneurs remontent à nouveau #26

Merged
thystips merged 2 commits from feat/container-logs into main 2026-08-09 18:18:32 +02:00
Owner

Un conteneur système a son propre journald : depuis que les services y sont descendus (PR #24 et #25), l'Alloy des nœuds ne voyait plus rien d'eux. Ni un Caddy qui refuse de démarrer, ni une authentification AppRole refusée, ni un rendu de template en erreur — c'est-à-dire précisément ce qui n'a de trace que dans un journal.

Chaque conteneur porte donc son Alloy, en mode journaux seulement.

base est scindé

La PR #24 avait refusé de l'appliquer aux conteneurs et écrit pourquoi : il est fait pour un routeur — sysctl de routage, wireguard-tools, tcpdump. Mais c'est lui qui posait le dépôt Grafana dont Alloy a besoin.

Les deux dépôts tiers partent donc dans apt_sources, dont base dépend et qui les active tous les deux : rien ne change pour un nœud, et le conteneur ne prend que Grafana. Il n'installe pas Bird depuis CZ.NIC — celui de l'ingress prend la Bird 3 de Debian.

Les deux obstacles, mesurés avant d'écrire une ligne

loki.thystips.xyz résout vers un A privé et un AAAA public. Un conteneur n'a aucune IPv6 routable vers internet — sa seule adresse v6 est DN42 — donc laissé à RFC 6724 il tentait l'IPv6 :

loki.thystips.xyz  v6  → Network is unreachable
loki.thystips.xyz  v4  → TimeoutError   (10.2.70.168, plan privé)

D'où gai.conf, le même fichier que sur les nœuds. ⚠ Le gabarit passe dans ansible/templates/ plutôt que d'être copié : une seule ligne precedence remplace toute la table par défaut, et deux copies d'une table de préférence d'adresses ne divergent pas bruyamment — elles changent silencieusement quel chemin le trafic prend. Il nommait la variable de base, ce qui a échoué franchement dans un conteneur ; il prend maintenant un paramètre neutre.

L'ingest est sur le plan privé, donc dans les plages que notre propre règle ferme. Une ligne de plus à côté de celle de ca1, avec sa ligne de matrice. ⚠ Une adresse et pas un nom — nftables n'en prend pas d'autre, et un changement côté infrastructure se paierait ici en silence.

Deux pièges de supervision évités

caddy.service sort de la liste des unités des nœuds. L'y laisser aurait été pire qu'inutile : un filtre qui ne matche plus rien ne se distingue pas d'un service silencieux, donc la supervision aurait eu l'air de couvrir le tier d'ingress sans plus rien voir de lui.

instance distingue le conteneur de son nœud. dn42_node vaut ici le nom de l'hôte — voulu partout ailleurs, un service en conteneur répondant pour ce nœud — mais s'en servir comme instance aurait mélangé deux journaux sous une étiquette, openbao-agent.service existant des deux côtés.

Vérifié

« Pas d'erreur dans le journal d'Alloy » ne suffisait pas. Ses compteurs, eux, tranchent :

lues envoyées perdues
fr-rbx1 47 47 0
fr-rbx2 39 39 0
fr-bod1 1004 1004 0
fr-bod2 42 42 0

avec un HTTP 204 de Loki sur le tenant dn42. Témoin négatif : le SSH d'un nœud depuis un conteneur reste fermé. changed=0 sur les douze hôtes.

Au passage

render-check gagne les gabarits partagés : il ne parcourt que les rôles, donc gai.conf.j2 n'était plus vérifié par personne une fois déplacé — un gabarit qu'aucun check ne rend est un gabarit qui échoue sur un routeur à la place.

Un conteneur système a son **propre `journald`** : depuis que les services y sont descendus (PR #24 et #25), l'Alloy des nœuds ne voyait plus rien d'eux. Ni un Caddy qui refuse de démarrer, ni une authentification AppRole refusée, ni un rendu de template en erreur — c'est-à-dire précisément ce qui n'a de trace *que* dans un journal. Chaque conteneur porte donc son Alloy, en **mode journaux seulement**. ## `base` est scindé La PR #24 avait refusé de l'appliquer aux conteneurs et écrit pourquoi : il est fait pour un routeur — sysctl de routage, `wireguard-tools`, `tcpdump`. Mais c'est lui qui posait le dépôt Grafana dont Alloy a besoin. Les deux dépôts tiers partent donc dans **`apt_sources`**, dont `base` dépend et qui les active tous les deux : rien ne change pour un nœud, et le conteneur ne prend que Grafana. Il n'installe pas Bird depuis CZ.NIC — celui de l'ingress prend la Bird 3 de Debian. ## Les deux obstacles, mesurés avant d'écrire une ligne **`loki.thystips.xyz` résout vers un A privé et un AAAA public.** Un conteneur n'a *aucune* IPv6 routable vers internet — sa seule adresse v6 est DN42 — donc laissé à RFC 6724 il tentait l'IPv6 : ``` loki.thystips.xyz v6 → Network is unreachable loki.thystips.xyz v4 → TimeoutError (10.2.70.168, plan privé) ``` D'où `gai.conf`, le même fichier que sur les nœuds. ⚠ Le gabarit passe dans `ansible/templates/` plutôt que d'être copié : **une seule ligne `precedence` remplace toute la table par défaut**, et deux copies d'une table de préférence d'adresses ne divergent pas bruyamment — elles changent silencieusement quel chemin le trafic prend. Il nommait la variable de `base`, ce qui a échoué franchement dans un conteneur ; il prend maintenant un paramètre neutre. **L'ingest est sur le plan privé**, donc dans les plages que notre propre règle ferme. Une ligne de plus à côté de celle de `ca1`, avec sa ligne de matrice. ⚠ Une **adresse** et pas un nom — nftables n'en prend pas d'autre, et un changement côté infrastructure se paierait ici en silence. ## Deux pièges de supervision évités `caddy.service` **sort** de la liste des unités des nœuds. L'y laisser aurait été pire qu'inutile : un filtre qui ne matche plus rien ne se distingue pas d'un service silencieux, donc la supervision aurait eu l'air de couvrir le tier d'ingress sans plus rien voir de lui. `instance` distingue le conteneur de son nœud. `dn42_node` vaut ici le nom de l'**hôte** — voulu partout ailleurs, un service en conteneur répondant *pour* ce nœud — mais s'en servir comme instance aurait mélangé deux journaux sous une étiquette, `openbao-agent.service` existant des deux côtés. ## Vérifié « Pas d'erreur dans le journal d'Alloy » ne suffisait pas. Ses compteurs, eux, tranchent : | | lues | envoyées | perdues | |---|---|---|---| | fr-rbx1 | 47 | 47 | 0 | | fr-rbx2 | 39 | 39 | 0 | | fr-bod1 | 1004 | 1004 | 0 | | fr-bod2 | 42 | 42 | 0 | avec un `HTTP 204` de Loki sur le tenant `dn42`. Témoin négatif : le SSH d'un nœud depuis un conteneur reste fermé. `changed=0` sur les douze hôtes. ## Au passage `render-check` gagne les gabarits **partagés** : il ne parcourt que les rôles, donc `gai.conf.j2` n'était plus vérifié par personne une fois déplacé — un gabarit qu'aucun check ne rend est un gabarit qui échoue sur un routeur à la place.
Un conteneur système a son propre `journald` : depuis que les services y sont
descendus, l'Alloy des nœuds ne voyait plus rien d'eux — ni un Caddy qui refuse
de démarrer, ni une authentification AppRole refusée, ni un rendu de template en
erreur. Chaque conteneur porte donc son Alloy, en mode JOURNAUX SEULEMENT.

Pas de métriques : le nœud mesure déjà sa mémoire et son CPU, conteneurs
compris, et ce qu'un `node_exporter` par conteneur apporterait n'a pas encore de
question à laquelle répondre. Le jour où il y en aura une, c'est une ligne dans
`monitoring_container_alloy_files`.

`base` EST SCINDÉ, comme la PR #24 l'avait annoncé en refusant de l'appliquer
aux conteneurs. Il est écrit pour un routeur — sysctl de routage,
`wireguard-tools`, `tcpdump` — mais c'est lui qui posait le dépôt Grafana dont
Alloy a besoin. Les deux dépôts tiers partent donc dans `apt_sources`, dont
`base` dépend et qui les active tous les deux : rien ne change pour un nœud, et
le conteneur ne prend que Grafana. Il n'installe pas Bird depuis CZ.NIC — celui
de l'ingress prend la Bird 3 de Debian.

DEUX OBSTACLES, tous deux mesurés avant d'écrire une ligne :

1. `loki.thystips.xyz` résout vers un A PRIVÉ et un AAAA public. Un conteneur
   n'a AUCUNE IPv6 routable vers internet — sa seule adresse v6 est DN42 — donc
   laissé à RFC 6724 il tentait l'IPv6 : « Network is unreachable ». D'où
   `gai.conf`, le même que celui des nœuds. ⚠ Le gabarit passe dans
   `ansible/templates/` plutôt que d'être copié : une seule ligne `precedence`
   remplace TOUTE la table par défaut, et deux copies d'une table de préférence
   d'adresses ne divergent pas bruyamment — elles changent quel chemin le trafic
   prend. Il nommait la variable de `base`, ce qui a échoué franchement dans un
   conteneur ; il prend maintenant un paramètre neutre.
2. L'ingest est sur le plan privé (`10.2.70.168`), donc dans les plages que
   notre propre règle ferme. Une ligne de plus à côté de celle de `ca1`, avec sa
   ligne de matrice. ⚠ Une ADRESSE et pas un nom : nftables n'en prend pas
   d'autre, et un changement côté infrastructure se paierait ici en silence.

`caddy.service` sort de la liste des unités des NŒUDS. L'y laisser aurait été
pire qu'inutile : un filtre qui ne matche plus rien ne se distingue pas d'un
service silencieux, donc la supervision aurait eu l'air de couvrir le tier
d'ingress sans plus rien voir de lui.

⚠ `instance` distingue le conteneur de son nœud. `dn42_node` vaut ici le nom de
l'HÔTE — voulu partout ailleurs — mais s'en servir aurait mélangé deux journaux
sous une étiquette, `openbao-agent.service` existant des deux côtés.

`render-check` gagne les gabarits PARTAGÉS : il ne parcourt que les rôles, donc
`gai.conf.j2` n'était plus vérifié par personne une fois déplacé.

Vérifié — et « pas d'erreur dans le journal » ne suffisait pas, les compteurs
d'Alloy le disent :

    fr-rbx1   lues=47    envoyees=47    perdues=0
    fr-rbx2   lues=39    envoyees=39    perdues=0
    fr-bod1   lues=1004  envoyees=1004  perdues=0
    fr-bod2   lues=42    envoyees=42    perdues=0

avec un HTTP 204 de Loki sur le tenant `dn42`. `changed=0` sur les douze hôtes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGwvXLVXJ2PrRFwJTB3Xp9
Le push direct a été déployé et mesuré avant d'être remplacé — il marchait. Ce
qui l'a fait retirer, ce sont deux coûts que CHAQUE conteneur à venir aurait
payés (`dns_rec`, le frontal LG, le coffre) :

  1. Le mot de passe de l'ingest en clair dans chaque conteneur. Quatre copies
     de plus d'un secret qui n'en avait que quatre.
  2. Une ouverture vers le plan privé. Le pont est NATé, donc une règle ne peut
     porter que sur la DESTINATION : ouvrir l'ingest à un conteneur, c'est
     l'ouvrir à tous les conteneurs du nœud — vers un service qui accepte des
     écritures.

Le nœud reçoit sur `loki.source.api` et réexpédie avec SES identifiants. Le
conteneur ne parle qu'à son hôte ; la ligne `10.2.70.168:443` disparaît de
`nftables_incus_allowed_private`.

⚠ LE NŒUD ÉCOUTE, ce que le dépôt affirme partout ailleurs qu'il ne fait pas. La
nuance qui rend ça acceptable est l'ADRESSE : `10.42.0.1`, le pont NATé, jamais
le wildcard — que `tcp_l3mdev_accept=1` rendrait joignable depuis tout
l'overlay. Vérifié dans les deux sens : `ss` ne montre que `10.42.0.1:3500`, et
le port est fermé depuis la VRF d'un autre nœud.

⚠ PAS D'AUTHENTIFICATION sur ce port, et c'est assumé : tout conteneur du nœud
peut écrire dans notre tenant. Ce sont nos conteneurs, le pare-feu borne qui
atteint le port, et une paire d'identifiants de plus aurait rendu au conteneur
le secret que ce relais existe pour lui retirer.

`use_incoming_timestamp` : sans lui, un redémarrage du relais réécrirait à
l'instant présent des lignes vieilles de plusieurs heures — `max_age` fait
rattraper jusqu'à 12 h au démarrage d'un conteneur.

`gai.conf` RESTE dans le conteneur, mais sa raison change et le commentaire le
dit : ce n'est plus Loki, dont l'adresse est devenue locale, c'est
`ca1.thystips.xyz` — même profil A privé / AAAA public (`10.1.70.20` et
`2a0c:b641:112:70::20`, mesuré), et c'est le nom que le descellement par transit
résout. C'est de la configuration de MACHINE, pas de service.

Vérifié : zéro ligne `password`/`username` dans les quatre conteneurs, 42
entrées envoyées par chacun avec HTTP 204 du relais, 44 reçues côté nœud, 478
réexpédiées vers Loki, aucune perte sur aucun des deux sauts. `changed=0` sur
les douze hôtes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FGwvXLVXJ2PrRFwJTB3Xp9
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ThysTips_dn42/infra-dn42!26
No description provided.