Aller au contenu

Grafana

Grafana est l'outil principal de visualisation de PerfShop. Il agrège les quatre puits de télémétrie (Prometheus, Loki, Tempo, Pyroscope), expose 10 dashboards livrés (5 Élèves + 5 Formateurs) avec une politique d'accès différenciée, et permet l'exploration interactive en mode Explore.

Source de vérité

Cette page est tirée du bloc grafana dans les fichiers compose, des fichiers de provisioning grafana/provisioning/datasources/*.yml, grafana/provisioning/dashboards/dashboards.yml, et du script d'initialisation grafana-seed/seed.py.

Version pinned

PerfShop fige Grafana à la version 12.0.0 (image grafana/grafana:12.0.0). Les autres composants d'observabilité utilisent latest ; Grafana est l'exception, parce que :

  • Les 10 dashboards livrés reposent sur des fonctionnalités de panel (flamegraph, traces table, TraceQL editor, format auto-units) introduites en Grafana 11+ et stabilisées en 12.
  • Le seed Python utilise l'API legacy POST /api/folders/{uid}/permissions qui pourrait évoluer dans une version majeure ultérieure.
  • Les datasources Pyroscope et Tempo dépendent de plugins natifs dont le format de configuration peut changer.

Une mise à jour vers une version ultérieure devra être validée à la main contre les 10 dashboards.

Configuration via variables d'environnement

environment:
  - GF_SECURITY_ADMIN_USER=admin
  - GF_SECURITY_ADMIN_PASSWORD=${GF_SECURITY_ADMIN_PASSWORD:-perfshop}
  - GF_USERS_ALLOW_SIGN_UP=false
  - GF_SERVER_ROOT_URL=${PUBLIC_GRAFANA_URL:-http://localhost:3002}
  - GF_PANELS_DISABLE_SANITIZE_HTML=true
  - GF_AUTH_ANONYMOUS_ENABLED=true
  - GF_AUTH_ANONYMOUS_ORG_NAME=Main Org.
  - GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer
  - GF_USERS_DEFAULT_LANGUAGE=${PERFSHOP_UI_LOCALE:-en-US}
Variable Effet
GF_SECURITY_ADMIN_USER / _PASSWORD Compte superadmin Grafana, utilisé par le seed et par le formateur
GF_USERS_ALLOW_SIGN_UP=false Désactive l'auto-inscription depuis l'UI
GF_SERVER_ROOT_URL URL publique utilisée pour les liens absolus (mails, partage, OAuth)
GF_PANELS_DISABLE_SANITIZE_HTML=true Permet aux panels Text d'utiliser du HTML brut (utilisé par les panels « Guide » des dashboards APM)
GF_AUTH_ANONYMOUS_ENABLED=true Accès anonyme activé — clé de la stratégie pédagogique
GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer Les utilisateurs anonymes ont uniquement le rôle Viewer (lecture seule)
GF_USERS_DEFAULT_LANGUAGE Locale UI Grafana (fr-FR ou en-US), pilotée par PERFSHOP_UI_LOCALE

Le port management interne de Grafana est 3000, exposé sur le port hôte 3002 par défaut (variable GRAFANA_HTTP_PORT).

Le healthcheck Docker interroge /api/health toutes les 10 secondes avec start_period: 30s :

healthcheck:
  test: ["CMD-SHELL", "wget -q --spider http://localhost:3000/api/health || exit 1"]
  interval: 10s
  timeout: 5s
  retries: 10
  start_period: 30s

Datasources provisionnées

Grafana lit automatiquement le dossier grafana/provisioning/datasources/ au démarrage et y déclare quatre datasources, chacune avec un UID stable utilisé par les dashboards JSON.

flowchart LR
  G["Grafana 12.0.0"]
  G --> P["uid: prometheus<br/>http://prometheus:9090<br/>(default)"]
  G --> L["uid: loki<br/>http://perfshop-loki:3100"]
  G --> T["uid: tempo<br/>http://perfshop-tempo:3200"]
  G --> PY["uid: pyroscope<br/>http://perfshop-pyroscope:4040"]

Datasource Prometheus

- name: Prometheus
  type: prometheus
  uid: prometheus
  access: proxy
  url: http://prometheus:9090
  isDefault: true
  editable: true
  jsonData:
    timeInterval: "5s"
  • isDefault: true : c'est la datasource sélectionnée par défaut dans l'éditeur de panel et dans Explore.
  • timeInterval: "5s" : informe Grafana du scrape_interval réel pour calibrer correctement les fonctions rate() et irate() quand l'utilisateur ne précise pas la fenêtre.

Datasource Loki

- name: Loki
  type: loki
  uid: loki
  access: proxy
  url: http://perfshop-loki:3100
  isDefault: false
  editable: true
  jsonData:
    maxLines: 1000
    timeout: 60
  • maxLines: 1000 : limite le nombre de lignes retournées par défaut dans les panels logs (peut être surchargé par panel).
  • timeout: 60 : timeout de query côté Grafana en secondes.

Datasource Tempo

- name: Tempo
  type: tempo
  uid: tempo
  access: proxy
  url: http://perfshop-tempo:3200
  isDefault: false
  editable: true
  jsonData:
    httpMethod: GET
    tracesToLogsV2:
      datasourceUid: loki
      spanStartTimeShift: "-1m"
      spanEndTimeShift: "1m"
      tags:
        - key: service.name
          value: app
      filterByTraceID: true
      filterBySpanID: false
      customQuery: false
    tracesToMetrics:
      datasourceUid: prometheus
      spanStartTimeShift: "-1m"
      spanEndTimeShift: "1m"
      tags:
        - key: service.name
          value: app
    serviceMap:
      datasourceUid: prometheus
    search:
      hide: false
    nodeGraph:
      enabled: true
    lokiSearch:
      datasourceUid: loki

C'est la datasource la plus richement configurée. Trois corrélations croisées sont activées :

  • tracesToLogsV2 → Loki : depuis n'importe quel span dans la vue trace, on peut cliquer pour ouvrir Loki avec un filtre temporel ±1 min autour du span et un filtre traceID automatique.
  • tracesToMetrics → Prometheus : depuis un span, on peut afficher les métriques Prometheus de l'opération sur la même fenêtre temporelle.
  • serviceMap : utilise les métriques traces_service_graph_* produites par le metrics_generator de Tempo et stockées dans Prometheus pour générer la carte des services.
  • nodeGraph: enabled: true : active la visualisation graphe pour les traces.
  • lokiSearch : permet la recherche de logs corrélés directement depuis l'éditeur Tempo.

Datasource Pyroscope

- name: Pyroscope
  type: grafana-pyroscope-datasource
  uid: pyroscope
  access: proxy
  url: http://perfshop-pyroscope:4040
  isDefault: false
  editable: true

Configuration minimale — Pyroscope n'a pas de corrélations spéciales à configurer ; le plugin natif de Grafana gère tout.

Dossiers et provisioning des dashboards

Les dashboards sont organisés en deux dossiers distincts dans Grafana, avec deux providers de provisioning séparés.

# grafana/provisioning/dashboards/dashboards.yml
apiVersion: 1
providers:
  - name: 'PerfShop Dashboards  Élèves'
    orgId: 1
    folder: 'Élèves'
    folderUid: 'perfshop-eleves'
    type: file
    disableDeletion: false
    updateIntervalSeconds: 10
    allowUiUpdates: true
    options:
      path: /etc/grafana/dashboards/eleves

  - name: 'PerfShop Dashboards  Formateurs'
    orgId: 1
    folder: 'Formateurs'
    folderUid: 'perfshop-formateurs'
    type: file
    disableDeletion: false
    updateIntervalSeconds: 10
    allowUiUpdates: true
    options:
      path: /etc/grafana/dashboards/formateurs
Aspect Dossier Élèves Dossier Formateurs
folderUid perfshop-eleves perfshop-formateurs
Chemin source ./grafana/dashboards/eleves/ ./grafana/dashboards/formateurs/
Nombre de dashboards 5 5
ACL après seed Hérite du rôle Viewer (visible sans login) Admin uniquement (posée par le seed)
updateIntervalSeconds 10 (Grafana relit les fichiers toutes les 10 s) 10
allowUiUpdates true true
disableDeletion false false

Les bind mounts dans le compose :

volumes:
  - grafana-data:/var/lib/grafana
  - ./grafana/provisioning:/etc/grafana/provisioning
  - ./grafana/dashboards:/etc/grafana/dashboards

Stratégie d'accès — l'élément clé

PerfShop sépare nettement deux populations d'utilisateurs sur Grafana :

flowchart TB
  V["Utilisateur anonyme<br/>(pas de login)"]
  A["Admin connecté<br/>(login admin/perfshop)"]

  subgraph G["Grafana"]
    direction TB
    FE["Dossier Élèves<br/>(visible Viewer)"]
    FF["Dossier Formateurs<br/>(ACL Admin-only)"]
  end

  V -->|GF_AUTH_ANONYMOUS_ENABLED=true<br/>Role=Viewer| FE
  V -.-|❌ HTTP 403| FF
  A -->|✅| FE
  A -->|✅| FF

Comment ça marche concrètement

  1. GF_AUTH_ANONYMOUS_ENABLED=true + GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer déclarés en variables d'environnement → tout visiteur arrivant sur l'URL Grafana est automatiquement authentifié comme un utilisateur anonyme avec le rôle Viewer dans l'org "Main Org.".
  2. Par défaut, Grafana applique l'héritage organisationnel : un Viewer voit tous les dossiers de son org.
  3. Le seed perfshop-grafana-seed applique une ACL explicite sur le dossier Formateurs : Admin uniquement. Cette ACL remplace l'héritage Viewer pour ce dossier — résultat, les utilisateurs anonymes voient le dossier Élèves mais pas le dossier Formateurs.

Le seed grafana-seed/seed.py

Le service perfshop-grafana-seed est un container python:3.11-slim qui :

  1. Installe requests==2.31.0 au démarrage.
  2. Attend que Grafana réponde sur /api/health (boucle retry, timeout 7m30).
  3. Vérifie que le dossier Formateurs est bien provisionné (boucle retry sur GET /api/folders/{uid}).
  4. Pose l'ACL Admin-only via POST /api/folders/perfshop-formateurs/permissions :
    { "items": [{ "role": "Admin", "permission": 4 }] }
    
    permission: 4 = Admin (1=View, 2=Edit, 4=Admin). Un seul item suffit : l'API replace toute l'ACL existante.
  5. Vérifie que l'ACL est effective en faisant un appel sans authentification sur /api/folders/perfshop-formateurs — doit retourner 401 ou 403.
  6. Configure le dashboard home sur perfshop-general-v1 (« Vue Générale Containers ») via PATCH /api/org/preferences avec {"homeDashboardUID": "perfshop-general-v1"}.

Le seed est marqué restart: "no" (one-shot) et depends_on: grafana: condition: service_healthy.

sequenceDiagram
  autonumber
  participant S as perfshop-grafana-seed
  participant G as Grafana

  S->>G: GET /api/health<br/>(boucle 5s × 90)
  G-->>S: 200 OK

  S->>G: GET /api/folders/perfshop-formateurs<br/>(auth admin)<br/>(boucle 5s × 24)
  G-->>S: 200 (dossier provisionné)

  S->>G: POST /api/folders/perfshop-formateurs/permissions<br/>{"items":[{"role":"Admin","permission":4}]}
  G-->>S: 200 OK

  S->>G: GET /api/folders/perfshop-formateurs<br/>(SANS auth)
  G-->>S: 401 ou 403
  Note over S: ✓ ACL effective

  S->>G: PATCH /api/org/preferences<br/>{"homeDashboardUID":"perfshop-general-v1"}
  G-->>S: 200 OK

  Note over S: Sortie code 0

Idempotence du seed

Le seed peut être relancé manuellement à tout moment (docker compose up perfshop-grafana-seed) sans effet indésirable : POST /api/folders/{uid}/permissions remplace l'ACL complète, et PATCH /api/org/preferences est une simple mise à jour.

Volumes et données persistantes

Volume Montage Contenu
grafana-data (volume nommé) /var/lib/grafana Base SQLite Grafana, sessions utilisateurs, plugins installés, dashboards créés à la main, ACL personnalisées
./grafana/provisioning (bind mount) /etc/grafana/provisioning Fichiers de provisioning datasources et dashboards (lecture seule en pratique)
./grafana/dashboards (bind mount) /etc/grafana/dashboards 10 fichiers JSON livrés (Élèves + Formateurs)

Modifier un dashboard livré

Les dashboards JSON livrés sont rechargés toutes les 10 s par Grafana (updateIntervalSeconds: 10). Une modification faite dans l'UI est conservée pendant 10 s puis écrasée par le contenu du fichier JSON. Pour modifier durablement un dashboard livré, il faut éditer le fichier JSON correspondant dans grafana/dashboards/{eleves|formateurs}/ et le repackager dans le déploiement.

Pour aller plus loin