Grafana

Rôle

Visualisation et tableaux de bord pour la supervision de l'infrastructure Indio, en complément de Zabbix. Installé et configuré sur INDSUPV003 via un rôle Ansible dédié (/root/grafana/ansible/).

Correction par rapport à une version précédente de cette page

Une version antérieure de cette page affirmait qu'« aucune configuration Ansible n'existe dans ce dépôt » et que les datasources n'étaient « pas confirmées ». C'était faux/obsolète : /root/grafana/ansible/site.yml existe et déploie réellement le paquet, le TLS, la datasource Zabbix et des dashboards — détaillé ci-dessous.

Architecture

  • 1 nœud Rocky Linux 9.6 (clone de tmpl-rocky96-hardened) : INDSUPV003, IP 10.100.2.228, segment seg-supervision (10.100.2.224/28).
  • 1 vCPU / 2048 Mo RAM — dimensionnement minimal, cohérent avec un rôle de frontend de visualisation sans stockage de métriques propre (toutes les données viennent de l'API Zabbix).
  • Grafana sert HTTPS directement via son serveur intégré, sur le port 3000 — pas de reverse proxy local (httpd/nginx) ni de VIP HAProxy partagée, même exception que Kibana (voir HAProxy) pour un service qui gère déjà nativement son propre TLS.
  • Certificat TLS émis par la CA interne Vault PKI (pki_int/roles/infra-indio, voir Vault PKI), CN dashboards.infra.indio — fichiers statiques déployés depuis le dépôt (pas d'appel Vault dynamique dans ce rôle, même pattern que Zabbix/GLPI/ELK). Ce nom a nécessité un enregistrement DNS créé manuellement (voir Procédure manuelle).
  • Datasource Zabbix branchée : plugin communautaire alexanderzobnin-zabbix-app (v6.5.0, signé « Grafana Labs »), authentification par token API contre un compte de service Zabbix dédié en lecture seule (svc-grafana), interrogeant https://supervision.infra.indio/zabbix/api_jsonrpc.php.
  • Dashboards provisionnés (fichiers, dossier « Zabbix ») : le dashboard Zabbix Server par défaut du plugin (adapté à INDSUPV001), sa variante dupliquée pour INDSUPV002, et un template Linux Server générique. Le 3ᵉ dashboard livré par le plugin (« Zabbix System Status ») est explicitement retiré du provisioning — structurellement incompatible avec la version du plugin installée (voir Points d'attention).

Structure du dépôt

Fichier/Dossier Contenu
main.tf VM unique (pas de for_each, contrairement aux autres projets), clone du template, IP via NetBox
variables.tf vsphere_network = "seg-supervision", vm_name = "INDSUPV003", vm_cpu = 1, vm_ram = 2048
versions.tf Providers hashicorp/vsphere (>= 2.6.0), e-breuninger/netbox (>= 5.0.0, < 6.0.0)
ansible/site.yml Play unique, rôle grafana, hôte INDSUPV003
ansible/group_vars/grafana.yml grafana_fqdn, grafana_admin_password et zabbix_api_token (vaultés)
ansible/roles/grafana/tasks/main.yml Dépôt Nexus, paquet, TLS, grafana.ini, plugin Zabbix, correctif permissions, datasource, dashboards, vérifications post-déploiement
ansible/roles/grafana/files/tls/ Certificat/clé/CA Vault PKI (statiques)
ansible/roles/grafana/files/dashboards/ 4 JSON (3 provisionnés + zabbix_system_status.json gardé pour référence, non déployé)
flowchart LR
    subgraph SEGSUPV["seg-supervision — 10.100.2.224/28"]
        GRAF["INDSUPV003<br/>10.100.2.228<br/>grafana-server :3000 (TLS intégré)"]
    end
    USER["Utilisateur<br/>https://dashboards.infra.indio:3000"] --> GRAF
    GRAF -- "API JSON-RPC<br/>token svc-grafana (lecture seule)" --> ZBXAPI["Zabbix API<br/>supervision.infra.indio<br/>(INDSUPV004)"]
    ZBXAPI --> ZBXSRV["Zabbix Server HA<br/>INDSUPV001 / INDSUPV002"]
    ZBXSRV --> PGVIP(("VIP PostgreSQL<br/>10.100.2.195"))
    VAULT["Vault PKI<br/>pki_int/roles/infra-indio"] -. "certificat statique<br/>CN=dashboards.infra.indio" .-> GRAF

Approvisionnement du paquet Grafana — rpm.grafana.com (Fastly, 151.101.0.0/16) est bloqué en sortie sur ce réseau, contournement retenu :

sequenceDiagram
    participant CTRL as Nœud de contrôle (10.15.100.61)
    participant API as grafana.com/api
    participant DL as dl.grafana.com (Fastly, autre plage)
    participant NEXUS as Nexus — dépôt grafana-rpm (hosted)
    participant VM as INDSUPV003

    Note over CTRL,VM: rpm.grafana.com résout en 151.101.0.0/16 -- bloqué en sortie, cause exacte non identifiée
    CTRL->>API: GET /api/grafana/versions/stable
    API-->>CTRL: URL du RPM + sha256
    CTRL->>DL: téléchargement direct (plage Fastly différente, non bloquée)
    DL-->>CTRL: grafana_<version>_linux_amd64.rpm
    CTRL->>NEXUS: POST /service/rest/v1/components?repository=grafana-rpm
    Note over NEXUS: régénération repodata plus lente que d'habitude (~30-40s, paquet volumineux)
    VM->>NEXUS: dnf install grafana (grafana.repo, proxy=_none_)
    NEXUS-->>VM: paquet grafana

Provisioning Terraform

  • /root/grafana/main.tf : VM unique (pas de for_each, contrairement aux autres projets de l'infra), clone de data.vsphere_virtual_machine.template, IP assignée par NetBox (data.netbox_ip_addresses.vm, filtre dns_name en minuscules).
  • lifecycle.ignore_changes sur annotation, clone[0].template_uuid, clone[0].customize, disk[0].io_share_count (pattern standard de l'infra).
  • Variables (variables.tf) : vsphere_network = "seg-supervision", vm_gateway = "10.100.2.238", vm_netmask = 28, vm_name = "INDSUPV003", vm_cpu = 1, vm_ram = 2048.
  • Providers : hashicorp/vsphere (>= 2.6.0), e-breuninger/netbox (>= 5.0.0, < 6.0.0).
  • Identifiants vCenter/NetBox via terraform.tfvars (non versionné) ; modèle dans terraform.tfvars.example.
  • output "grafana_ip" expose l'IP effective attribuée.

Configuration Ansible

Playbook /root/grafana/ansible/site.yml, rôle unique grafana appliqué à l'hôte INDSUPV003. Étapes réelles de roles/grafana/tasks/main.yml, dans l'ordre :

  1. Déploie /etc/yum.repos.d/grafana.repo depuis grafana.repo.j2 — pointe vers le dépôt Nexus hébergé grafana-rpm (https://repo.infra.infra/repository/grafana-rpm/, proxy=_none_), pas vers rpm.grafana.com (voir schéma d'approvisionnement ci-dessus, cf. aussi Nexus).
  2. Installe le paquet grafana.
  3. Crée /etc/grafana/certs (grafana:grafana, mode 0750).
  4. Déploie le certificat/clé/CA Vault PKI (fichiers statiques de files/tls/) — notifie un restart.
  5. Déploie grafana.ini depuis grafana.ini.j2 : [server] protocol=https, http_port=3000, domain/root_url sur {{ grafana_fqdn }}, chemins des certificats ; [security] admin_user=admin, admin_password={{ grafana_admin_password }} (vaulté) — notifie un restart.
  6. Ouvre le port 3000/tcp dans firewalld.
  7. Installe le plugin communautaire alexanderzobnin-zabbix-app via grafana-cli --homepath /usr/share/grafana plugins install ... (idempotent via creates:).
  8. Tâche corrective dédiée : reprend la propriété de /var/lib/grafana/plugins (grafana:grafana, recurse: true) — grafana-cli exécuté en sudo laisse ce dossier en root:root, invisible au process grafana-server (voir Points d'attention) — notifie un restart.
  9. Déploie le provisioning de la datasource Zabbix (zabbix-datasource.yaml.j2/etc/grafana/provisioning/datasources/zabbix.yaml) : type alexanderzobnin-zabbix-datasource, access: proxy, URL de l'API Zabbix, authType: token, apiToken vaulté ({{ zabbix_api_token }}), trends: true.
  10. Crée le dossier de provisioning des dashboards (/etc/grafana/provisioning/dashboards/zabbix).
  11. Déploie 3 dashboards JSON (zabbix_server_dashboard.json, zabbix_server_dashboard_indsupv002.json, template_linux_server.json).
  12. Retire explicitement zabbix_system_status.json du dossier de provisioning (state: absent) — dashboard structurellement incompatible avec le backend du plugin (voir Points d'attention) ; le JSON reste dans files/dashboards/ pour référence uniquement.
  13. Déploie le provider de dashboards (dashboards-provider.yaml.j2/etc/grafana/provisioning/dashboards/zabbix.yaml) : provider de type file, dossier Grafana « Zabbix », foldersFromFilesStructure: false.
  14. Active et démarre grafana-server, puis force l'application des handlers (meta: flush_handlers).
  15. Attend que GET /api/health réponde 200 en HTTPS (12 tentatives, 5s d'intervalle).
  16. Vérifie que la datasource Zabbix est fonctionnelle : GET /api/datasources/uid/zabbix-datasource/health en Basic Auth admin, avec force_basic_auth: true explicite (voir Points d'attention), jusqu'à status == "OK".

Procédure manuelle

Plusieurs prérequis, réalisés une fois, ne sont couverts par aucune ressource Terraform/Ansible de ce dépôt :

  1. Approvisionnement du RPM Grafana dans Nexus (à refaire à chaque montée de version, rpm.grafana.com restant bloqué) :
    1. Depuis un poste avec accès Internet réel (nœud de contrôle) : interroger https://grafana.com/api/grafana/versions/stable pour obtenir l'URL du RPM et son sha256.
    2. Télécharger depuis dl.grafana.com (pas rpm.grafana.com) et vérifier le sha256.
    3. Uploader dans Nexus : POST /service/rest/v1/components?repository=grafana-rpm (asset yum.asset, dépôt yum hosted dédié — pas mélangé à un dépôt générique).
    4. Attendre la régénération des métadonnées yum (repodata/repomd.xml) avant de tester le dépôt — plus lente que pour un petit paquet (~30-40s observés pour ce RPM de l'ordre de 227 Mo).
    5. Aucune modification Ansible nécessaire ensuite si grafana.repo.j2 reste inchangé : dnf prend automatiquement la dernière version présente dans le dépôt.
  2. Émission du certificat TLS Vault PKI : vault write pki_int/issue/infra-indio common_name=dashboards.infra.indio ..., en récupérant le champ ca_chain complet (intermédiaire + racine), pas issuing_ca seul — sinon CERTIFICATE_VERIFY_FAILED: unable to get issuer certificate côté clients stricts. Le résultat (cert/clé/CA) est ensuite copié tel quel dans roles/grafana/files/tls/ — un renouvellement futur est donc une réémission manuelle suivie d'un remplacement de fichiers, pas un mécanisme dynamique.
  3. Enregistrement DNS : le nom dashboards.infra.indio a été créé manuellement comme enregistrement A sur le contrôleur de domaine (INDIDEN008) — toute création de nom fonctionnel *.infra.indio suit ce même geste manuel côté AD.
  4. Compte de service Zabbix svc-grafana (consommé par la datasource, étape 9 de la Configuration Ansible), créé via l'API Zabbix — aucune ressource IaC ne gère les objets Zabbix (comptes, groupes de droits, tokens) :
    1. Groupe d'utilisateurs dédié (hostgroup_rights en lecture seule, permission=2, sur tous les host groups existants).
    2. Utilisateur svc-grafana dans ce groupe, rôle stock « User role » (pas Admin).
    3. Génération du token API en 2 appels distincts (token.create puis token.generate — le premier ne renvoie que l'identifiant, pas la valeur secrète).
    4. La valeur obtenue est ensuite chiffrée dans group_vars/grafana.yml (zabbix_api_token) — jamais en clair dans le dépôt.

Procédure de déploiement

  1. terraform apply (dans /root/grafana) : clone INDSUPV003 et lui attribue son IP via NetBox.
  2. Prérequis manuels à satisfaire avant le premier ansible-playbook (voir Procédure manuelle ci-dessus) : RPM déjà présent dans le dépôt Nexus grafana-rpm, certificat TLS déjà émis et placé dans roles/grafana/files/tls/, DNS dashboards.infra.indio déjà créé, compte svc-grafana + token déjà générés côté Zabbix (sinon la vérification finale de l'étape 16 échoue).
  3. ansible-playbook site.yml (dans /root/grafana/ansible) : installe et configure Grafana de bout en bout. Playbook idempotent — un second run n'entraîne aucun changement.

Contrôle de santé / Vérification

  • curl -k https://dashboards.infra.indio:3000/api/health → code 200, JSON incluant "database":"ok" et la version installée.
  • curl -k -u admin:<mot_de_passe> https://dashboards.infra.indio:3000/api/datasources/uid/zabbix-datasource/health{"status":"OK","message":"Zabbix API version 8.0.0"} attendu.
  • openssl s_client -connect 10.100.2.228:3000 : vérifier CN=dashboards.infra.indio et la chaîne jusqu'à l'Intermediate CA Vault.
  • stat -c '%U:%G' /var/lib/grafana/plugins (et son contenu) : doit renvoyer grafana:grafana — régression classique après une réinstallation manuelle du plugin (voir Points d'attention).
  • ls /etc/grafana/provisioning/dashboards/zabbix/ : ne doit pas contenir zabbix_system_status.json (retiré intentionnellement).

Points d'attention

rpm.grafana.com bloqué en sortie — spécifique à la plage Fastly 151.101.0.0/16

Le dépôt Nexus proxy classique (remoteUrl=https://rpm.grafana.com, même pattern que Zabbix/PostgreSQL) ne fonctionne pas : la whitelist Squid est acceptée (CONNECT passe), mais la connexion time-out ensuite, car rpm.grafana.com résout uniquement sur des IP 151.101.0.0/16 (Fastly), bloquées en sortie quelque part entre la zone admin et Internet (cause exacte non identifiée, probablement le FortiGate). Ce n'est pas un blocage Fastly générique : dl.grafana.com (autre plage Fastly) répond normalement depuis le même nœud. Pour tout futur service dont le dépôt officiel est servi par Fastly, tester la plage IP réelle avant de supposer qu'une whitelist Squid suffit.

  • Permissions du dossier plugins : grafana-cli exécuté en sudo crée /var/lib/grafana/plugins/ en root:root s'il n'existait pas encore — le process grafana-server (utilisateur grafana) ne peut alors même pas lister ce dossier (open ... permission denied dans journalctl -u grafana-server), le plugin reste invisible (404 Plugin not found) sans aucune erreur explicite côté installation. Le correctif doit porter sur le dossier parent /var/lib/grafana/plugins avec recurse: true, pas seulement le sous-dossier du plugin — tâche Ansible dédiée, idempotente, toujours suivie d'un restart.
  • force_basic_auth: true requis pour tout healthcheck uri contre l'API Grafana : sans cette option, le module ansible.builtin.uri n'envoie url_username/url_password qu'après un défi HTTP 401 explicite du serveur — Grafana ne renvoie pas ce défi sur l'endpoint /api/datasources/.../health, donc la requête part sans authentification et boucle en 401 malgré des identifiants corrects.
  • Émission de certificat Vault PKI : utiliser le champ ca_chain (intermédiaire + racine), jamais issuing_ca seul — piège potentiellement présent (non vérifié) sur les autres rôles *_tls de l'infra (Zabbix, GLPI, ELK) qui utilisent des certificats émis manuellement en amont ; personne n'a testé validate_certs: true dessus à ce jour.
  • Dashboard « Zabbix System Status » retiré, pas un faux négatif : confirmé au niveau du code source du plugin (alexanderzobnin/grafana-zabbix, pipeline queryData) — seuls les modes MODE_METRICS et MODE_ITEMID sont supportés par les panels Grafana core (table/stat) ; le mode MODE_TRIGGERS utilisé par ce dashboard tombe systématiquement en erreur (ErrNonMetricQueryNotSupported). Incompatibilité structurelle avec la version du plugin installée (6.5.0), pas un problème de configuration réparable sans le panel custom dédié du plugin (non utilisé par ce dashboard exporté).
  • 3 panels vides sur le dashboard dédié à INDSUPV002 (Queue, NVPS, processus Zabbix) : leurs items de self-monitoring (Zabbix server: Queue, etc.) n'existent que sur INDSUPV001 — le template applicatif Zabbix correspondant n'a jamais été attaché au 2ᵉ nœud lors du bootstrap du cluster HA. Correction possible (attacher le bon template côté Zabbix) mais non faite — décision de supervision hors périmètre d'un provisioning de dashboard.
  • Dimensionnement minimal (1 vCPU / 2 Go) : Grafana ne stocke aucune métrique lui-même (tout vient de l'API Zabbix) — à revoir si le nombre de dashboards/utilisateurs simultanés augmente sensiblement.
  • Secrets : grafana_admin_password et zabbix_api_token chiffrés via Ansible Vault dans group_vars/grafana.yml ; clé privée TLS statique dans roles/grafana/files/tls/ (pas d'émission dynamique au déploiement) — voir gestion des secrets.