Zabbix¶
Rôle¶
Plateforme de supervision centrale de l'infrastructure Indio : collecte de métriques (agents fleet-wide, vCenter, SNMP FortiGate, JMX), alerting et source de données pour les tableaux de bord Grafana. Zabbix Server tourne en cluster HA natif à 2 nœuds (mécanisme propre à Zabbix, indépendant de toute VIP), adossé à un backend PostgreSQL hautement disponible. Services et SLA sont modélisés pour suivre la disponibilité des briques critiques de l'infra (HAProxy, PostgreSQL, Vault, AD, VMware) avec une cible mensuelle de 99.9%.
Architecture¶
- 3 VM provisionnées par Terraform sur le segment
seg-supervision(10.100.2.224/28) :INDSUPV001(10.100.2.226) etINDSUPV002(10.100.2.227) — cluster Zabbix Server HA natif (bascule gérée nativement par le processzabbix_servervia la table interneha_node, pas de keepalived côté serveur — voir contraste avec PostgreSQL ci-dessous).INDSUPV004(10.100.2.229) — frontend web (zabbix-websur httpd/php-fpm). Installé hors IaC : ce dépôt ne fait que provisionner la VM (Terraform) et poser le TLS (rôlezabbix_tls) ; aucun rôle Ansible ne déploie le frontend lui-même ni sa propre connexion à la base (zabbix.conf.php, configuré manuellement).
- Backend base de données : PostgreSQL, via la VIP
10.100.2.195(cluster repmgr/keepalived, voir PostgreSQL), basezabbix. Les 2 nœuds Zabbix Server pointent sur cette VIP plutôt que sur un nœud PostgreSQL nommément désigné, ce qui rend le backend transparent à un failover PostgreSQL. - Contraste HA important : contrairement à PostgreSQL (IP flottante keepalived unique), le cluster Zabbix Server HA natif n'expose aucune IP virtuelle. Les clients (agents fleet-wide) doivent connaître explicitement les deux IP réelles des nœuds — c'est cette différence de mécanisme qui est à l'origine de l'incident du 2026-07-16 (voir ci-dessous).
- Frontend exposé en HTTPS (
supervision.infra.indio) via un certificat Vault PKI (pki_int/roles/infra-indio), redirection HTTP→HTTPS forcée. Certificats déployés en fichiers statiques (pas d'intégration Vault dynamique dans ce rôle), même schéma que GLPI/ELK. zabbix-java-gatewaylocal (port 10052) pour le polling JMX (Kafka notamment, partiellement fonctionnel — voir Points d'attention).- Collecteurs VMware natifs activés (
StartVMwareCollectors=2danszabbix_server.conf), certificat CA vCenter (VMCA auto-signé) déployé dans le trust store des nœuds serveur. - Aucun objet Zabbix n'est versionné en IaC : templates (VMware, FortiGate, PostgreSQL, HAProxy, Vault, Elasticsearch), hosts, macros, groupes d'utilisateurs, comptes de service, Services et SLA sont tous créés via l'API/UI Zabbix — seule la configuration serveur/agent (
zabbix_server.conf,zabbix_agentd.conf) est versionnée dans ce dépôt et dans/root/baseline. Un rebuild complet depuis Terraform+Ansible seuls ne restaurerait donc aucun de ces objets (voir Procédure manuelle). - Agents supervisés : déployés fleet-wide par le dépôt baseline-config (rôle
zabbix_agent), avec des varianteszabbix_agent2_postgresql(nœuds PostgreSQL) etzabbix_agent2_mysql(GLPI/OCS/Guacamole, qui utilisent chacun un MariaDB local) pour exposer les clés de métriques natives (pgsql.*,mysql.*) que l'agent classique ne connaît pas.
Structure du dépôt¶
| Fichier/Dossier | Contenu |
|---|---|
main.tf |
3 VM (for_each), clone du template, IP via NetBox |
variables.tf |
vsphere_network = "seg-supervision", nodes = [INDSUPV001, INDSUPV002, INDSUPV004], vm_cpu = 2, vm_ram = 4096 |
versions.tf |
Providers hashicorp/vsphere (>= 2.6.0), e-breuninger/netbox (>= 5.0.0, < 6.0.0) |
ansible/site.yml |
2 plays : zabbix_server (001/002) puis zabbix_tls (004) |
ansible/inventory.ini |
Groupes zabbix_server/zabbix_web, ha_node_name/node_address par hôte |
ansible/group_vars/zabbix_server.yml |
DBHost = VIP PostgreSQL, identifiants DB vaultés |
ansible/group_vars/zabbix_web.yml |
zabbix_fqdn = supervision.infra.indio |
ansible/roles/zabbix_server/ |
Paquets serveur + Java Gateway, certificat vCenter (trust store), zabbix_server.conf, SELinux, firewalld 10050/10051 |
ansible/roles/zabbix_tls/ |
mod_ssl, certificat Vault PKI, vhost SSL, redirection HTTP→HTTPS, vérification post-déploiement |
Topologie HA¶
flowchart TB
subgraph SEGSUPV["seg-supervision — 10.100.2.224/28"]
S1["INDSUPV001<br/>10.100.2.226<br/>zabbix-server (HA)"]
S2["INDSUPV002<br/>10.100.2.227<br/>zabbix-server (HA)"]
WEB["INDSUPV004<br/>10.100.2.229<br/>frontend zabbix-web<br/>(install hors IaC)"]
end
PGVIP(("VIP PostgreSQL<br/>10.100.2.195"))
S1 -. "table ha_node — 1 actif / 1 standby" .-> S2
S1 --> PGVIP
S2 --> PGVIP
WEB --> PGVIP
FLEET["Toute la flotte<br/>agents zabbix_agent / agent2"] -- "Server= / ServerActive=<br/>10.100.2.226,10.100.2.227 (CSV)" --> S1
FLEET --> S2
Incident du 2026-07-16 — bascule HA spontanée¶
sequenceDiagram
participant S1 as INDSUPV001 (actif)
participant S2 as INDSUPV002 (standby)
participant DB as ha_node (PostgreSQL)
participant A as Agents (toute la flotte)
Note over S1,S2: Fonctionnement normal
S1->>DB: heartbeat (status=actif)
S2->>DB: heartbeat (status=standby)
A->>S1: checks actifs/passifs (Server= ne listait que .226)
Note over S1: Bascule spontanée (cause non identifiée)
S2->>DB: devient status=actif
Note over S2: INDSUPV002 devient le nœud actif
S2->>A: tentative de connexion depuis .227
Note over A: .227 absent de Server= -> agent ferme la connexion<br/>« Assuming that agent dropped connection because of access permissions »
Note over A,S2: Toute la flotte remonte en rouge (message trompeur, pas un vrai problème de permissions)
Note over A: Fix : zabbix_server_ip = "10.100.2.226,10.100.2.227" (CSV)<br/>appliqué en urgence par SSH direct, puis versionné dans /root/baseline
Services et SLA (parent/enfant)¶
flowchart TD
SLA["SLA « Core Infra 99.9% »<br/>mensuel, tag sla:core"]
SLA --> HAP["HAProxy (repo.infra.infra)<br/>algorithm=2"]
SLA --> PG["PostgreSQL HA (cluster INDDATA)<br/>algorithm=2"]
SLA --> VLT["Vault / PKI (INDSERV016-018)<br/>algorithm=2"]
SLA --> AD["Active Directory (infra.indio)<br/>feuille — INDIDEN008 seul"]
SLA --> VMW["vCenter / Infra VMware<br/>feuille — host vcenter"]
HAP --> HAP1["INDSERV002"]
HAP --> HAP2["INDSERV003"]
HAP --> HAP3["INDSERV004"]
PG --> PG1["INDDATA001"]
PG --> PG2["INDDATA002"]
VLT --> V1["INDSERV016"]
VLT --> V2["INDSERV017"]
VLT --> V3["INDSERV018"]
algorithm=2 signifie « problème seulement si TOUS les enfants sont en problème » — tolère la perte d'un nœud (ou deux sur trois pour HAProxy) sans faire chuter le SLA du service parent, tant qu'au moins un nœud reste up.
Provisioning Terraform¶
/root/zabbix/main.tfclone 3 VM (for_eachsurnodes) depuistmpl-rocky96-hardened, IP assignées par NetBox.lifecycle.ignore_changessurannotation,clone[0].template_uuid,clone[0].customize,disk[0].io_share_count(pattern standard).- Variables (
variables.tf) :vsphere_network = "seg-supervision",vm_gateway = "10.100.2.238",vm_netmask = 28,nodes = [INDSUPV001, INDSUPV002, INDSUPV004],vm_cpu = 2,vm_ram = 4096. - 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 dansterraform.tfvars.example.
Configuration Ansible¶
Playbook /root/zabbix/ansible/site.yml, 2 plays :
zabbix_server(groupezabbix_server= INDSUPV001/002) : installezabbix-server-pgsql+zabbix-java-gateway, déploie le certificat CA vCenter (vcenter-ca.crt, VMCA auto-signé) dans le trust store système (update-ca-trust extracten handler), déploiezabbix_server.confdepuis le template, active le booléen SELinuxzabbix_can_network, ouvre les ports firewalld10050/tcp(agent) et10051/tcp(server/HA), démarrezabbix-serveretzabbix-java-gateway.zabbix_tls(groupezabbix_web= INDSUPV004) : installemod_ssl, crée/etc/httpd/certs(root:apache,0750), déploie certificat/clé/CA Vault PKI, un vhost SSL dédié (ssl.conf.j2, remplace lessl.confpar défaut du RPMmod_ssl— les alias/zabbixlivrés parzabbix-apache-confsont définis hors de toutVirtualHostet donc hérités automatiquement), une redirection HTTP→HTTPS (zabbix-redirect.conf.j2,RewriteRuleplutôt qu'un simpleRedirectpour être évaluée avant la fusion des alias hérités), ouvre le port 443, valide la configuration (apachectl configtest) puis vérifie via des requêtes HTTP/HTTPS réelles (uri, retries) que le frontend répond correctement après redémarrage.
zabbix_server.conf.j2 — champs notables au-delà de DBHost/DBName/DBUser/DBPassword (base zabbix sur la VIP) : StartVMwareCollectors=2 (active le polling natif vCenter), JavaGateway=127.0.0.1/JavaGatewayPort=10052/StartJavaPollers=5 (JMX local), HANodeName/NodeAddress (propres à chaque nœud, injectés depuis ha_node_name/node_address de l'inventaire — c'est ce qui active le mode HA natif au lieu du mode standalone implicite), StatsAllowedIP=127.0.0.1, EnableGlobalScripts=0.
group_vars/zabbix_server.yml : zabbix_db_host = VIP PostgreSQL (10.100.2.195), zabbix_db_password chiffré Ansible Vault. group_vars/zabbix_web.yml : uniquement zabbix_fqdn = "supervision.infra.indio" (aucun secret — la connexion DB du frontend lui-même n'est pas gérée par ce rôle, voir Procédure manuelle).
Procédure manuelle¶
Le cluster Zabbix concentre la plus grande part de configuration non versionnée de toute l'infra — à documenter précisément puisqu'un rebuild Terraform+Ansible seul ne suffit pas à retrouver un système fonctionnel.
1. Frontend applicatif (INDSUPV004) — installation manuelle, hors playbook :
- Installer
zabbix-web,httpd,php-fpmet les paquets associés (paquets stock Zabbix, pas gérés par ce dépôt). - Configurer
zabbix.conf.phpavec les identifiants de connexion à la basezabbixsur la VIP PostgreSQL10.100.2.195— geste manuel, indépendant du rôlezabbix_tlsqui ne pose que la couche TLS/httpd par-dessus. - Seulement ensuite, appliquer
ansible-playbook site.yml(playzabbix_tls) pour le certificat et la redirection.
2. Bootstrap du cluster HA natif (si l'on repart d'un nœud déjà en service standalone) : Zabbix refuse de démarrer un 2ᵉ nœud HA tant qu'un nœud existant tourne encore en standalone implicite (HANodeName vide) — erreur cannot change mode to HA while standalone node is active. Il faut déployer HANodeName/NodeAddress aux deux nœuds simultanément (jamais un seul à la fois) puis redémarrer les deux. Le tout premier redémarrage du 2ᵉ nœud échoue quasi systématiquement (le 1ᵉʳ nœud n'a pas fini sa propre transition) ; le Restart=on-failure de l'unit systemd livrée retente automatiquement en quelques secondes — ne pas s'inquiéter d'un premier échec si le service est active (running) juste après.
3. Objets Zabbix (templates, hosts, macros) — 100% via l'API https://supervision.infra.indio/zabbix/api_jsonrpc.php, aucune trace en IaC :
- VMware : host
vcenterrattaché au template racineVMware(templateid 10173 — pasVMware FQDN, qui n'a ni interface ni macros et ne remonte rien), macros{$VMWARE.URL}=https://vcenter.infra.indio/sdk,{$VMWARE.USERNAME},{$VMWARE.PASSWORD}(macro secrète, type 1). Le certificat VMCA auto-signé doit être en confiance côté serveur (déjà couvert par le rôle Ansible) — sans cela le collector échoue en boucle (unable to get local issuer certificate). - FortiGate : host
fortigate-edge, interface SNMPv2 vers10.15.91.14:161(IP SNAT FortiGate), templateFortiGate by SNMP(10604). Nécessite au préalable l'activation de SNMP côté FortiOS lui-même (config system snmp sysinfo, communauté dédiée,allowaccess snmpsur l'interface uplink) — configuration CLI FortiOS, hors de tout Terraform/Ansible de cette infra (pas de provider Fortios dans ce projet), identifiants gérés hors dépôt. - PostgreSQL : le template stock « PostgreSQL by Zabbix agent » (10357) définit ses items avec la signature
[Host, Port, User, Password, Database](5 paramètres), incompatible avec le plugin réellement installé (zabbix-agent2-plugin-postgresql 8.0.0-beta2), qui attend[ConnString, User, Password](3 paramètres,ConnString=host:port). Correctif appliqué viaitem.updatesur les 16 itemstype=0du template. Piège additionnel : la macro{$PG.HOST}par défaut vautlocalhost, qui déclenche une connexion socket Unix côté plugin Go (échoue faute de mappingpeer) — toujours la surcharger explicitement à127.0.0.1(force une vraie connexion TCP). Rôle Ansible dédié dans/root/baseline(zabbix_agent2_postgresql) bascule INDDATA001/002 de l'agent classique verszabbix-agent2(seul agent avec le plugin PostgreSQL). - HAProxy : même famille de piège que PostgreSQL — la macro par défaut du template stock (
{$HAPROXY.STATS.HOST}=localhost) pointe vers le mauvais hôte côté items HTTP exécutés par le serveur ; à surcharger avec l'hôte réel. - Avant de faire confiance à un template stock non modifié (PostgreSQL/HAProxy), toujours tester une clé en live (
zabbix_get) plutôt que de supposer la signature/macro par défaut correcte — particulièrement sur une version beta de Zabbix (8.0.0-beta2 à ce jour).
4. Services, SLA et alerting — également 100% API, créés le 2026-07-17 :
- Schéma de tags : chaque trigger de disponibilité pertinent reçoit un tag
service:<slug>(fusionné, jamais en écrasement du tableautagsexistant). - 5 Services au sommet (voir schéma ci-dessus), tous tagués
sla:core: HAProxy/PostgreSQL/Vault en parentsalgorithm=2, AD et vCenter en feuilles simples. - SLA « Core Infra 99.9% » (
slo=99.9, période mensuelle,service_tagsscopé sursla:coreuniquement — pas les enfants, pour éviter le bruit dans le rapport). - Alerting : media type Email rebranché sur le relais Postfix interne (
INDSERV009:25, sans authentification), utilisateurAdminavec media email actif 24/7 toutes sévérités, action de type Service (eventsource=4) filtrée sur le tagsla=core. Le courriel reste livré localement surINDSERV009(/var/mail/root) — pas de relais vers une boîte aux lettres externe réelle à ce jour. - Gap connu, non couvert : FreeIPA (aucun host Zabbix) et
INDIDEN009(2ᵉ DC AD, absent comme host Zabbix) ne font partie d'aucun Service/SLA.
5. Correctif d'urgence fleet-wide (2026-07-16) : au moment de l'incident, le correctif zabbix_server_ip a d'abord été appliqué en SSH direct (sed + restart de l'agent) sur toute la flotte en parallèle, pour ne pas attendre un plein re-run Ansible du dépôt /root/baseline (qui réapplique aussi d'autres rôles plus lourds) — puis committé proprement dans group_vars/fleet.yml pour que tout futur provisioning fleet-wide parte déjà de la bonne valeur.
Procédure de déploiement¶
terraform apply(dans/root/zabbix) : clone les 3 VM et attribue leurs IP via NetBox.- Installer et configurer manuellement le frontend (
zabbix-web/httpd/php-fpm +zabbix.conf.php) surINDSUPV004— préalable non automatisé (voir Procédure manuelle). ansible-playbook site.yml(dans/root/zabbix/ansible) : configure le cluster Zabbix Server HA sur INDSUPV001/002 (déployerHANodeName/NodeAddressaux deux nœuds dans la même exécution, jamais un seul à la fois), puis le TLS du frontend sur INDSUPV004.- Recréer manuellement, via l'API Zabbix, tous les objets applicatifs : templates VMware/FortiGate/PostgreSQL/HAProxy/Vault/Elasticsearch, hosts, macros, groupes d'utilisateurs, comptes de service, Services/SLA, alerting (voir Procédure manuelle).
- S'assurer que
/root/baseline(group_vars/fleet.yml,zabbix_server_ip) liste bien les deux nœuds en CSV avant de déployer/rejouer les agents fleet-wide.
Contrôle de santé / Vérification¶
SELECT name, status, lastaccess FROM ha_node;(psql, via la VIP PostgreSQL) : identifie le nœud réellement actif (status=3) et le standby (status=0) — seule source de vérité fiable.getsebool zabbix_can_networksur les 2 nœuds serveur : doit renvoyeron(persistant).- Sur un agent quelconque :
grep -E 'Server=|ServerActive=' /etc/zabbix/zabbix_agentd.confdoit lister les deux IP10.100.2.226,10.100.2.227. curl -sk https://supervision.infra.indio/zabbix/→ 200 (ou 302 vers la page de login) ;curl -sI http://<IP INDSUPV004>/→ 301 vers HTTPS.- Vérification applicative :
host.get/item.getvia l'API pour confirmer que les items VMware/FortiGate/PostgreSQL remontent des valeurs récentes (lastclockproche de l'heure courante) plutôt qu'une erreur (ZBX_NOTSUPPORTED,No Such Instance,read timeout). sla.getsli(API) : vérifie que le SLA « Core Infra 99.9% » calcule effectivement un SLI (pas d'erreur, historique cohérent).
Points d'attention¶
HA natif sans VIP : Server=/ServerActive= doit toujours lister les DEUX nœuds
Contrairement à PostgreSQL (VIP keepalived unique), le cluster Zabbix Server HA natif n'expose pas d'IP flottante. Les hôtes supervisés (variable zabbix_server_ip du dépôt /root/baseline) doivent donc lister les deux IP (10.100.2.226,10.100.2.227) dans Server=/ServerActive=, sous peine de refuser les checks passifs si le nœud actif bascule. Une bascule HA spontanée du 2026-07-16 (cause non identifiée) a fait passer toute la flotte en rouge le temps du correctif — symptôme caractéristique : message trompeur « Assuming that agent dropped connection because of access permissions » (ce n'est pas un problème de permissions, l'IP source du nouveau nœud actif n'est simplement pas dans Server=). Réflexe de diagnostic : si « tout est rouge en même temps », vérifier ha_node avant toute autre piste (agent, DFW, firewalld).
- SELinux
zabbix_can_network: le processzabbix_servertourne dans le domaine confinézabbix_t, avec ce booléen off par défaut — quand il est off,zabbix_serverne peut faire aucune connexion réseau sortante, y compris vers son propre Java Gateway local (127.0.0.1:10052), bloquant silencieusement VMware ET JMX. Piège de diagnostic : invisible dansausearch -m avc(même avecdontaudit off) et dans les logs du Java Gateway lui-même (le blocage a lieu avant que la requête n'atteigne le gateway) — le symptôme est unPermission deniedgénérique et immédiat, à ne pas confondre avec un problème de port applicatif.setsebool -P zabbix_can_network onsur les 2 nœuds HA résout le blocage. - JMX Kafka non résolu : une fois
zabbix_can_networkactivé, l'erreur passe dePermission deniedàread timeout— la connectivité progresse mais bute sur le second port RMI aléatoire qu'utilise JMX après la connexion initiale (non ouvert côté DFW SOC). Décision explicite : non traité, jugé non critique — ne pas relancer cette investigation sans demande explicite. Le reste du monitoring Kafka (agent classique, port 10050) fonctionne normalement ; seul le template « Apache Kafka by JMX » reste incomplet. Point également à faire figurer dans le registre de dette technique global (voir Points de vigilance). - Aucun objet Zabbix versionné en IaC : templates, hosts, macros, groupes, comptes de service, Services/SLA, alerting — tout vit uniquement dans la base
zabbix, créé/modifié via API ou UI. Un rebuild complet depuis Terraform+Ansible ne restaure que la plateforme serveur nue ; toute la configuration applicative (voir Procédure manuelle) doit être reconstruite à la main ou via un script externe qui n'existe pas encore. - Frontend hors IaC : le rôle
zabbix_tlssuppose quezabbix-web/httpd/php-fpm sont déjà installés, fonctionnels et connectés à la base (zabbix.conf.phpconfiguré manuellement) — ce dépôt ne les installe pas. Un redéploiement complet de cette VM nécessite de réinstaller et reconnecter le frontend manuellement avant de rejouerzabbix_tls. - Groupe de sécurité NSX déjà prêt pour la HA native :
grp-zabbix-server(/root/nsx/groups.tf) référence déjà les deux IP (10.100.2.226et.227) — un failover HA ne nécessite donc aucun changement DFW. Le commentaire du code Terraform à cet endroit (« INDSUPV002 est passif/standby, pas de VIP HA à ce jour ») est resté rédigé pour l'ancien état (avant l'activation du HA natif) alors que la définition du groupe elle-même était déjà correcte — écart purement documentaire dans le code, sans impact fonctionnel. - Compte
Adminpar défaut toujours actif : le mot de passe d'usine du compteAdminn'a jamais été changé sur cette installation Zabbix — même famille de constat que le compte par défaut de Guacamole, à corriger. Également à faire figurer dans le registre de dette technique global (voir Points de vigilance). - Secrets : mot de passe de connexion base de données (
zabbix_db_password) chiffré via Ansible Vault (group_vars/zabbix_server.yml) ; certificat/clé TLS du frontend déployés depuisroles/zabbix_tls/files/tls/; macros secrètes Zabbix ({$VMWARE.PASSWORD}etc.) et token APIsvc-grafanagérés uniquement côté base/API, jamais en IaC — voir gestion des secrets.