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 :
- Série vers colonnes (
seriesToColumns) : fusionne les séries par le champn - Filtrer les champs (
filterFieldsByName) : ne garde quen,address, et les valeurs - 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).

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.

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

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)

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 :
1→ UP (vert)0→ DOWN (rouge)

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_replacepour joindre des métriques hétérogènes dans un tableau- Transformation
seriesToColumnspour 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) :
- Menu hamburger → Dashboards
- Le dashboard apparaît dans la liste "General"
- 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