Creare un plugin (tutorial)

Questo è un tutorial passo passo su come creare un semplice plugin caricabile per Zabbix agent 2.

Puoi anche utilizzare il repository di esempio come modello o guida per creare i tuoi plugin.

Cosa creerai

Questo tutorial mostra come creare un nuovo plugin caricabile MyIP. Il plugin implementerà una singola chiave di elemento, myip, che restituisce l'indirizzo IP esterno dell'host su cui è in esecuzione Zabbix agent 2.

Passaggio 1: Setup

  1. Un plugin è un modulo Go standard. Inizia inizializzando il file go.mod nella directory del plugin per tracciarne le dipendenze:
cd path/to/plugins/myip # Passa alla directory del tuo plugin
go mod init myip
  1. Installa la dipendenza obbligatoria Zabbix Go SDK (golang.zabbix.com/sdk):
go get golang.zabbix.com/sdk@$LATEST_COMMIT_HASH

Sostituisci $LATEST_COMMIT_HASH con il più recente hash del commit HEAD dal repository golang.zabbix.com/sdk nel branch di rilascio appropriato. Ad esempio:

go get golang.zabbix.com/sdk@af85407

Si noti che il versioning di golang.zabbix.com/sdk non è attualmente supportato, ma questo potrebbe cambiare in futuro.

È possibile installare dipendenze aggiuntive, se necessario, utilizzando go get.

  1. Crea un file main.go vuoto per il codice sorgente del plugin:
touch main.go

Ora il setup iniziale è completo e il plugin è pronto per lo sviluppo.

Passo 2: Struttura del plugin

Il modulo golang.zabbix.com/sdk, installato nel passaggio precedente, fornisce il framework necessario per lo sviluppo dei plugin e garantisce che tutti i plugin abbiano una struttura coerente.

  1. Configurare il flusso di esecuzione di base.

Inizia definendo il flusso di esecuzione principale del plugin. Aggiungi il seguente codice a main.go:

package main

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

func run() error {
    return nil
}

Questo stabilisce il flusso di esecuzione di base per il plugin. La funzione run conterrà in seguito la logica principale del plugin.

  1. Esplorare le interfacce del plugin.

Un plugin di Zabbix agent 2 deve essere rappresentato da una struct che implementa le interfacce del pacchetto golang.zabbix.com/sdk/plugin:

  • Accessor - definisce i metodi essenziali che tutti i plugin devono implementare, come l'impostazione del nome del plugin e la gestione dei timeout della chiave dell'item.
  • Una o più delle seguenti interfacce funzionali del plugin:
    • Exporter - esegue un poll e restituisce un valore (o valori), nulla oppure un errore; spesso usato insieme all'interfaccia Collector.
    • Collector - gestisce la raccolta periodica dei dati.
    • Runner - definisce le procedure di avvio e arresto del plugin.
    • Watcher - consente di implementare il polling indipendente delle metriche, bypassando lo scheduler interno dell'agent; utile per il monitoraggio basato su trap o guidato da eventi.
    • Configurator - definisce come il plugin legge e applica le proprie impostazioni di configurazione.

Puoi implementare queste interfacce autonomamente oppure usare quelle predefinite fornite dallo Zabbix Go SDK, modificandole secondo necessità. Questo tutorial utilizza le implementazioni predefinite.

  1. Creare la struct del plugin.

Ora importa il pacchetto plugin e crea una struct myIP che incorpora la struct plugin.Base:

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

type myIP struct {
    plugin.Base
}

La struct myIP soddisfa attualmente l'interfaccia Accessor. Più avanti nel tutorial verrà aggiunto un metodo per implementare una delle interfacce funzionali del plugin, l'Exporter.

Passaggio 3: Definire le chiavi item

Il tuo plugin ha bisogno delle chiavi item per raccogliere i dati e fornirli a Zabbix server o proxy.

  1. Importa il pacchetto errs per la gestione degli errori:
import "golang.zabbix.com/sdk/errs"
  1. Registra le chiavi item usando la funzione plugin.RegisterMetrics() all'interno della funzione run():
func run() error {
    p := &myIP{}

    // Registra la chiave item `myip`.
    err := plugin.RegisterMetrics(
        p,
        "MyIP",                           // Nome del plugin
        "myip",                           // Nome della chiave item
        "Restituisce l'indirizzo IP dell'host.", // Descrizione della chiave item
    )
    if err != nil {
        return errs.Wrap(err, "failed to register metrics")
    }

    return nil
}

Per registrare più chiavi item, ripeti i parametri metric name e description per ciascuna metrica. Ad esempio:

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

Passaggio 4: Configurare l'handler

L'handler facilita la comunicazione tra l'agent e il plugin.

  1. Importa il package container:
import "golang.zabbix.com/sdk/plugin/container"
  1. All'interno della funzione run() aggiungi il codice per creare e configurare un handler:
func run() error {
    p := &myIP{}

    // Registra la chiave item `myip`.
    err := plugin.RegisterMetrics(
        p,
        "MyIP",                           // Nome del plugin
        "myip",                           // Nome della chiave item
        "Restituisce l'indirizzo IP dell'host.", // Descrizione della chiave item
    )
    if err != nil {
        return errs.Wrap(err, "failed to register metrics")
    }

    // Crea un nuovo handler.
    h, err := container.NewHandler("MyIP") // Nome del plugin
    if err != nil {
        return errs.Wrap(err, "failed to create new handler")
    }

    // Configura il logging per inoltrare i log dal plugin all'agent.
    // Disponibile tramite p.Logger.Infof, p.Logger.Debugf, ecc.
    p.Logger = h

    // Avvia l'esecuzione del plugin.
    // Rimane in attesa fino a quando non viene ricevuta una richiesta di terminazione dall'agent.
    err = h.Execute()
    if err != nil {
        return errs.Wrap(err, "failed to execute plugin handler")
    }

    return nil
}

Passaggio 5: Implementare la raccolta dati

La raccolta dati viene eseguita tramite l'interfaccia Exporter, che descrive il metodo Export:

func Export(
  key string,             // La chiave dell'item da raccogliere.
  params []string,        // Argomenti passati alla chiave dell'item (`myip[arg1, arg2]`).
  context ContextProvider // Metadati sulla raccolta dati della chiave dell'item.
) (any, error)
  1. Importa i pacchetti necessari per le richieste HTTP e la lettura della risposta:
import (
    "io"
    "net/http"
)
  1. Implementa il metodo Export per la struct myIP:
func (p *myIP) Export(
    key string, params []string, context plugin.ContextProvider,
) (any, error) {
    // Il plugin può usare una logica di raccolta dati diversa in base al parametro `key`.
    // Questa implementazione verifica solo che il `key` fornito sia supportato.
    if key != "myip" {
        return nil, errs.Errorf("unknown item key %q", key)
    }

    // Il log verrà inoltrato al log di agent 2.
    p.Infof(
        "received request to handle %q key with %d parameters",
        key,
        len(params),
    )

    // Raccogli i dati e restituiscili.

    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
}

Passo 6: Compilare e configurare il plugin

  1. Per compilare il plugin, esegui:
go mod tidy
go build

Questo dovrebbe creare un eseguibile myip nella directory corrente.

  1. Configura Zabbix agent 2 per usare il plugin:
echo "Plugins.MyIP.System.Path=$PATH_TO_THE_MYIP_PLUGIN_EXECUTABLE" > /etc/zabbix_agent2.d/plugins.d/myip.conf

Sostituisci $PATH_TO_THE_MYIP_PLUGIN_EXECUTABLE con il percorso del myip creato nel passaggio 5.

Il nome del plugin nel nome del parametro di configurazione (MyIP in questo tutorial) deve corrispondere al nome del plugin definito nella funzione plugin.RegisterMetrics().

  1. Per testare il plugin e il relativo item myip, esegui:
zabbix_agent2 -c /etc/zabbix_agent2.conf -t myip

L'output dovrebbe contenere un indirizzo IP esterno del tuo host e apparire simile a questo:

myip                                          [s|192.0.2.1]

Con questo, hai creato un semplice plugin caricabile per Zabbix agent 2. Complimenti!

Codice sorgente completo

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

    // Registra la chiave item `myip`.
    err := plugin.RegisterMetrics(
        p,
        "MyIP",                           // Nome del plugin
        "myip",                           // Nome della chiave item
        "Restituisce l'indirizzo IP dell'host.", // Descrizione della chiave item
    )
    if err != nil {
        return errs.Wrap(err, "impossibile registrare le metriche")
    }

    // Crea un nuovo handler.
    h, err := container.NewHandler("MyIP") // Nome del plugin
    if err != nil {
        return errs.Wrap(err, "impossibile creare un nuovo handler")
    }

    // Configura la registrazione per inoltrare i log dal plugin all'agent.
    // Disponibile tramite p.Logger.Infof, p.Logger.Debugf, ecc.
    p.Logger = h

    // Avvia l'esecuzione del plugin.
    // Rimane bloccato finché non viene ricevuta una richiesta di terminazione dall'agent.
    err = h.Execute()
    if err != nil {
        return errs.Wrap(err, "impossibile eseguire l'handler del plugin")
    }

    return nil
}

func (p *myIP) Export(
    key string, params []string, context plugin.ContextProvider,
) (any, error) {
    // Il plugin può usare una logica diversa di raccolta dati in base al parametro `key`.
    // Questa implementazione verifica solo che il `key` fornito sia supportato.
    if key != "myip" {
        return nil, errs.Errorf("chiave item sconosciuta %q", key)
    }

    // Il log verrà inoltrato al log dell'agent 2.
    p.Infof(
        "ricevuta richiesta per gestire la chiave %q con %d parametri",
        key,
        len(params),
    )

    // Raccoglie i dati e li restituisce.

    resp, err := http.Get("https://api.ipify.org")
    if err != nil {
        return nil, errs.Wrap(err, "impossibile ottenere l'indirizzo IP")
    }

    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, errs.Wrap(err, "impossibile leggere il corpo della risposta")
    }

    return string(body), nil
}