Ein Plugin erstellen (Tutorial)

Dies ist ein Schritt-für-Schritt-Tutorial, das zeigt, wie man ein einfaches ladbares Plugin für Zabbix Agent 2 erstellt.

Sie können auch das Beispiel-Repository als Vorlage oder Leitfaden verwenden, um Ihre eigenen Plugins zu erstellen.

Was Sie erstellen werden

Dieses Tutorial zeigt, wie Sie ein neues ladbares Plugin MyIP erstellen. Das Plugin implementiert einen einzelnen Datenpunkt-Schlüssel, myip, der die externe IP-Adresse des Hosts zurückgibt, auf dem der Zabbix Agent 2 ausgeführt wird.

Schritt 1: Einrichtung

  1. Ein Plugin ist ein Standard-Go-Modul. Beginnen Sie damit, die Datei go.mod im Plugin-Verzeichnis zu initialisieren, um die Abhängigkeiten des Plugins zu verfolgen:
cd path/to/plugins/myip # Wechseln Sie in Ihr Plugin-Verzeichnis
go mod init myip
  1. Installieren Sie die erforderliche Abhängigkeit Zabbix Go SDK (golang.zabbix.com/sdk):
go get golang.zabbix.com/sdk@$LATEST_COMMIT_HASH

Ersetzen Sie $LATEST_COMMIT_HASH durch den neuesten HEAD-Commit-Hash aus dem Repository von golang.zabbix.com/sdk im entsprechenden Release-Branch. Zum Beispiel:

go get golang.zabbix.com/sdk@af85407

Beachten Sie, dass die Versionierung von golang.zabbix.com/sdk derzeit nicht unterstützt wird, sich dies jedoch in Zukunft ändern kann.

Zusätzliche Abhängigkeiten können bei Bedarf mit go get installiert werden.

  1. Erstellen Sie eine leere Datei main.go für den Quellcode des Plugins:
touch main.go

Damit ist die erste Einrichtung abgeschlossen, und das Plugin ist bereit für die Entwicklung.

Schritt 2: Plugin-Struktur

Das im vorherigen Schritt installierte Modul golang.zabbix.com/sdk stellt das notwendige Framework für die Plugin-Entwicklung bereit und sorgt dafür, dass alle Plugins eine einheitliche Struktur haben.

  1. Grundlegenden Ausführungsablauf einrichten.

Beginnen Sie damit, den Hauptausführungsablauf des Plugins zu definieren. Fügen Sie den folgenden Code zu main.go hinzu:

package main

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

func run() error {
    return nil
}

Damit wird der grundlegende Ausführungsablauf für das Plugin festgelegt. Die Funktion run wird später die Kernlogik des Plugins enthalten.

  1. Die Plugin-Interfaces kennenlernen.

Ein Zabbix Agent 2-Plugin soll durch eine Struktur dargestellt werden, die Interfaces aus dem Paket golang.zabbix.com/sdk/plugin implementiert:

  • Accessor - definiert grundlegende Methoden, die alle Plugins implementieren müssen, z. B. das Setzen des Plugin-Namens und die Behandlung von Timeouts für Datenpunkt-Schlüssel.
  • Eines oder mehrere der folgenden funktionalen Plugin-Interfaces:
    • Exporter - führt eine Abfrage aus und gibt einen Wert (oder mehrere Werte), nichts oder einen Fehler zurück; wird häufig zusammen mit dem Collector-Interface verwendet.
    • Collector - verwaltet die periodische Erfassung von Daten.
    • Runner - definiert Start- und Beendigungsprozeduren des Plugins.
    • Watcher - ermöglicht die Implementierung einer unabhängigen Metrikabfrage unter Umgehung des internen Schedulers des Agenten; nützlich für trap-basierte oder ereignisgesteuerte Überwachung.
    • Configurator - definiert, wie das Plugin seine Konfigurationseinstellungen liest und anwendet.

Sie können diese Interfaces entweder selbst implementieren oder die vom Zabbix Go SDK bereitgestellten Standardimplementierungen verwenden und diese bei Bedarf anpassen. Dieses Tutorial verwendet die Standardimplementierungen.

  1. Die Plugin-Struktur erstellen.

Importieren Sie nun das Paket plugin und erstellen Sie eine myIP-Struktur, die die Struktur plugin.Base einbettet:

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

type myIP struct {
    plugin.Base
}

Die Struktur myIP erfüllt derzeit das Accessor-Interface. Eine Methode zur Implementierung eines der funktionalen Plugin-Interfaces, des Exporter, wird später im Tutorial hinzugefügt.

Schritt 3: Datenpunkt-Schlüssel definieren

Ihr Plugin benötigt die Datenpunkt-Schlüssel, um Daten zu erfassen und an den Zabbix Server oder Proxy bereitzustellen.

  1. Importieren Sie das Paket errs für die Fehlerbehandlung:
import "golang.zabbix.com/sdk/errs"
  1. Registrieren Sie Datenpunkt-Schlüssel mit der Funktion plugin.RegisterMetrics() innerhalb der Funktion run():
func run() error {
    p := &myIP{}

    // Registriert den Datenpunkt-Schlüssel `myip`.
    err := plugin.RegisterMetrics(
        p,
        "MyIP",                           // Plugin-Name
        "myip",                           // Datenpunkt-Schlüsselname
        "Returns the host's IP address.", // Beschreibung des Datenpunkt-Schlüssels
    )
    if err != nil {
        return errs.Wrap(err, "failed to register metrics")
    }

    return nil
}

Um mehrere Datenpunkt-Schlüssel zu registrieren, wiederholen Sie die Parameter metric name und description für jede Metrik.
Zum Beispiel:

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

Schritt 4: Den Handler einrichten

Der Handler erleichtert die Kommunikation zwischen dem Agent und dem Plugin.

  1. Importieren Sie das container-Paket:
import "golang.zabbix.com/sdk/plugin/container"
  1. Fügen Sie innerhalb der Funktion run() Code hinzu, um einen Handler zu erstellen und einzurichten:
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
}

Schritt 5: Datenerfassung implementieren

Die Datenerfassung erfolgt über die Exporter-Schnittstelle, die die Methode Export beschreibt:

func Export(
  key string,             // Der Schlüssel des Datenpunkts, der erfasst werden soll.
  params []string,        // An den Datenpunktschlüssel übergebene Argumente (`myip[arg1, arg2]`).
  context ContextProvider // Metadaten zur Datenerfassung des Datenpunktschlüssels.
) (any, error)
  1. Importieren Sie die erforderlichen Pakete für HTTP-Anfragen und das Einlesen von Antworten:
import (
    "io"
    "net/http"
)
  1. Implementieren Sie die Methode Export für die Struktur myIP:
func (p *myIP) Export(
    key string, params []string, context plugin.ContextProvider,
) (any, error) {
    // Das Plugin kann je nach `key`-Parameter unterschiedliche Logik zur Datenerfassung verwenden.
    // Diese Implementierung prüft nur, ob der angegebene `key` unterstützt wird.
    if key != "myip" {
        return nil, errs.Errorf("unknown item key %q", key)
    }

    // Das Log wird an das Log von Agent 2 weitergeleitet.
    p.Infof(
        "received request to handle %q key with %d parameters",
        key,
        len(params),
    )

    // Erfassen Sie die Daten und geben Sie sie zurück.

    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
}

Schritt 6: Das Plugin erstellen und konfigurieren

  1. Um das Plugin zu erstellen, führen Sie aus:
go mod tidy
go build

Dadurch sollte im aktuellen Verzeichnis ein ausführbares myip erstellt werden.

  1. Konfigurieren Sie Zabbix Agent 2 so, dass das Plugin verwendet wird:
echo "Plugins.MyIP.System.Path=$PATH_TO_THE_MYIP_PLUGIN_EXECUTABLE" > /etc/zabbix_agent2.d/plugins.d/myip.conf

Ersetzen Sie $PATH_TO_THE_MYIP_PLUGIN_EXECUTABLE durch den Pfad zu dem in Schritt 5 erstellten myip.

Der Plugin-Name im Namen des Konfigurationsparameters (MyIP in diesem Tutorial) muss mit dem in der Funktion plugin.RegisterMetrics() definierten Plugin-Namen übereinstimmen.

  1. Um das Plugin und seinen myip-Datenpunkt zu testen, führen Sie aus:
zabbix_agent2 -c /etc/zabbix_agent2.conf -t myip

Die Ausgabe sollte eine externe IP-Adresse Ihres Hosts enthalten und etwa so aussehen:

myip                                          [s|192.0.2.1]

Damit haben Sie ein einfaches ladbares Plugin für Zabbix Agent 2 erstellt. Glückwunsch!

Vollständiger Quellcode

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{}

    // Registrieren Sie den `myip` Datenpunkt-Schlüssel.
    err := plugin.RegisterMetrics(
        p,
        "MyIP",                           // Name des Plugins
        "myip",                           // Name des Datenpunkt-Schlüssels
        "Gibt die IP-Adresse des Hosts zurück.", // Beschreibung des Datenpunkt-Schlüssels
    )
    if err != nil {
        return errs.Wrap(err, "Fehler beim Registrieren der Metriken")
    }

    // Einen neuen Handler erstellen.
    h, err := container.NewHandler("MyIP") // Name des Plugins
    if err != nil {
        return errs.Wrap(err, "Fehler beim Erstellen eines neuen Handlers")
    }

    // Logging einrichten, um Protokolle vom Plugin an den Agent weiterzuleiten.
    // Verfügbar über p.Logger.Infof, p.Logger.Debugf usw.
    p.Logger = h

    // Die Ausführung des Plugins starten.
    // Blockiert, bis eine Beendigungsanforderung vom Agent empfangen wird.
    err = h.Execute()
    if err != nil {
        return errs.Wrap(err, "Fehler bei der Ausführung des Plugin-Handlers")
    }

    return nil
}

func (p *myIP) Export(
    key string, params []string, context plugin.ContextProvider,
) (any, error) {
    // Das Plugin kann je nach `key`-Parameter unterschiedliche Datenerfassungslogik verwenden.  
    // Diese Implementierung prüft nur, ob der angegebene `key` unterstützt wird. 
    if key != "myip" {
        return nil, errs.Errorf("unbekannter Datenpunkt-Schlüssel %q", key)
    }

    // Das Protokoll wird an das Log des Agent 2 weitergeleitet.
    p.Infof(
        "Anforderung zum Verarbeiten des Schlüssels %q mit %d Parametern empfangen",
        key,
        len(params),
    )

    // Die Daten erfassen und zurückgeben.

    resp, err := http.Get("https://api.ipify.org")
    if err != nil {
        return nil, errs.Wrap(err, "Fehler beim Abrufen der IP-Adresse")
    }

    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, errs.Wrap(err, "Fehler beim Lesen des Antwortinhalts")
    }

    return string(body), nil
}