Skip to content

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 /metrics et écrit ses logs sur sa sortie standard (stdout).
  • Prometheus va chercher (scrape) /metrics toutes 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) :

Architecture DevSecOps EKS


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 :

  1. Il installe ArgoCD (le moteur GitOps), via Helm.
  2. Il applique le root-app.yaml (l'équivalent d'un kubectl 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.