Criando um plugin

Esta página destina-se àqueles que têm interesse em desenvolver seu próprio plugin (coletor de dados independente) para o Zabbix agent 2.

Um plugin personalizado permite estender a funcionalidade do Zabbix agent 2 além dos plugins integrados e dos plugins carregáveis fornecidos.

Cada plugin é um pacote Go que define a estrutura e implementa uma ou várias interfaces (Exporter, Configurator, Runner). Para mais informações, consulte Interfaces de plugin e Diagrama de conexão.

Este guia ajudará você a criar um plugin carregável personalizado.

Para orientações adicionais, consulte também o repositório de:

O que você criará

Este é um tutorial passo a passo para criar um plugin carregável simples chamado MyIP. Este plugin implementará uma única chave de item (myip) que retorna o endereço IP externo do host do agent Zabbix.

Etapa 1: Configuração

  1. Um plugin é um módulo Go padrão. Comece inicializando o arquivo go.mod no diretório do plugin para rastrear as dependências do plugin:
cd path/to/plugins/myip # Mude para o diretório do seu plugin
go mod init myip
  1. Instale a dependência obrigatória Zabbix Go SDK (golang.zabbix.com/sdk):
go get golang.zabbix.com/sdk@$LATEST_COMMIT_HASH

Substitua $LATEST_COMMIT_HASH pelo hash do commit HEAD mais recente do repositório golang.zabbix.com/sdk no branch de release apropriado. Por exemplo:

go get golang.zabbix.com/sdk@af85407

Observe que o versionamento de golang.zabbix.com/sdk atualmente não é suportado, mas isso pode mudar no futuro.

Dependências adicionais podem ser instaladas conforme necessário usando go get.

  1. Crie um arquivo main.go vazio para o código-fonte do plugin:
touch main.go

Agora a configuração inicial está concluída, e o plugin está pronto para desenvolvimento.

Etapa 2: Estrutura do plugin

O módulo golang.zabbix.com/sdk, instalado na etapa anterior, fornece a estrutura necessária para o desenvolvimento de plugins e garante que todos os plugins tenham uma estrutura consistente.

  1. Configure o fluxo básico de execução.

Comece definindo o fluxo principal de execução do plugin. Adicione o seguinte código a main.go:

package main

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

func run() error {
    return nil
}

Isso estabelece o fluxo básico de execução do plugin. A função run conterá posteriormente a lógica principal do plugin.

  1. Explore as interfaces do plugin.

Um plugin do Zabbix agent 2 deve ser representado por uma struct que implemente interfaces do pacote golang.zabbix.com/sdk/plugin:

  • Accessor - define métodos essenciais que todos os plugins devem implementar, como definir o nome do plugin e lidar com timeouts de chave de item.
  • Uma ou mais das seguintes interfaces funcionais de plugin:
    • Exporter - executa uma coleta e retorna um valor (ou valores), nada ou um erro; frequentemente usado em conjunto com a interface Collector.
    • Collector - gerencia a coleta periódica de dados.
    • Runner - define os procedimentos de inicialização e encerramento do plugin.
    • Watcher - permite implementar a coleta independente de métricas, contornando o agendador interno do agent; útil para monitoramento baseado em traps ou orientado a eventos.
    • Configurator - define como o plugin lê e aplica suas configurações.

Você pode implementar essas interfaces por conta própria ou usar as implementações padrão fornecidas pelo Zabbix Go SDK, modificando-as conforme necessário. Este tutorial usa as implementações padrão.

  1. Crie a struct do plugin.

Agora, importe o pacote plugin e crie uma struct myIP que incorpora a struct plugin.Base:

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

type myIP struct {
    plugin.Base
}

No momento, a struct myIP satisfaz a interface Accessor. Um método para implementar uma das interfaces funcionais de plugin, Exporter, será adicionado posteriormente no tutorial.

Etapa 3: Definir chaves de item

Seu plugin precisa das chaves de item para coletar dados e fornecê-los ao Zabbix server ou proxy.

  1. Importe o pacote errs para tratamento de erros:
import "golang.zabbix.com/sdk/errs"
  1. Registre as chaves de item usando a função plugin.RegisterMetrics() dentro da função 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
}

Para registrar várias chaves de item, repita os parâmetros nome da métrica e descrição para cada métrica.
Por exemplo:

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

Etapa 4: Configurar o handler

O handler facilita a comunicação entre o agent e o plugin.

  1. Importe o pacote container:
import "golang.zabbix.com/sdk/plugin/container"
  1. Dentro da função run(), adicione código para criar e configurar um handler:
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
}

Etapa 5: Implementar a coleta de dados

A coleta de dados é feita por meio da interface Exporter, que descreve o método Export:

func Export(
  key string,             // A chave do item a ser coletado.
  params []string,        // Argumentos passados para a chave do item (`myip[arg1, arg2]`).
  context ContextProvider // Metadados sobre a coleta de dados da chave do item.
) (any, error)
  1. Importe os pacotes necessários para requisições HTTP e leitura da resposta:
import (
    "io"
    "net/http"
)
  1. Implemente o método Export para a struct myIP:
func (p *myIP) Export(
    key string, params []string, context plugin.ContextProvider,
) (any, error) {
    // O plugin pode usar diferentes lógicas de coleta de dados com base no parâmetro `key`.
    // Esta implementação apenas verifica se a `key` fornecida é suportada.
    if key != "myip" {
        return nil, errs.Errorf("unknown item key %q", key)
    }

    // O log será encaminhado para o log do agent 2.
    p.Infof(
        "received request to handle %q key with %d parameters",
        key,
        len(params),
    )

    // Coleta os dados e os retorna.

    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
}

Etapa 6: Compilar e configurar o plugin

  1. Para compilar o plugin, execute:
go mod tidy
go build

Isso deve criar um executável myip no diretório atual.

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

Substitua $PATH_TO_THE_MYIP_PLUGIN_EXECUTABLE pelo caminho para o myip criado na etapa 5.

O nome do plugin no nome do parâmetro de configuração (MyIP neste tutorial) deve corresponder ao nome do plugin definido na função plugin.RegisterMetrics().

  1. Para testar o plugin e seu item myip, execute:
zabbix_agent2 -c /etc/zabbix_agent2.conf -t myip

A saída deve conter um endereço IP externo do seu host e ser semelhante a isto:

myip                                          [s|192.0.2.0]

Com isso, você criou um plugin carregável simples para o Zabbix agent 2. Parabéns!

Código-fonte 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 a chave de item `myip`.
    err := plugin.RegisterMetrics(
        p,
        "MyIP",                           // Nome do plugin
        "myip",                           // Nome da chave de item
        "Retorna o endereço IP do host.", // Descrição da chave de item
    )
    if err != nil {
        return errs.Wrap(err, "falha ao registrar métricas")
    }

    // Cria um novo handler.
    h, err := container.NewHandler("MyIP") // Nome do plugin
    if err != nil {
        return errs.Wrap(err, "falha ao criar novo handler")
    }

    // Configura o log para encaminhar logs do plugin para o agent.
    // Disponível via p.Logger.Infof, p.Logger.Debugf etc.
    p.Logger = h

    // Inicia a execução do plugin.
    // Bloqueia até que uma solicitação de encerramento do agent seja recebida.
    err = h.Execute()
    if err != nil {
        return errs.Wrap(err, "falha ao executar o handler do plugin")
    }

    return nil
}

func (p *myIP) Export(
    key string, params []string, context plugin.ContextProvider,
) (any, error) {
    // O plugin pode usar diferentes lógicas de coleta de dados com base no parâmetro `key`.  
    // Esta implementação apenas verifica se a `key` fornecida é suportada. 
    if key != "myip" {
        return nil, errs.Errorf("chave de item desconhecida %q", key)
    }

    // O log será encaminhado para o log 2 do agent.
    p.Infof(
        "solicitação recebida para processar a chave %q com %d parâmetros",
        key,
        len(params),
    )

    // Coleta os dados e os retorna.

    resp, err := http.Get("https://api.ipify.org")
    if err != nil {
        return nil, errs.Wrap(err, "falha ao obter o endereço IP")
    }

    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, errs.Wrap(err, "falha ao ler o corpo da resposta")
    }

    return string(body), nil
}