Aller au contenu

Observabilité

Cette page ne couvre que la supervision : collecte des métriques, collecte des journaux, tableaux de bord, accès aux interfaces d'exploitation. Les alertes sont traitées à la page Exploitation : sur l'instance de référence, trente-quatre règles et le routage Alertmanager vivent dans observability/prometheus-values.yaml, et les courriels partent par le SMTP du client. Cette page ne couvre pas non plus la recette de conformité après installation, qui vit à la page Validation.

Deux piles de supervision, une seule à déployer

C'est le piège principal pour un installateur tiers.

Stack Origine Rôle dans une installation neuve
monitoring charts Helm, appliqués à la main, hors du kustomize racine c'est celle que l'on déploie et que l'on consomme
observability manifests observability/*.yaml, tirés par le kustomize racine héritage. Un installateur ne la déploie pas

Un opérateur qui applique kubectl kustomize . (l'étape 3 du Pas-à-pas) obtient la seconde stack, dans le namespace observability : un Prometheus, un Grafana et un Kibana qui démarrent sur leur configuration par défaut, sans ConfigMap, sans argument --config.file, sans datasource provisionnée. Elle tourne, elle répond, et elle ne reçoit aucune métrique ni aucun journal de la plateforme. Un opérateur qui ouvre ce Grafana sans connaitre cette page conclut à tort que la supervision est en panne, alors qu'il regarde le mauvais Grafana : celui qui porte réellement les données, les deux tableaux de bord versionnés et l'authentification prévue est celui de la stack monitoring.

Décision pour toute installation cliente : déployer et exploiter uniquement la stack monitoring. La stack observability posée par le kustomize racine peut être supprimée sans risque une fois l'installation terminée (kubectl delete namespace observability) ou laissée inerte ; dans les deux cas, ne jamais s'y fier et ne jamais y importer les tableaux de bord de cette page. Kibana relève du même arbitrage : son Deployment ne porte aucune variable ELASTICSEARCH_HOSTS, donc rien ne le raccorde à Elasticsearch. Le chemin de journaux réellement utilisé passe par Alloy et Loki, décrit plus bas.

Installer les quatre releases Helm de la stack monitoring

Aucune de ces releases ne vient du kustomize racine. Chacune a son fichier de valeurs dans observability/, complet et daté ; helm upgrade remplace le jeu de valeurs fourni, donc ces fichiers sont la source de vérité, pas helm get values. Alertmanager n'est pas une cinquième release : c'est un sous-chart de Prometheus, et le Service prometheus-alertmanager vient de là.

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

kubectl apply -f observability/alertmanager-smtp.yaml
helm upgrade --install prometheus prometheus-community/prometheus -n monitoring --create-namespace \
  --version 29.14.0 -f observability/prometheus-values.yaml
helm upgrade --install grafana grafana/grafana -n monitoring \
  --version 10.5.15 -f observability/grafana-values.yaml
helm upgrade --install loki grafana/loki -n monitoring \
  --version 7.0.0 -f observability/loki-values.yaml
helm upgrade --install alloy grafana/alloy -n monitoring \
  --version 1.10.0 -f observability/alloy-values.yaml
kubectl apply -f observability/scrape/services-metriques.yaml
Release Chart Ce que le fichier de valeurs porte
prometheus prometheus-community/prometheus 29.14.0 les trente-quatre règles d'alerte, le routage Alertmanager, node-exporter du chart désactivé
grafana grafana/grafana 10.5.15 source de données, provisionnement des tableaux de bord
loki grafana/loki 7.0.0 mode SingleBinary, bornes memcached, labels
alloy grafana/alloy 1.10.0 collecte des journaux vers Loki

observability/alertmanager-smtp.yaml vient AVANT la release prometheus, et pas par habitude : le fichier de valeurs monte le Secret alertmanager-smtp dans le pod Alertmanager, qui y lit son mot de passe SMTP (smtp_auth_password_file). Si le Secret n'existe pas, le pod reste en ContainerCreating sur un volume introuvable, et aucune alerte ne part. Le chemin Vault est prod/alertmanager, clé smtp_password, écrit à l'étape 11 du pas-à-pas ; le serveur, l'expéditeur et le destinataire sont dans le fichier de valeurs, en clair, car rien n'y est secret. Le serveur est TV_SMTP_HOST, que render-config.sh substitue là aussi. L'identifiant (smtp_auth_username) et l'expéditeur (smtp_from) restent ceux de l'instance de référence : les remplacer à la main par ceux du client avant le helm upgrade, l'expéditeur sur le domaine que le relais a vérifié. Le destinataire (to: sous default-receiver) est une boîte de l'instance de référence dont render-config.sh ne réécrit que le domaine : y mettre ${TV_ACME_EMAIL} à la main, dans le même passage. Vérifier le chemin complet une fois la release posée, sans attendre une vraie panne :

kubectl -n monitoring exec sts/prometheus-alertmanager -- \
  amtool alert add TestCourriel severity=warning --alertmanager.url=http://localhost:9093
Le courriel [FIRING:1] TestCourriel arrive sous une minute (group_wait: 30s). S'il n'arrive pas, le journal du même pod porte la cause, notify retry avec le code SMTP, et rien d'autre ne la portera. L'alerte de test expire d'elle-même au bout de cinq minutes.

observability/scrape/services-metriques.yaml ajoute les cibles que Prometheus ne découvre pas seul : external-secrets, Longhorn, la base Postgres de Keycloak, cloudflared et Vault. Sans lui, les groupes secrets, stockage, coffre, base-keycloak et exposition des règles n'ont aucune série à lire et se taisent.

Composants et versions

Composant Espace de noms Manifeste ou chart Rôle
Prometheus monitoring chart prometheus-29.14.0, observability/prometheus-values.yaml ; Service prometheus-server:80 métriques et règles d'alerte
Alertmanager monitoring sous-chart de Prometheus, même fichier de valeurs ; Service prometheus-alertmanager:9093 routage des trente-quatre règles
Grafana monitoring chart grafana-10.5.15, observability/grafana-values.yaml ; Service grafana:80 restitution
Loki monitoring chart loki-7.0.0, observability/loki-values.yaml, mode SingleBinary journaux
Alloy monitoring chart grafana/alloy, observability/alloy-values.yaml, DaemonSet lecture des journaux de pods
node-exporter node-exporter core/monitoring/node-exporter.yaml, node-exporter:v1.11.1, port 9100 ; hors de la couche de base, posé par l'étape 10 du Pas-à-pas métriques hôte
metrics-server kube-system core/metrics-server/metrics-server.yaml, v0.7.2, --metric-resolution=15s kubectl top et HPA, pas une source de supervision

metrics-server alimente kubectl top et les HPA, jamais une alerte ni un tableau de bord : sa latence de 15 à 30 secondes le rend aveugle aux défaillances rapides.

Traefik expose ses propres métriques Prometheus sur son entrypoint d'administration : cette seule source couvre toute la couche HTTP de la plateforme, les trois portails, Keycloak et les sept APIs métier, sans instrumentation applicative. C'est la source du tableau de bord RED décrit plus bas.

Le fichier prometheus.yaml versionné dans observability/ (image prom/prometheus:v2.54.1, PVC prometheus-data de 50Gi) appartient à la stack observability, pas à la stack monitoring : ce n'est pas un dimensionnement de référence pour l'instance réellement exploitée, seulement la configuration par défaut d'un Prometheus qui ne reçoit rien.

Paramètres à régler par client

Les deux paramètres Loki ci-dessous ne se règlent qu'une fois la release loki créée : voir la question ouverte plus haut.

Paramètre Valeur de référence À faire pour un nouveau client
Rétention Prometheus, taille du PVC non fixée dans ce dépôt question ouverte, voir l'encart ci-dessus : ces valeurs se règlent au moment où la stack monitoring est installée, à convenir avec le client selon la durée de rétention contractuelle et le stockage disponible
Bornes des caches memcached de Loki chunksCache.allocatedMemory: 1024, resultsCache.allocatedMemory: 512 (Mo), dans observability/loki-values.yaml ces valeurs ont été calibrées sur la mémoire des workers de l'instance de référence, après qu'un défaut de chart à 8192 Mo a monopolisé 91 % de la mémoire d'un nœud ; les recaler à la baisse ou à la hausse selon la mémoire réelle des workers du client avant le premier helm upgrade, pas après
Endpoint de push Loki http://loki-gateway.monitoring.svc.cluster.local/loki/api/v1/push fixe pour tout client : c'est une adresse de Service interne au cluster, indépendante de ${TV_DOMAIN}

Le fichier de valeurs Loki doit toujours porter l'intégralité des valeurs voulues : helm upgrade remplace le jeu fourni, un fichier partiel réinitialise tout le reste aux défauts du chart, dont le cache à 8192 Mo déjà responsable d'un incident. Il en va de même pour observability/alloy-values.yaml.

Labels Loki, et pourquoi ils doivent rester stables

Les métadonnées Kubernetes arrivent dans Alloy sous forme de labels internes préfixés __, qui ne sont jamais persistés s'ils ne sont pas explicitement recopiés. Sans cette recopie, Loki ne reçoit que le label pod, qui contient le hash du ReplicaSet et change à chaque recréation : les journaux d'un service décrochent alors à chaque déploiement, et il faut re-sélectionner le nom du nouveau pod à la main.

observability/alloy-values.yaml recopie donc quatre labels stables, par une étape discovery.relabel qui ne se retire pas :

Label Source Rôle
namespace __meta_kubernetes_namespace cloisonnement
app label du pod app.kubernetes.io/name, sinon app sélection du service, invariante par déploiement
container __meta_kubernetes_pod_container_name tri des conteneurs d'un même pod
pod __meta_kubernetes_pod_name isoler une réplique précise

La requête de référence porte sur le service, jamais sur le pod :

{namespace="api", app="trade-classification-svc"}

La config Alloy est gérée par Helm : un kubectl edit ou un kubectl apply direct sur sa ConfigMap est écrasé au prochain helm upgrade. La seule source de vérité est observability/alloy-values.yaml.

Importer les tableaux de bord

Grafana ne charge aucun tableau de bord au démarrage sur cette instance : aucun sidecar ne va chercher de fichiers, le chemin /etc/grafana/provisioning n'est monté par rien. L'import est manuel, pour chacun des deux fichiers versionnés dans le dépôt (red-par-service.json, UID tradeval-red ; ressources.json, UID tradeval-ressources) :

Dashboards -> New -> Import -> Upload JSON file, puis choisir la datasource Prometheus.

Les deux fichiers déclarent un bloc __inputs : la boîte de dialogue réclame donc la datasource à chaque import, rien n'est codé en dur. Le tableau tradeval-red répond au débit, au taux d'erreur et à la latence par service, sur la seule source Traefik. Le tableau tradeval-ressources répond à la question de savoir si le cluster est le facteur limitant, avec une sélection par namespace. Les fichiers du dépôt font foi : une modification faite dans l'interface Grafana n'existe que dans sa base SQLite et disparait au prochain import.

Vérifier que la pile reçoit réellement des données

L'import réussit à l'identique que la datasource choisie reçoive des métriques ou non : Grafana affiche « Dashboard imported » aussi bien si la pile monitoring fonctionne, si elle n'existe pas encore (voir la question ouverte plus haut) ou si la datasource retenue par erreur est celle de la stack observability, qui ne reçoit rien. Un import réussi ne prouve donc rien à lui seul : les trois contrôles suivants vérifient que les données arrivent, pas seulement que le geste a été exécuté.

Les deux datasources s'appellent l'une comme l'autre « Prometheus » dans la boîte d'import de Grafana ; seule leur URL les distingue. La bonne cible le Service prometheus-server dans le namespace monitoring ; celle de la stack héritée cible le Service prometheus dans le namespace observability. C'est ce nom de Service, dans l'URL de la datasource, qui tranche.

Vérification Symptôme si échec
Les cibles Prometheus sont up, pas seulement déclarées : kubectl -n monitoring port-forward svc/prometheus-server 9090:80 & puis curl -sS 'http://localhost:9090/api/v1/query?query=up' renvoie au moins deux séries à la valeur 1, dont une pour Traefik Aucune série, ou commande qui échoue : le port-forward vise la mauvaise stack (observability n'a pas de Service prometheus-server), ou Traefik ne porte pas encore ses annotations de scrape
Une requête LogQL de contrôle renvoie des lignes, après avoir provoqué du trafic sur le service interrogé : kubectl -n api rollout restart deploy/trade-classification-svc, puis kubectl -n monitoring port-forward svc/loki-gateway 3100:80 & et curl -sS -G http://localhost:3100/loki/api/v1/query_range --data-urlencode 'query={namespace="api", app="trade-classification-svc"}' Réponse vide : la release alloy n'existe pas (voir la question ouverte plus haut), les labels ne sont pas recopiés, ou le port-forward vise un Loki qui n'existe pas
Le tableau tradeval-red affiche un débit non nul sur trade-classification-svc, après avoir envoyé une vingtaine de requêtes qui traversent réellement Traefik : for i in $(seq 1 20); do curl -sS -o /dev/null https://classification.${TV_DOMAIN}/; done, depuis un poste qui résout ce host vers la VIP, ou depuis un pod du cluster. Un redémarrage de pod ne suffit pas : ce tableau ne lit que des métriques Traefik, jamais produites par un simple rollout restart Débit qui reste à zéro malgré les requêtes envoyées, ou « No data » : la datasource retenue à l'import porte l'URL du Service prometheus dans le namespace observability (la pile héritée), pas celle du Service prometheus-server dans monitoring (la seule à choisir) ; ou les requêtes n'ont pas atteint Traefik, host mal résolu ou TLSStore pas encore prêt

Accès aux interfaces et basic-auth

core/platform-access/ contient cinq Ingress Traefik : les trois de la stack monitoring, Longhorn, et l'UI de Vault. L'étape 10 du Pas-à-pas n'en pose que deux, Longhorn et Vault, qu'elle applique par fichiers nommés : les trois autres visent le namespace monitoring, qu'aucun manifeste du dépôt ne crée, et ne s'appliquent qu'une fois la stack monitoring installée, ce que ce guide ne couvre pas. Tous portent les mêmes annotations d'entrypoints et de TLS, sans spec.tls propre : le certificat vient du TLSStore par défaut qui sert le wildcard *.${TV_DOMAIN}. Cette section ne couvre que les quatre premières ; l'UI Vault, déjà présentée au Pas-à-pas, a sa propre authentification par jeton et un profil de risque distinct.

Host Backend Authentification
grafana.${TV_DOMAIN} monitoring:grafana:80 login Grafana intégré
prometheus.${TV_DOMAIN} monitoring:prometheus-server:80 basic-auth Traefik, core/platform-access/auth.yaml
alertmanager.${TV_DOMAIN} monitoring:prometheus-alertmanager:9093 idem
longhorn.${TV_DOMAIN} longhorn-system:longhorn-frontend:80 idem

La basic-auth est posée par core/platform-access/auth.yaml depuis le 2026-09-15 : un ExternalSecret et un Middleware dashboard-auth par namespace concerné, monitoring et longhorn-system, et l'annotation traefik.ingress.kubernetes.io/router.middlewares: <namespace>-dashboard-auth@kubernetescrd sur les trois Ingress. Le fichier htpasswd vit dans Vault, prod/platform-access, clé users, une ligne par compte au format bcrypt. Il n'y a rien à créer à la main : poser la clé dans Vault, appliquer le fichier.

htpasswd -nbB admin '<mot de passe>' | vault kv patch -mount=secrets prod/platform-access users=-
kubectl apply -f core/platform-access/auth.yaml

Sans htpasswd sur le poste, n'importe quel conteneur httpd:2.4-alpine en porte un. Traefik accepte bcrypt, MD5-APR1 et SHA1 ; openssl passwd -6 produit du SHA-512, qu'il ne lit pas.

Deux autres options existent, à la place de la basic-auth : restreindre par IP source avec un Middleware ipAllowList, ou passer par un SSO (Traefik forward-auth vers Keycloak) pour une protection homogène avec le reste de la plateforme.

Ne jamais publier d'enregistrement DNS public pour ces quatre hosts

La basic-auth protège d'un clic malheureux sur le réseau interne, pas d'Internet. La console Longhorn permet de supprimer des volumes. La protection réelle de ces hosts reste de n'être joignables que par le réseau interne ou le VPN : aucun enregistrement public, aucune route dans le tunnel Cloudflare.

Sans metrics-server, les HPA restent inertes ; ne pas rejouer l'incident historique sur eux

Les dix HPA de ce dépôt restent inertes tant que metrics-server n'est pas installé, sans le signaler autrement que par une valeur <unknown> dans kubectl get hpa. Sur l'instance de référence, l'installation de metrics-server a un jour réveillé d'un coup des HPA réglés sur une cible mémoire et un maxReplicas de 50, saturant le CPU du cluster en quelques minutes. Cette configuration a été corrigée depuis : les HPA versionnés aujourd'hui ne portent plus de métrique mémoire et plafonnent à 3 répliques, 6 pour hub-portail. Ne pas supposer pour autant qu'installer metrics-server est sans risque sur un cluster de dimensionnement différent, ou après l'ajout d'un nouveau service : confronter les valeurs de requests et les bornes maxReplicas à un usage réel avant d'installer metrics-server, plutôt que d'hériter des chiffres déjà présents dans le dépôt sans les revérifier.