Chapitre 05

API et configuration en code

Tout ce que fait l'interface, l'API le fait aussi. Au-delà de quelques dizaines d'hôtes, c'est elle qui garde la plateforme cohérente, pas la souris.

Niveau avancé  ·  Lecture 11 min  ·  Base Zabbix 8.0 LTS

01Les jetons d'API

L'API JSON-RPC de Zabbix s'authentifie par jeton, créé dans le profil d'un utilisateur. Nous créons un utilisateur technique par usage, avec un rôle limité à ce dont il a besoin, et un jeton à date d'expiration.

UsageRôleDroits sur les hôtes
Lecture pour Grafana et les rapportsUtilisateur, API en lectureLecture sur les groupes concernés
Scripts d'exploitation (maintenances)Administrateur restreint aux méthodes utilesÉcriture sur les groupes concernés
Provisionnement AnsibleAdministrateurÉcriture sur tout le périmètre géré
Restreindre les méthodes

Un rôle Zabbix peut limiter l'API à une liste de méthodes. Un jeton qui ne sert qu'à poser des maintenances n'a besoin que de host.get, maintenance.get, maintenance.create et maintenance.delete. S'il fuit, c'est tout ce qu'il permet.

02Premiers appels

Les problèmes graves en cours, depuis un terminal
export ZBX_URL=https://zabbix.exemple.fr/api_jsonrpc.php
export ZBX_TOKEN='jeton-de-lecture'

curl -s -X POST "$ZBX_URL" \
  -H 'Content-Type: application/json-rpc' \
  -H "Authorization: Bearer $ZBX_TOKEN" \
  -d '{"jsonrpc":"2.0","method":"problem.get","id":1,
       "params":{"severities":[4,5],"recent":true,
                 "output":["eventid","name","severity","clock"],
                 "sortfield":["eventid"],"sortorder":"DESC"}}' | jq

Le jeton passe dans l'en-tête Authorization, jamais dans le corps de la requête ni dans l'URL : il finirait dans les journaux du proxy inverse.

03La bibliothèque Python officielle

Zabbix publie zabbix_utils, une bibliothèque Python qui couvre l'API, l'envoi de valeurs et l'interrogation des agents. Elle remplace avantageusement les bibliothèques communautaires plus anciennes.

Installation dans un environnement dédié
python3 -m venv /opt/zbx-outils
/opt/zbx-outils/bin/pip install zabbix_utils

04Une maintenance posée par le script

Le cas d'usage qui justifie à lui seul l'automatisation. Avant de redémarrer un noeud, le script d'intervention pose une maintenance sur l'hôte, fait son travail, puis la retire. Plus de fausse alerte pendant l'opération, et plus d'oubli de maintenance laissée ouverte.

Fichier zbx_maintenance.py
#!/opt/zbx-outils/bin/python3
"""Pose ou retire une maintenance Zabbix sur un hote.

  zbx_maintenance.py pose  pve-02 60   # 60 minutes
  zbx_maintenance.py retire pve-02
"""
import os
import sys
import time
from zabbix_utils import ZabbixAPI

PREFIXE = "intervention-"

def api():
    a = ZabbixAPI(url=os.environ["ZBX_URL"])
    a.login(token=os.environ["ZBX_TOKEN"])
    return a

def hote(a, nom):
    r = a.host.get(filter={"host": [nom]}, output=["hostid"])
    if not r:
        sys.exit("hote inconnu dans Zabbix : %s" % nom)
    return r[0]["hostid"]

def poser(nom, minutes):
    a = api()
    debut = int(time.time())
    a.maintenance.create(
        name=PREFIXE + nom,
        active_since=debut,
        active_till=debut + minutes * 60,
        maintenance_type=0,          # collecte maintenue
        hosts=[{"hostid": hote(a, nom)}],
        timeperiods=[{"timeperiod_type": 0, "start_date": debut,
                      "period": minutes * 60}])
    print("maintenance posee sur %s pour %d minutes" % (nom, minutes))

def retirer(nom):
    a = api()
    ids = [m["maintenanceid"] for m in
           a.maintenance.get(filter={"name": PREFIXE + nom}, output=["maintenanceid"])]
    if ids:
        a.maintenance.delete(*ids)
    print("maintenance retiree sur %s (%d)" % (nom, len(ids)))

if __name__ == "__main__":
    action, nom = sys.argv[1], sys.argv[2]
    if action == "pose":
        poser(nom, int(sys.argv[3]) if len(sys.argv) > 3 else 60)
    else:
        retirer(nom)
Une durée toujours bornée

Le script pose une maintenance qui expire seule, même si l'intervention s'interrompt avant l'étape de retrait. Une maintenance sans fin est la meilleure façon de découvrir, trois mois plus tard, qu'un hôte ne prévenait plus de rien.

05Pousser des valeurs depuis un script

Un élément de type trappeur attend qu'on lui envoie des valeurs. C'est l'équivalent, dans Zabbix, de la sonde push d'Uptime Kuma : le script de sauvegarde envoie son résultat, un déclencheur nodata() alerte si rien n'arrive.

Fin d'un script de sauvegarde
#!/opt/zbx-outils/bin/python3
from zabbix_utils import Sender

# envoye au proxy du site, ou a la grappe de serveurs
envoi = Sender(server="10.10.0.61", port=10051)
r = envoi.send_value("srv-fichiers", "metier.sauvegarde.resultat", 0)
print(r)

06Ansible pour les agents et les hôtes

La collection community.zabbix fournit un rôle de déploiement de l'agent et des modules pour gérer hôtes, groupes, macros et maintenances. Combinée à l'autoregistration du chapitre 02, elle couvre tout le cycle de vie : le rôle installe l'agent avec ses métadonnées, l'hôte s'inscrit seul, et le module le supprime au décommissionnement.

Extrait de playbook
- hosts: serveurs_linux
  become: true
  roles:
    - role: community.zabbix.zabbix_agent
      vars:
        zabbix_agent2: true
        zabbix_agent_version: "8.0"
        zabbix_agent_server: ""
        zabbix_agent_serveractive: "10.10.0.61;10.10.0.62"
        zabbix_agent_hostmetadata: "linux {{ role_metier }} client-{{ client }}"
        zabbix_agent_tlsconnect: psk
        zabbix_agent_tlsaccept: psk
        zabbix_agent_tlspskidentity: "PSK-{{ client | upper }}"
        zabbix_agent_tlspsk_secret: "{{ vault_psk[client] }}"
Vérifiez les noms de variables de la collection

La collection évolue avec Zabbix et certains noms de variables ont changé d'une version à l'autre. Avant de déployer pour la 8.0, relisez la documentation du rôle dans la version de la collection que vous installez.

07Les modèles dans Git

Les modèles maison et les modèles de politique du chapitre 03 sont du code : ils se versionnent. L'API exporte un modèle au format YAML, lisible en différentiel. Nous exportons chaque nuit, et une modification faite dans l'interface apparaît le lendemain comme un changement dans Git, avec son auteur.

Exporter les modèles maison
#!/opt/zbx-outils/bin/python3
import os
from zabbix_utils import ZabbixAPI

a = ZabbixAPI(url=os.environ["ZBX_URL"])
a.login(token=os.environ["ZBX_TOKEN"])

# les modeles maison sont ranges dans un groupe dedie
groupe = a.templategroup.get(filter={"name": ["Modeles/Captain"]}, output=["groupid"])
modeles = a.template.get(groupids=[groupe[0]["groupid"]], output=["templateid", "host"])

os.makedirs("modeles", exist_ok=True)
for m in modeles:
    yaml = a.configuration.export(format="yaml",
                                  options={"templates": [m["templateid"]]})
    with open("modeles/%s.yaml" % m["host"].replace("/", "_"), "w") as f:
        f.write(yaml)

Le chemin inverse, l'import depuis Git vers Zabbix, se fait par configuration.import. Nous le réservons aux modèles maison : les officiels s'importent depuis les fichiers publiés par l'éditeur pour la version installée.

Une supervision qui veille pendant que vous dormez

Nous concevons, installons et exploitons des plateformes de supervision pour des PME, des hébergeurs et des collectivités : Zabbix, Prometheus et Grafana, ou plus léger quand le besoin l'est. Reprise d'une supervision existante, réglage des alertes qui sonnent pour rien, astreinte : nous intervenons au forfait comme au long cours.