Partie 16 : Dashboards Grafana personnalisés

Partie 16 : Dashboards Grafana personnalisés

Février 2026

Le besoin

Prometheus collecte les métriques du cluster, mais sans dashboards.

On va créer un dashboard personnalisé provisionné automatiquement via GitOps :

  • Cluster K3s Overview : santé globale du cluster Raspberry Pi (CPU, RAM, disque, réseau, pods)

Le provisioning via ConfigMap

Grafana dans kube-prometheus-stack embarque un sidecar qui surveille les ConfigMaps Kubernetes. Dès qu'il détecte un ConfigMap avec le label grafana_dashboard: "1", il charge automatiquement le dashboard JSON contenu dedans.

┌──────────────────────────────────────────────────┐
│                Grafana Pod                        │
│  ┌──────────┐        ┌─────────────────────┐     │
│  │ Grafana  │◀───────│ Sidecar (k8s-sidecar)│    │
│  │          │        │ surveille ConfigMaps │     │
│  └──────────┘        └──────────┬──────────┘     │
│                                  │                │
└──────────────────────────────────┼────────────────┘
                                   │ watch
                          ┌────────┴────────┐
                          │   ConfigMaps    │
                          │ label:          │
                          │ grafana_dashboard│
                          │ = "1"           │
                          └─────────────────┘

L'avantage par rapport à créer les dashboards dans l'UI : ils sont versionnés dans Git et redéployés automatiquement via ArgoCD. Si quelqu'un casse un dashboard, un simple sync le restaure.

Piège Helm : échapper les accolades

Le JSON des dashboards Grafana contient des {{ instance }} et {{ namespace }} dans les legendFormat. Problème : Helm interprète les {{ }} comme des templates Go et plante avec function "instance" not defined.

La solution : entourer ces expressions avec la syntaxe Helm raw string :

"legendFormat": "{{`{{ instance }}`}}"

Les backticks disent à Helm de traiter le contenu comme du texte brut.

Dashboard : Cluster K3s Overview

Ce dashboard est le point d'entrée principal du monitoring — celui qu'on ouvre en premier. Il contient 5 panneaux organisés en 3 sections : les nœuds, les graphiques de tendance, et les applications.

Fichier : helm-charts/monitoring/templates/dashboard-cluster-overview.yaml

Panneau 1 : Info Nodes (Table)

C'est le panneau le plus complexe du dashboard. Il combine 6 requêtes PromQL différentes dans un seul tableau grâce aux transformations Grafana.

Cible Métrique Description
A kube_node_status_addresses{type="InternalIP"} Adresse IP du nœud
B 100 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100 CPU %
C (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 RAM %
D (1 - node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100 Disque %
E node_memory_MemTotal_bytes RAM totale
F node_time_seconds - node_boot_time_seconds Uptime

L'astuce : label_replace pour joindre les métriques

Le problème : chaque métrique utilise un label différent pour identifier le nœud (node, instance). Pour les fusionner dans un tableau, on utilise label_replace pour créer un label commun n :

# Exemple pour le CPU
label_replace(
  100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100),
  "n", "$1", "instance", "(.+)"
)

Transformations Grafana

Les 6 résultats sont combinés via 3 transformations :

  1. Série vers colonnes (seriesToColumns) : fusionne les séries par le champ n
  2. Filtrer les champs (filterFieldsByName) : ne garde que n, address, et les valeurs
  3. Organiser (organize) : renomme les colonnes (Nœud, IP, CPU %, RAM %, etc.)

Seuils visuels

Les colonnes CPU, RAM et Disque utilisent des jauges colorées :

  • Vert : < 60%
  • Jaune : 60-80%
  • Rouge : > 80%

La RAM totale est affichée en bytes (auto-formaté en Go), et l'uptime en durée humaine (jours, heures).

Dashboard_k3s.jpeg

Panneau 2 : CPU par nœud (Timeseries)

Graphique temporel montrant l'évolution du CPU de chaque nœud.

100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

La légende utilise le label instance pour afficher le hostname de chaque nœud.

Piège Go template

Dans un chart Helm, les {{ instance }} dans le JSON Grafana sont interprétés comme des directives Go template. La solution : les échapper de cette façon :

{{` {{ instance }} `}}

Sans ça, Helm crashe au rendu du template avec une erreur cryptique.

Dashboard_k3s_graph_cpu.jpeg

Panneau 3 : RAM par nœud (Timeseries)

Même principe que le CPU, en pourcentage :

(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100

Dashboard_k3s_graph_ram.jpeg

Panneau 4 : Applications (Table)

Tableau listant toutes les applications déployées (hors monitoring et kube-system) avec leur statut.

Cible Métrique Description
A kube_deployment_status_replicas Replicas total
B kube_deployment_status_replicas_ready Replicas prêts
C kube_pod_info + label_replace Nœud d'exécution
D container_memory_working_set_bytes RAM utilisée (MB)
E increase(kube_pod_container_status_restarts_total[24h]) Restarts 24h
G kube_pod_container_status_restarts_total Restarts total

Extraire le nom du deployment depuis le pod

Le nom du pod contient le hash du ReplicaSet (ex: radarr-6bf6567f85-f7xqt). Pour retrouver le deployment, on utilise label_replace avec une regex :

label_replace(
  kube_pod_info{namespace!~"monitoring|kube-system"},
  "deployment", "$1", "pod", "(.+)-[a-f0-9]{7,10}-[a-z0-9]{5}"
)

Seuils sur les restarts

La colonne "Restarts 24h" est colorée :

  • Vert : 0 restarts
  • Jaune : 1-4 restarts
  • Rouge : 5+ restarts (problème à investiguer)

Dashboard_k3s_list_pods.jpeg

Panneau 5 : Pods Monitoring (Table)

Tableau dédié aux pods du namespace monitoring — parce qu'on veut surveiller le monitoring lui-même.

# Info des pods
max by (pod, node) (kube_pod_info{namespace="monitoring"})

# Statut ready
min by (pod) (kube_pod_status_ready{namespace="monitoring", condition="true"})

Le statut est affiché avec un mapping :

  • 1UP (vert)
  • 0DOWN (rouge)

Dashboard_k3s_pods_moni.jpeg

Provisioning via ConfigMap

Le dashboard est un ConfigMap Helm provisionné automatiquement :

apiVersion: v1
kind: ConfigMap
metadata:
  name: grafana-dashboard-cluster-overview
  namespace: monitoring
  labels:
    grafana_dashboard: "1"    # Le sidecar Grafana détecte ce label
data:
  cluster-overview.json: |-
    { ... }

Le JSON est embarqué dans le ConfigMap, versionné dans Git, et redéployé automatiquement via ArgoCD. Si quelqu'un casse un dashboard, un simple sync le restaure.

Techniques clés utilisées

  • label_replace pour joindre des métriques hétérogènes dans un tableau
  • Transformation seriesToColumns pour fusionner les colonnes
  • Échappement Go template avec backticks pour les légendes Helm
  • Seuils colorés avec jauges sur les pourcentages
  • Mapping de valeurs (1=UP, 0=DOWN) pour le statut des pods

Ajustement des ressources

Le déploiement initial a révélé que Prometheus et Grafana étaient sous-dimensionnés pour un cluster avec autant de métriques.

Symptômes observés

  • Prometheus : 503 Service Unavailable, liveness/readiness probes en timeout
  • Grafana : chargement des dashboards extrêmement lent

Diagnostic

kubectl top pod -n monitoring --sort-by=memory
Composant Avant (limite) Consommation réelle Après (limite)
Prometheus 512Mi / 500m 506Mi / 421m 1Gi / 1000m
Grafana 256Mi / 200m 371Mi / 202m 512Mi / 500m

Les deux étaient au plafond de leurs limites, causant du throttling CPU et des OOM potentiels.

Leçon apprise

Sur Raspberry Pi, il y a des limites de ressources du monitoring. Prometheus et Grafana ont besoin de mémoire pour charger les time series et rendre les dashboards. Mieux vaut des limites un peu hautes que des services instables.

Structure des fichiers

helm-charts/monitoring/
├── Chart.yaml
├── values.yaml                          # Ressources ajustées + relabeling hostnames
└── templates/
    ├── alerts.yaml                      # Alertes (Partie 15)
    ├── sealed-secret-grafana.yaml       # Credentials Grafana
    └── dashboard-cluster-overview.yaml  # Dashboard Cluster K3s

Vérification

Après le push et la synchronisation ArgoCD :

# Vérifier que les ConfigMaps sont créés
KUBECONFIG=~/.kube/config-k3s kubectl get configmap -n monitoring | grep dashboard

# Résultat attendu :
# grafana-dashboard-cluster-overview   1      ...

# Vérifier que Prometheus est stable
KUBECONFIG=~/.kube/config-k3s kubectl top pod -n monitoring --sort-by=memory

Dans Grafana (grafana.home-fonta.fr) :

  1. Menu hamburger → Dashboards
  2. Le dashboard apparaît dans la liste "General"
  3. Cluster K3s Overview : tous les panneaux affichent des données avec les hostnames

Impact ressources

Les dashboards eux-mêmes sont de simples ConfigMaps (quelques Ko). L'ajustement des limites ressources est le vrai changement :

Composant Requests mémoire Limits mémoire Requests CPU Limits CPU
Prometheus 512Mi 1Gi 200m 1000m
Grafana 256Mi 512Mi 100m 500m

Le rpi4-master (8Go) peut absorber ces limites sans problème (sauf s'il est déjà prit par d'autres applis).


Prochain article : Partie 17 — Dashboard Pi-hole