Helm Charts / Detect changed charts (pull_request) Successful in 3m18s
Helm Charts / Library charts tool (pull_request) Skipped
Helm Charts / Application charts crowdsec (pull_request) Skipped
Helm Charts / Application charts hashicorp-vault (pull_request) Skipped
Helm Charts / Application charts minio (pull_request) Skipped
Helm Charts / Application charts prometheus (pull_request) Skipped
Helm Charts / Application charts pgbouncer (pull_request) Skipped
Helm Charts / Application charts pgcat (pull_request) Skipped
Helm Charts / Application charts redis (pull_request) Skipped
Helm Charts / Application charts chart (pull_request) Successful in 29s
Helm Charts / Application charts alloy (pull_request) Successful in 59s
Helm Charts / Application charts grafana (pull_request) Successful in 1m1s
Helm Charts / Application charts loki (pull_request) Successful in 48s
Le cluster n'avait AUCUNE collecte de journaux : la seule lecture possible
était `kubectl logs`, c'est-à-dire le tampon du kubelet, qui tourne. Mesuré
le 2026-09-07 en demandant à chaque journal jusqu'où il remonte :
kube-system/traefik pod démarré il y a 12 j — journal remontant à 10 MIN
tools/clickhouse-0 pod démarré il y a 8 j — journal remontant à 2 MIN
Les deux composants les plus bavards du cluster gardent entre deux et dix
minutes d'histoire. C'est pire que ce que l'issue supposait.
Ce lot pose Loki (binaire unique, stockage fichier sur Longhorn) et Alloy
(l'agent, un pod par nœud), et branche la source de données au Grafana qui
tourne déjà.
L'AGENT : Alloy, pas Promtail — et c'est mesuré, pas une préférence. L'index
Helm de Grafana marque le chart `promtail` `deprecated: true`, dernière
publication 2025-10-31. Alloy : 2026-08-27. On n'installe pas à neuf un agent
que l'amont a déjà rangé.
LA RÉTENTION : 30 jours, sur un débit mesuré de ~305 Mio/jour bruts pour tout
le cluster, soit 0,9 à 1,8 Gio compressés. 7 jours auraient perdu le début des
deux pannes qui motivent ce lot (kadans#1033, des SEMAINES ; tools#36, 3 jours).
LES PLAFONDS NE SONT PAS OPTIONNELS : pi2 est mesuré à 108 % de sa mémoire
allouable. Les défauts amont du chart Loki y auraient posé un memcached
réclamant 8 Gio (`chunksCache.allocatedMemory: 8192`), un second memcached, un
nginx, un DaemonSet canari et trois jeux de répliques. Chaque extinction est
nommée dans `loki/values.yaml`, avec sa raison.
⚠ La CI de ce dépôt monte une matrice CODÉE EN DUR : un chart absent de la
liste n'est jamais construit et sa PR est verte quand même (tools#32). `loki`
et `alloy` y sont ajoutés — sans ça, ce lot n'aurait aucune preuve.
Deux découvertes faites en déployant, pas en lisant :
- `admin_api_directory` est accepté par le chart mais REFUSÉ par le binaire
OSS (champ d'édition entreprise) : CrashLoopBackOff jusqu'à ce qu'on l'ôte ;
- le chart ajoute un side-car `loki-sc-rules` SANS aucune limite de
ressources — un conteneur non plafonné dans un pod plafonné annule le
plafond. Éteint (le ruler est à 0 réplique).
Closes #38
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Fable 5.1 <[email protected]>
176 lines
7.3 KiB
YAML
176 lines
7.3 KiB
YAML
# -----------------------------------------------------------------------------
|
|
# Grafana Alloy — l'agent de collecte, un pod par nœud.
|
|
# -----------------------------------------------------------------------------
|
|
# POURQUOI ALLOY ET PAS PROMTAIL (l'arbitrage de tools#38, mesuré le 2026-09-07)
|
|
#
|
|
# Relevé sur l'index du dépôt Helm de Grafana lui-même
|
|
# (`curl https://grafana.github.io/helm-charts/index.yaml`) :
|
|
#
|
|
# chart versions dernière publication `deprecated:` dans l'index
|
|
# promtail 141 2025-10-31 true
|
|
# alloy 57 2026-08-27 (absent)
|
|
# loki 344 2026-08-10 (absent)
|
|
#
|
|
# Promtail n'est pas « en fin de vie bientôt » : son chart porte le drapeau
|
|
# `deprecated: true` dans l'index, et sa dernière publication a plus de dix mois.
|
|
# Déployer AUJOURD'HUI, sur une installation NEUVE, un agent que l'amont a déjà
|
|
# rangé, c'est se donner une dette à échéance connue pour économiser de la
|
|
# mémoire — et l'économie, elle, est à vérifier, pas à supposer.
|
|
#
|
|
# Le coût réel d'Alloy sur CE matériel est mesuré après déploiement et reporté
|
|
# dans la PR ; c'est ce que le plafond `resources` ci-dessous encadre.
|
|
#
|
|
# ARM64 : vérifié au registre, manifeste multi-plateforme (jeton anonyme
|
|
# Docker Hub) — `grafana/alloy:v1.19.2` publie linux/amd64, **linux/arm64**,
|
|
# ppc64le, s390x. `grafana/loki:3.6.12` publie amd64, **arm64**, arm/v7.
|
|
# -----------------------------------------------------------------------------
|
|
alloy:
|
|
controller:
|
|
# C'est déjà le défaut du chart ; on l'écrit parce que c'est LA décision
|
|
# structurante — un agent par nœud, donc trois fois le coût mémoire.
|
|
type: daemonset
|
|
|
|
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
|
|
# Pi, faire transiter tous les journaux par l'API du cluster ajouterait au
|
|
# 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.
|
|
dockercontainers: false
|
|
|
|
# ⚠ 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
|
|
# précisément le nœud où un agent de trop bascule la machine.
|
|
#
|
|
# Le plafond n'est donc pas une formalité : il est ce qui garantit que
|
|
# l'agent meurt AVANT le service qu'il observe. Un OOM d'Alloy est un
|
|
# incident de collecte ; un OOM de MinIO ou de pgbouncer est un incident
|
|
# tout court.
|
|
resources:
|
|
requests:
|
|
cpu: 50m
|
|
memory: 128Mi
|
|
limits:
|
|
# Pas de limite CPU (l'étranglement d'un agent lui fait perdre des
|
|
# lignes en silence, ce qui est le défaut qu'on essaie de supprimer).
|
|
memory: 256Mi
|
|
|
|
# Pas de grappe : chaque agent lit SON nœud, il n'a rien à coordonner.
|
|
clustering:
|
|
enabled: false
|
|
|
|
configMap:
|
|
create: true
|
|
# -----------------------------------------------------------------------
|
|
# La configuration Alloy. Trois étages : découvrir, lire, pousser.
|
|
# -----------------------------------------------------------------------
|
|
content: |-
|
|
// ÉTAGE 1 — découvrir les pods de CE nœud, et de lui seul.
|
|
//
|
|
// ⚠ Sans le filtre par nœud, chacun des trois agents découvrirait les
|
|
// pods des TROIS nœuds, chercherait leurs fichiers en local, n'en
|
|
// trouverait aucun pour les deux autres, et referait ce travail en
|
|
// boucle. Le filtre se pose côté serveur d'API (`field`), pas en
|
|
// relabel : ce qui n'est pas envoyé ne coûte rien.
|
|
discovery.kubernetes "pods_du_noeud" {
|
|
role = "pod"
|
|
|
|
selectors {
|
|
role = "pod"
|
|
field = "spec.nodeName=" + sys.env("HOSTNAME")
|
|
}
|
|
}
|
|
|
|
// ÉTAGE 2 — traduire les métadonnées Kubernetes en étiquettes Loki,
|
|
// et fabriquer le chemin du fichier journal.
|
|
//
|
|
// ⚠ LES ÉTIQUETTES SONT LE SEUL INDEX DE LOKI, ET CHAQUE COMBINAISON
|
|
// DISTINCTE EST UN FLUX. On garde ce par quoi on cherche vraiment
|
|
// (namespace, pod, conteneur, nœud) et RIEN de ce qui varie sans
|
|
// qu'on l'interroge : mettre ici une étiquette à forte cardinalité
|
|
// (un identifiant de requête, un horodatage) est la façon classique
|
|
// de faire tomber un Loki. Le reste du contenu reste cherchable en
|
|
// plein texte, simplement non indexé.
|
|
discovery.relabel "journaux_de_pods" {
|
|
targets = discovery.kubernetes.pods_du_noeud.targets
|
|
|
|
rule {
|
|
source_labels = ["__meta_kubernetes_namespace"]
|
|
target_label = "namespace"
|
|
}
|
|
rule {
|
|
source_labels = ["__meta_kubernetes_pod_name"]
|
|
target_label = "pod"
|
|
}
|
|
rule {
|
|
source_labels = ["__meta_kubernetes_pod_container_name"]
|
|
target_label = "container"
|
|
}
|
|
rule {
|
|
source_labels = ["__meta_kubernetes_node_name"]
|
|
target_label = "node"
|
|
}
|
|
rule {
|
|
source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_name"]
|
|
target_label = "app"
|
|
}
|
|
|
|
// Le chemin réel posé par le kubelet :
|
|
// /var/log/pods/<ns>_<pod>_<uid>/<conteneur>/0.log
|
|
// On vise par l'UID (unique) et le nom du conteneur.
|
|
rule {
|
|
source_labels = ["__meta_kubernetes_pod_uid", "__meta_kubernetes_pod_container_name"]
|
|
separator = "/"
|
|
action = "replace"
|
|
replacement = "/var/log/pods/*$1/*.log"
|
|
target_label = "__path__"
|
|
}
|
|
}
|
|
|
|
// ÉTAGE 3 — lire les fichiers, décoder le format CRI, pousser.
|
|
local.file_match "journaux_de_pods" {
|
|
path_targets = discovery.relabel.journaux_de_pods.output
|
|
}
|
|
|
|
loki.source.file "journaux_de_pods" {
|
|
targets = local.file_match.journaux_de_pods.targets
|
|
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.
|
|
loki.process "journaux_de_pods" {
|
|
stage.cri {}
|
|
|
|
forward_to = [loki.write.loki.receiver]
|
|
}
|
|
|
|
loki.write "loki" {
|
|
endpoint {
|
|
url = "http://loki.tools.svc.cluster.local:3100/loki/api/v1/push"
|
|
}
|
|
}
|
|
|
|
# L'agent a besoin de LIRE les pods du serveur d'API pour les découvrir.
|
|
# Le chart pose un ClusterRole en lecture seule (pods, nodes, services,
|
|
# endpoints) — rien qui écrive.
|
|
rbac:
|
|
create: true
|
|
|
|
serviceAccount:
|
|
create: true
|
|
|
|
# Pas de collecte de métriques d'Alloy par Prometheus pour l'instant : le
|
|
# ServiceMonitor suppose l'opérateur Prometheus, que ce cluster n'a pas
|
|
# (le chart `prometheus` d'ici est le chart communautaire, pas l'opérateur).
|
|
serviceMonitor:
|
|
enabled: false
|
|
|
|
ingress:
|
|
enabled: false
|