Observabilité : GitOps, métriques et logs
Ce guide explique comment la stack d'observabilité du projet est installée et comment ses composants se parlent. Objectif : pouvoir y revenir sans se perdre.
ADR liés (le pourquoi des décisions) : GitOps ArgoCD, Observabilité, Logs Loki + Alloy.
L'idée en une image
Piloter une appli en production, c'est comme un cockpit d'avion :
- Les cadrans (vitesse, altitude) = les métriques. Des chiffres dans le temps. Ils disent QUE quelque chose ne va pas.
- Le journal de bord = les logs. Du texte détaillé. Il dit POURQUOI.
- L'écran qui affiche tout = Grafana.
La clé : deux plans à ne pas confondre
C'est le point qui perd tout le monde au début. Il y a deux plans distincts.
Plan A, l'installation (GitOps / ArgoCD)
Comment les composants arrivent dans le cluster.
Git ──► ArgoCD ──► cluster
Les fichiers YAML qu'on écrit (les « Application ») vivent ici. Ils décrivent QUOI installer. C'est un plan déclaratif : on décrit l'état voulu, ArgoCD le réalise.
Plan B, le fonctionnement (runtime)
Une fois installés, comment les composants s'échangent les données.
appli ──► Prometheus ──► Grafana
Aucun fichier « Application » ici, ce sont juste les composants qui tournent et se parlent.
Règle mentale : quand tu lis un
root-app.yaml, tu es dans le plan A (installation). Quand tu lis « Prometheus scrape /metrics », tu es dans le plan B (runtime). Cette séparation enlève l'essentiel de la confusion.
Plan B : qui parle à qui (runtime)
┌─ scrape /metrics ──────────────┐
FastAPI ─┤ ▼
│ └─ stdout (logs) ─► Alloy ─► Loki
│ │
▼ ▼
(2 sources) Grafana lit Prometheus + Loki
- FastAPI publie ses chiffres sur l'URL
/metricset écrit ses logs sur sa sortie standard (stdout). - Prometheus va chercher (
scrape)/metricstoutes les 30s et stocke les chiffres. - Alloy ramasse les logs des pods et les pousse vers Loki.
- Loki stocke et indexe les logs.
- Grafana se branche sur Prometheus (chiffres) et Loki (logs) via des « datasources » et affiche tout.
Détail important sur le sens des flèches : Prometheus va chercher les données lui-même (pull), Alloy les lui envoie (push). C'est une vraie différence de conception entre métriques et logs.
Le schéma d'ensemble (installation + runtime) :

Plan A : comment c'est installé (GitOps)
L'amorçage : Ansible, puis ArgoCD prend le relais
Au démarrage du cluster (aws-start), le bootstrap Ansible fait deux choses,
une seule fois, puis ne touche plus à rien :
- Il installe ArgoCD (le moteur GitOps), via Helm.
- Il applique le
root-app.yaml(l'équivalent d'unkubectl apply -f root-app.yaml).
Ensuite ArgoCD prend le relais : il déploie et maintient tout le reste tout seul.
Image : Ansible appuie sur le bouton de démarrage (impératif, one-shot), ArgoCD est le pilote automatique qui prend ensuite la main (déclaratif, continu).
C'est quoi une « Application » ArgoCD ?
Un objet kind: Application, c'est une fiche qui dit à ArgoCD :
« Prends CE contenu (source), installe-le LÀ (destination), et garde-le synchronisé (syncPolicy). »
Trois blocs : source, destination, syncPolicy. C'est tout.
Le fichier root-app.yaml (le chef d'orchestre)
apiVersion: argoproj.io/v1alpha1
kind: Application # c'est une "Application" ArgoCD
metadata:
name: apps # son nom : "apps"
namespace: argocd # elle vit dans le namespace d'ArgoCD
finalizers:
- resources-finalizer.argocd.argoproj.io # à la suppression, supprime AUSSI ce qu'elle a créé (teardown propre)
spec:
project: default
source: # QUOI déployer
repoURL: https://gitlab.com/yk-devops/fastapi-eks-project.git
targetRevision: develop # quelle branche lire
path: k8s/platform/argocd-apps # quel DOSSIER lire
destination: # OÙ déployer
server: https://kubernetes.default.svc # le cluster local
namespace: argocd
syncPolicy:
automated:
prune: true # si je retire un fichier de Git, supprime-le du cluster
selfHeal: true # si on modifie à la main dans le cluster, ArgoCD remet l'état de Git
Ce qui rend ce fichier spécial : son path pointe vers le dossier
k8s/platform/argocd-apps/... qui contient les autres fichiers Application
(fastapi.yaml, kube-prometheus-stack.yaml, loki.yaml, alloy.yaml...).
Donc apps est une Application dont le contenu, ce sont d'autres Applications.
C'est le pattern app-of-apps : on installe UNE seule chose à la main (le
root), et elle déploie tout le reste. Pour ajouter une brique, on dépose un
fichier dans ce dossier, ArgoCD le découvre tout seul.
root-app (apps)
└── surveille k8s/platform/argocd-apps/
├── fastapi.yaml → déploie l'appli
├── kube-prometheus-stack.yaml → déploie Prometheus + Grafana
├── monitoring.yaml → ServiceMonitor + dashboards + datasource Loki
├── loki.yaml → déploie Loki
└── alloy.yaml → déploie Alloy
Un exemple de brique : kube-prometheus-stack.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: kube-prometheus-stack
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "0" # ORDRE : vague 0 = installé en premier
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources: # "sourceS" au pluriel = multi-source (2 origines)
- repoURL: https://prometheus-community.github.io/helm-charts
chart: kube-prometheus-stack
targetRevision: 86.2.2 # VERSION figée de la recette (reproductible)
helm:
valueFiles:
- $values/k8s/platform/monitoring/values.yaml # nos réglages
- repoURL: https://gitlab.com/yk-devops/fastapi-eks-project.git
targetRevision: develop
ref: values # cette 2e source sert juste à fournir "$values"
destination:
server: https://kubernetes.default.svc
namespace: monitoring # installé dans le namespace "monitoring"
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true # crée le namespace s'il n'existe pas
- ServerSideApply=true # contourne une limite d'ArgoCD sur les gros fichiers CRD
loki.yaml et alloy.yaml suivent exactement le même moule (multi-source :
chart Grafana + nos values, destination namespace: logging, sync-waves 1 et 2).
Une fois qu'on lit un de ces fichiers, on les lit tous.
Le pourquoi des grands choix (plan A)
| Champ / choix | Ce que ça fait | Le pourquoi |
|---|---|---|
sources (pluriel) |
multi-source : la recette vient d'internet, nos réglages de notre Git | On ne modifie pas la recette publique, on la pilote depuis notre repo (diffable en review) |
targetRevision: 86.2.2 |
version figée du chart | Reproductibilité : même version à chaque déploiement |
$values/.../values.yaml |
nos réglages (rétention, ressources...) | Séparer « la recette » de « nos choix » |
sync-wave |
ordre d'installation | La stack pose les CRD en vague 0, les briques qui les utilisent suivent |
prune + selfHeal |
auto-suppression + anti-dérive | Git = la seule vérité, le cluster suit toujours Git |
CreateNamespace=true |
crée le namespace cible | Sinon l'install échoue (le namespace n'existe pas) |
ServerSideApply=true |
applique côté serveur | Les CRD de Prometheus sont énormes et dépassent une limite d'ArgoCD en mode normal |
namespace dédié |
range les composants (monitoring, logging) |
Séparation claire des responsabilités |
Le pourquoi des grands choix (composants)
| Choix | Le pourquoi |
|---|---|
| Loki en SingleBinary (pas distribué) | Cluster mono-node éphémère : l'archi microservices serait surdimensionnée |
| Stockage filesystem, pas de PVC | Le cluster est éteint chaque soir : inutile de garder l'historique (choix coût) |
| Rétention 24h (métriques et logs) | Même raison : aligné sur la durée de vie du cluster |
| Alloy plutôt que Promtail | Promtail est déprécié ; Alloy est son successeur (collecteur unifié) |
| Alloy en DaemonSet | Un agent par machine, pour ramasser les logs de tous les pods locaux |
/metrics non public |
Le scrape se fait en interne (ClusterIP) ; la vitrine, c'est Grafana |
| Labels low-cardinality (namespace/pod/container) | La cardinalité des labels est le 1er facteur d'explosion mémoire de Loki |
Bonus : valider sans cluster
Comme le cluster est éteint la plupart du temps, on valide un maximum en local avant de livrer (l'infra réelle confirmera au prochain démarrage) :
helm template <chart> -f values.yaml: vérifie que nos réglages sont acceptés et regarde ce qui serait déployé, sans cluster.alloy fmt/alloy validate: vérifie la config Alloy (syntaxe + câblage des composants).kubectl kustomize <dossier>: vérifie le rendu des manifests kustomize.mkdocs build: vérifie la doc.
C'est aussi pour ça que « l'infra est éteinte » n'empêche pas de livrer : on écrit la recette dans Git, ArgoCD l'appliquera tout seul au prochain démarrage du cluster.