Crear un plugin (tutorial)

Este tutorial paso a paso explica cómo crear un plugin cargable sencillo para el agente 2 de Zabbix.

También puedes usar el repositorio de ejemplo como plantilla o guía para crear tus propios plugins.

Qué creará

Este tutorial muestra cómo crear un nuevo complemento cargable, MyIP. El complemento implementará una clave de métrica única, myip, que devuelve la dirección IP externa del equipo donde se ejecuta el agente 2 de Zabbix.

Paso 1: Configuración

  1. Un plugin es un módulo estándar de Go. Comience inicializando el archivo go.mod en el directorio del plugin para rastrear las dependencias del plugin:
cd path/to/plugins/myip # Cambie al directorio de su plugin
go mod init myip
  1. Instale la dependencia obligatoria Zabbix Go SDK (golang.zabbix.com/sdk):
go get golang.zabbix.com/sdk@$LATEST_COMMIT_HASH

Reemplace $LATEST_COMMIT_HASH con el último hash de commit HEAD del repositorio golang.zabbix.com/sdk en la rama de lanzamiento correspondiente. Por ejemplo:

go get golang.zabbix.com/sdk@af85407

Tenga en cuenta que actualmente no se admite la gestión de versiones de golang.zabbix.com/sdk, pero esto puede cambiar en el futuro.

Las dependencias adicionales se pueden instalar según sea necesario usando go get.

  1. Cree un archivo vacío main.go para el código fuente del plugin:
touch main.go

Ahora la configuración inicial está completa y el plugin está listo para su desarrollo.

Paso 2: Estructura del plugin

El módulo golang.zabbix.com/sdk, instalado en el paso anterior, proporciona el marco necesario para el desarrollo de plugins y garantiza que todos los plugins tengan una estructura coherente.

  1. Configurar el flujo básico de ejecución.

Comience definiendo el flujo principal de ejecución del plugin. Añada el siguiente código a main.go:

package main

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

func run() error {
    return nil
}

Esto establece el flujo básico de ejecución para el plugin. La función run contendrá más adelante la lógica principal del plugin.

  1. Explorar las interfaces del plugin.

Un plugin de Zabbix agent 2 debe estar representado por una estructura que implemente interfaces del paquete golang.zabbix.com/sdk/plugin:

  • Accessor - define los métodos esenciales que todos los plugins deben implementar, como establecer el nombre del plugin y gestionar los timeouts de las claves de item.
  • Una o más de las siguientes interfaces funcionales de plugin:
    • Exporter - realiza una consulta y devuelve un valor (o valores), nada o un error; a menudo se utiliza junto con la interfaz Collector.
    • Collector - gestiona la recopilación periódica de datos.
    • Runner - define los procedimientos de inicio y apagado del plugin.
    • Watcher - permite implementar la consulta de métricas de forma independiente, omitiendo el planificador interno del agent; útil para la monitorización basada en traps o eventos.
    • Configurator - define cómo el plugin lee y aplica su configuración.

Puede implementar estas interfaces usted mismo o utilizar las predeterminadas proporcionadas por el Zabbix Go SDK, modificándolas según sea necesario. Este tutorial utiliza las implementaciones por defecto.

  1. Crear la estructura del plugin.

Ahora, importe el paquete plugin y cree una estructura myIP que incluya la estructura plugin.Base:

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

type myIP struct {
    plugin.Base
}

La estructura myIP actualmente cumple con la interfaz Accessor. Más adelante en el tutorial se añadirá un método para implementar una de las interfaces funcionales del plugin, el Exporter.

Paso 3: Definir las claves de los items

Tu plugin necesita las claves de los items para recopilar datos y proporcionarlos al server o proxy de Zabbix.

  1. Importa el paquete errs para el manejo de errores:
import "golang.zabbix.com/sdk/errs"
  1. Registra las claves de los items usando la función plugin.RegisterMetrics() dentro de la función run():
func run() error {
    p := &myIP{}

    // Registrar la clave de item `myip`.
    err := plugin.RegisterMetrics(
        p,
        "MyIP",                           // Nombre del plugin
        "myip",                           // Nombre de la clave de item
        "Returns the host's IP address.", // Descripción de la clave de item
    )
    if err != nil {
        return errs.Wrap(err, "failed to register metrics")
    }

    return nil
}

Para registrar varias claves de items, repite los parámetros nombre de la métrica y descripción para cada métrica. Por ejemplo:

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

Paso 4: Configurar el handler

El handler facilita la comunicación entre el agent y el plugin.

  1. Importe el paquete container:
import "golang.zabbix.com/sdk/plugin/container"
  1. Dentro de la función run(), agregue el código para crear y configurar un handler:
func run() error {
    p := &myIP{}

    // Registrar la clave de item `myip`.
    err := plugin.RegisterMetrics(
        p,
        "MyIP",                           // Nombre del plugin
        "myip",                           // Nombre de la clave de item
        "Returns the host's IP address.", // Descripción de la clave de item
    )
    if err != nil {
        return errs.Wrap(err, "failed to register metrics")
    }

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

    // Configurar el registro para reenviar los logs del plugin al agent.
    // Disponible a través de p.Logger.Infof, p.Logger.Debugf, etc.
    p.Logger = h

    // Iniciar la ejecución del plugin.
    // Bloquea hasta que se reciba una solicitud de terminación desde el agent.
    err = h.Execute()
    if err != nil {
        return errs.Wrap(err, "failed to execute plugin handler")
    }

    return nil
}

Paso 5: Implementar la recopilación de datos

La recopilación de datos se realiza a través de la interfaz Exporter, que describe el método Export:

func Export(
  key string,             // La clave del item a recopilar.
  params []string,        // Argumentos pasados a la clave del item (`myip[arg1, arg2]`).
  context ContextProvider // Metadatos sobre la recopilación de datos de la clave del item.
) (any, error)
  1. Importe los paquetes necesarios para las solicitudes HTTP y la lectura de respuestas:
import (
    "io"
    "net/http"
)
  1. Implemente el método Export para la estructura myIP:
func (p *myIP) Export(
    key string, params []string, context plugin.ContextProvider,
) (any, error) {
    // El plugin puede usar una lógica de recopilación de datos diferente según el parámetro `key`.
    // Esta implementación solo verifica que la `key` proporcionada sea compatible.
    if key != "myip" {
        return nil, errs.Errorf("unknown item key %q", key)
    }

    // El registro se reenviará al registro del agent 2.
    p.Infof(
        "received request to handle %q key with %d parameters",
        key,
        len(params),
    )

    // Recopile los datos y devuélvalos.

    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
}

Paso 6: Compilar y configurar el plugin

  1. Para compilar el plugin, ejecute:
go mod tidy
go build

Esto debería crear un ejecutable myip en el directorio actual.

  1. Configure el agent Zabbix 2 para usar el plugin:
echo "Plugins.MyIP.System.Path=$PATH_TO_THE_MYIP_PLUGIN_EXECUTABLE" > /etc/zabbix_agent2.d/plugins.d/myip.conf

Reemplace $PATH_TO_THE_MYIP_PLUGIN_EXECUTABLE con la ruta al myip creado en el paso 5.

El nombre del plugin en el parámetro de configuración (MyIP en este tutorial) debe coincidir con el nombre del plugin definido en la función plugin.RegisterMetrics().

  1. Para probar el plugin y su item myip, ejecute:
zabbix_agent2 -c /etc/zabbix_agent2.conf -t myip

La salida debe contener una dirección IP externa de su host y verse similar a esto:

myip                                          [s|192.0.2.0]

Con esto, ha creado un plugin simple cargable para el agent Zabbix 2. ¡Felicidades!

Código fuente 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{}

    // Registrar la clave de item `myip`.
    err := plugin.RegisterMetrics(
        p,
        "MyIP",                           // Nombre del plugin
        "myip",                           // Nombre de la clave de item
        "Returns the host's IP address.", // Descripción de la clave de item
    )
    if err != nil {
        return errs.Wrap(err, "failed to register metrics")
    }

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

    // Configurar el logging para reenviar los logs del plugin al agent.
    // Disponible a través de p.Logger.Infof, p.Logger.Debugf, etc.
    p.Logger = h

    // Iniciar la ejecución del plugin.
    // Bloquea hasta que se recibe una solicitud de terminación del 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) {
    // El plugin puede usar diferentes lógicas de recopilación de datos según el parámetro `key`.
    // Esta implementación solo verifica que la `key` proporcionada sea compatible.
    if key != "myip" {
        return nil, errs.Errorf("unknown item key %q", key)
    }

    // El log se reenviará al log 2 del agent.
    p.Infof(
        "received request to handle %q key with %d parameters",
        key,
        len(params),
    )

    // Recopilar los datos y devolverlos.

    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
}