Ce qu'un pod a dit lui survit — Loki et Alloy, sous plafond (#38) #40

Merged
arcodange merged 6 commits from arcodange/loki-journaux into main 2026-09-07 18:06:10 +02:00
Showing only changes of commit c4720a1dfd - Show all commits
+80 -6
View File
@@ -30,6 +30,20 @@ alloy:
# structurante — un agent par nœud, donc trois fois le coût mémoire.
type: daemonset
# Le volume que consomme le montage `alloy.mounts.extra` ci-dessous : la
# racine Docker de ces machines, où aboutissent les liens de /var/log/pods.
volumes:
extra:
- name: docker-containers
hostPath:
path: /mnt/arcodange/docker/containers
# `Directory` (et non `DirectoryOrCreate`) EXPRÈS : si un jour un
# nœud n'a pas ce chemin, on veut que son agent refuse de démarrer
# et le DISE, plutôt que de monter un dossier vide et de rapporter
# zéro journal en se déclarant sain — le défaut exact qu'on vient
# de traverser.
type: Directory
alloy:
# -- Le montage de /var/log : c'est par là que l'agent lit les journaux.
# On tail des FICHIERS plutôt que d'interroger l'API du kubelet : sur trois
@@ -37,9 +51,37 @@ alloy:
# serveur d'API une charge qu'il n'a pas à porter.
mounts:
varlog: true
# k3s utilise containerd, pas Docker : ce montage-là ne servirait à rien.
# ⚠⚠ CE CLUSTER TOURNE SOUS DOCKER, PAS SOUS CONTAINERD — et son magasin
# d'images n'est pas là où le chart le cherche. Les deux faits ensemble
# rendent ce drapeau inutilisable.
#
# Mesuré le 2026-09-07 (`kubectl get nodes -o …containerRuntimeVersion`) :
# pi1 docker://29.1.3
# pi2 docker://28.4.0
# pi3 docker://27.4.0
#
# Et sous /var/log/pods, les journaux ne sont PAS des fichiers : ce sont
# des liens symboliques vers la racine Docker, DÉPLACÉE sur un disque
# externe (identique sur pi1 et pi3, vérifié pod par pod) :
#
# /var/log/pods/<ns>_<pod>_<uid>/<conteneur>/0.log
# -> /mnt/arcodange/docker/containers/<id>/<id>-json.log
#
# `dockercontainers: true` monterait `/var/lib/docker/containers`, qui
# n'existe pas ici. On monte donc la vraie racine, plus bas, en `extra`.
dockercontainers: false
# ⚠ SANS CE MONTAGE, LA COLLECTE EST SILENCIEUSEMENT VIDE — et c'est le
# mode d'échec le plus traître de ce lot, parce que TOUT A L'AIR SAIN :
# les six composants d'Alloy se déclarent `healthy`, aucune erreur de
# poussée, Loki répond `ready`. Le seul relevé qui le dit est
# `loki_source_file_files_active_total = 0`, et l'export vide de
# `local.file_match`. `ls` montre les liens, `stat` échoue sur la cible.
extra:
- name: docker-containers
mountPath: /mnt/arcodange/docker/containers
readOnly: true
# ⚠ LA MÉMOIRE EST LA RESSOURCE RARE, ET UN DAEMONSET LA PREND TROIS FOIS.
# Mesuré le 2026-09-07 avant tout déploiement : pi1 79 %, pi2 109 %,
# pi3 58 % de leur mémoire ALLOUABLE. pi2 est en surengagement — c'est
@@ -140,12 +182,17 @@ alloy:
forward_to = [loki.process.journaux_de_pods.receiver]
}
// ⚠ containerd n'écrit PAS du JSON de Docker : chaque ligne est
// « <horodatage> <flux> <F|P> <message> ». Sans `stage.cri`, l'horodatage
// stocké serait celui de la LECTURE et non celui de l'écriture — ce qui
// ruinerait précisément l'usage qui motive ce lot : dater une panne.
// ⚠⚠ `stage.docker`, PAS `stage.cri` — ces nœuds tournent sous Docker
// (mesuré : docker://29.1.3, 28.4.0, 27.4.0), donc chaque ligne est un
// objet JSON {"log":…,"stream":…,"time":…} et non le format CRI
// « <horodatage> <flux> <F|P> <message> ».
//
// Ce n'est pas cosmétique : sans le bon décodeur, l'horodatage stocké
// serait celui de la LECTURE et non celui de l'écriture, et les lignes
// arriveraient encore encapsulées dans leur JSON. Or dater une panne à
// la minute est PRÉCISÉMENT l'usage qui motive ce lot (tools#36).
loki.process "journaux_de_pods" {
stage.cri {}
stage.docker {}
forward_to = [loki.write.loki.receiver]
}
@@ -173,3 +220,30 @@ alloy:
ingress:
enabled: false
# ⚠⚠ LE MÊME PIÈGE QUE LE SIDE-CAR DE LOKI, DEUXIÈME OCCURRENCE — et celle-ci
# est plus sournoise, parce qu'elle RESSEMBLE à un plafond.
#
# Le chart ajoute au pod un second conteneur, `config-reloader`, qui recharge
# Alloy quand sa ConfigMap change. Mesuré sur le premier déploiement, ses
# ressources amont sont :
#
# resources:
# requests: { cpu: 10m, memory: 50Mi }
# ← et RIEN d'autre
#
# Une requête SANS limite n'est pas un plafond : c'est une réservation. Le
# conteneur peut croître jusqu'à épuiser le nœud, et comme il partage le
# cgroup du pod, il emporte Alloy avec lui. Sur un DaemonSet, cela vaut sur
# les TROIS nœuds — pi2 compris, mesuré à 108 % de son allouable.
#
# ⚠ On ne peut pas l'éteindre : sans lui, un changement de configuration
# n'atteindrait l'agent qu'au prochain redémarrage du pod. On le PLAFONNE.
configReloader:
enabled: true
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 64Mi