Créer un plugin (tutoriel)

Voici un tutoriel étape par étape expliquant comment créer un plugin chargeable simple pour Zabbix agent 2.

Vous pouvez également utiliser le dépôt d’exemple comme modèle ou guide pour créer vos propres plugins.

Ce que vous allez créer

Ce tutoriel montre comment créer un nouveau plugin chargeable MyIP. Le plugin implémentera une seule clé d’élément, myip, qui renvoie l’adresse IP externe de l’hôte sur lequel Zabbix agent 2 est en cours d’exécution.

Étape 1 : Configuration

  1. Un plugin est un module Go standard. Commencez par initialiser le fichier go.mod dans le répertoire du plugin afin de suivre les dépendances du plugin :
cd path/to/plugins/myip # Basculez vers le répertoire de votre plugin
go mod init myip
  1. Installez la dépendance obligatoire Zabbix Go SDK (golang.zabbix.com/sdk) :
go get golang.zabbix.com/sdk@$LATEST_COMMIT_HASH

Remplacez $LATEST_COMMIT_HASH par le hachage du commit HEAD le plus récent du dépôt golang.zabbix.com/sdk dans la branche de version appropriée. Par exemple :

go get golang.zabbix.com/sdk@af85407

Notez que le versionnement de golang.zabbix.com/sdk n'est actuellement pas pris en charge, mais cela pourrait changer à l'avenir.

Des dépendances supplémentaires peuvent être installées selon les besoins à l'aide de go get.

  1. Créez un fichier main.go vide pour le code source du plugin :
touch main.go

La configuration initiale est maintenant terminée, et le plugin est prêt pour le développement.

Étape 2 : Structure du plugin

Le module golang.zabbix.com/sdk, installé à l’étape précédente, fournit le framework nécessaire au développement de plugins et garantit que tous les plugins ont une structure cohérente.

  1. Définir le flux d’exécution de base.

Commencez par définir le flux d’exécution principal du plugin. Ajoutez le code suivant à main.go :

package main

func main() {
    err := run()
    if err != nil {
        panic(err)
    }
}

func run() error {
    return nil
}

Cela établit le flux d’exécution de base du plugin. La fonction run contiendra plus tard la logique principale du plugin.

  1. Explorer les interfaces du plugin.

Un plugin Zabbix agent 2 doit être représenté par une structure qui implémente des interfaces du package golang.zabbix.com/sdk/plugin :

  • Accessor - définit les méthodes essentielles que tous les plugins doivent implémenter, comme la définition du nom du plugin et la gestion des délais d’expiration des clés d’élément.
  • Une ou plusieurs des interfaces fonctionnelles de plugin suivantes :
    • Exporter - effectue une interrogation et renvoie une valeur (ou plusieurs valeurs), rien, ou une erreur ; souvent utilisée avec l’interface Collector.
    • Collector - gère la collecte périodique des données.
    • Runner - définit les procédures de démarrage et d’arrêt du plugin.
    • Watcher - permet d’implémenter une interrogation indépendante des métriques, en contournant le planificateur interne de l’agent ; utile pour la supervision basée sur les traps ou pilotée par des événements.
    • Configurator - définit la manière dont le plugin lit et applique ses paramètres de configuration.

Vous pouvez soit implémenter vous-même ces interfaces, soit utiliser celles par défaut fournies par le SDK Go de Zabbix, en les modifiant selon vos besoins. Ce tutoriel utilise les implémentations par défaut.

  1. Créer la structure du plugin.

Importez maintenant le package plugin et créez une structure myIP qui incorpore la structure plugin.Base :

import "golang.zabbix.com/sdk/plugin"

type myIP struct {
    plugin.Base
}

La structure myIP satisfait actuellement l’interface Accessor. Une méthode permettant d’implémenter l’une des interfaces fonctionnelles du plugin, Exporter, sera ajoutée plus loin dans ce tutoriel.

Étape 3 : Définir les clés d’élément

Votre plugin a besoin des clés d’élément pour collecter les données et les fournir au serveur ou au proxy Zabbix.

  1. Importez le package errs pour la gestion des erreurs :
import "golang.zabbix.com/sdk/errs"
  1. Enregistrez les clés d’élément à l’aide de la fonction plugin.RegisterMetrics() dans la fonction run() :
func run() error {
    p := &myIP{}

    // Register the `myip` item key.
    err := plugin.RegisterMetrics(
        p,
        "MyIP",                           // Plugin name
        "myip",                           // Item key name
        "Returns the host's IP address.", // Item key description
    )
    if err != nil {
        return errs.Wrap(err, "failed to register metrics")
    }

    return nil
}

Pour enregistrer plusieurs clés d’élément, répétez les paramètres nom de métrique et description pour chaque métrique.
Par exemple :

plugin.RegisterMetrics(&impl, "Myip", "metric.one", "Metric one description.", "metric.two", "Metric two description.")

Étape 4 : Configurer le gestionnaire

Le gestionnaire facilite la communication entre l’agent et le plugin.

  1. Importez le package container :
import "golang.zabbix.com/sdk/plugin/container"
  1. Dans la fonction run(), ajoutez le code permettant de créer et de configurer un gestionnaire :
func run() error {
    p := &myIP{}

    // Register the `myip` item key.
    err := plugin.RegisterMetrics(
        p,
        "MyIP",                           // Plugin name
        "myip",                           // Item key name
        "Returns the host's IP address.", // Item key description
    )
    if err != nil {
        return errs.Wrap(err, "failed to register metrics")
    }

    // Create a new handler.
    h, err := container.NewHandler("MyIP") // Plugin name
    if err != nil {
        return errs.Wrap(err, "failed to create new handler")
    }

    // Setup logging to forward logs from the plugin to the agent.
    // Available via p.Logger.Infof, p.Logger.Debugf, etc.
    p.Logger = h

    // Start plugin execution.
    // Blocks until a termination request is received from the agent.
    err = h.Execute()
    if err != nil {
        return errs.Wrap(err, "failed to execute plugin handler")
    }

    return nil
}

Étape 5 : Implémenter la collecte de données

La collecte de données s’effectue via l’interface Exporter, qui décrit la méthode Export :

func Export(
  key string,             // La clé d’élément à collecter.
  params []string,        // Arguments transmis à la clé d’élément (`myip[arg1, arg2]`).
  context ContextProvider // Métadonnées sur la collecte de données de la clé d’élément.
) (any, error)
  1. Importez les paquets requis pour les requêtes HTTP et la lecture des réponses :
import (
    "io"
    "net/http"
)
  1. Implémentez la méthode Export pour la structure myIP :
func (p *myIP) Export(
    key string, params []string, context plugin.ContextProvider,
) (any, error) {
    // Le plugin peut utiliser une logique de collecte de données différente selon le paramètre `key`.
    // Cette implémentation vérifie uniquement que la valeur `key` fournie est prise en charge.
    if key != "myip" {
        return nil, errs.Errorf("unknown item key %q", key)
    }

    // Le journal sera transmis au journal de l’agent 2.
    p.Infof(
        "received request to handle %q key with %d parameters",
        key,
        len(params),
    )

    // Collectez les données et renvoyez-les.

    resp, err := http.Get("https://api.ipify.org")
    if err != nil {
        return nil, errs.Wrap(err, "failed to get IP address")
    }

    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, errs.Wrap(err, "failed to read response body")
    }

    return string(body), nil
}

Étape 6 : Compiler et configurer le plugin

  1. Pour compiler le plugin, exécutez :
go mod tidy
go build

Cela devrait créer un exécutable myip dans le répertoire courant.

  1. Configurez Zabbix agent 2 pour utiliser le plugin :
echo "Plugins.MyIP.System.Path=$PATH_TO_THE_MYIP_PLUGIN_EXECUTABLE" > /etc/zabbix_agent2.d/plugins.d/myip.conf

Remplacez $PATH_TO_THE_MYIP_PLUGIN_EXECUTABLE par le chemin vers myip créé à l’étape 5.

Le nom du plugin dans le nom du paramètre de configuration (MyIP dans ce tutoriel) doit correspondre au nom du plugin défini dans la fonction plugin.RegisterMetrics().

  1. Pour tester le plugin et son élément myip, exécutez :
zabbix_agent2 -c /etc/zabbix_agent2.conf -t myip

La sortie doit contenir une adresse IP externe de votre hôte et ressembler à ceci :

myip                                          [s|192.0.2.0]

Vous avez ainsi créé un plugin chargeable simple pour Zabbix agent 2. Félicitations !

Code source complet

package main

import (
    "io"
    "net/http"

    "golang.zabbix.com/sdk/errs"
    "golang.zabbix.com/sdk/plugin"
    "golang.zabbix.com/sdk/plugin/container"
)

var _ plugin.Exporter = (*myIP)(nil)

type myIP struct {
    plugin.Base
}

func main() {
    err := run()
    if err != nil {
        panic(err)
    }
}

func run() error {
    p := &myIP{}

    // Enregistrer la clé d'élément `myip`.
    err := plugin.RegisterMetrics(
        p,
        "MyIP",                              // Nom du plugin
        "myip",                              // Nom de la clé d'élément
        "Renvoie l'adresse IP de l'hôte.",   // Description de la clé d'élément
    )
    if err != nil {
        return errs.Wrap(err, "failed to register metrics")
    }

    // Créer un nouveau gestionnaire.
    h, err := container.NewHandler("MyIP") // Nom du plugin
    if err != nil {
        return errs.Wrap(err, "failed to create new handler")
    }

    // Configurer la journalisation pour transférer les journaux du plugin vers l'agent.
    // Disponible via p.Logger.Infof, p.Logger.Debugf, etc.
    p.Logger = h

    // Démarrer l'exécution du plugin.
    // Bloque jusqu'à la réception d'une demande d'arrêt de l'agent.
    err = h.Execute()
    if err != nil {
        return errs.Wrap(err, "failed to execute plugin handler")
    }

    return nil
}

func (p *myIP) Export(
    key string, params []string, context plugin.ContextProvider,
) (any, error) {
    // Le plugin peut utiliser une logique de collecte de données différente selon le paramètre `key`.  
    // Cette implémentation vérifie uniquement que la valeur `key` fournie est prise en charge. 
    if key != "myip" {
        return nil, errs.Errorf("unknown item key %q", key)
    }

    // Le journal sera transféré vers le journal 2 de l'agent.
    p.Infof(
        "received request to handle %q key with %d parameters",
        key,
        len(params),
    )

    // Collecter les données et les renvoyer.

    resp, err := http.Get("https://api.ipify.org")
    if err != nil {
        return nil, errs.Wrap(err, "failed to get IP address")
    }

    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, errs.Wrap(err, "failed to read response body")
    }

    return string(body), nil
}