Criar um plugin (tutorial)

Este é um tutorial passo a passo sobre como criar um plugin carregável simples para o agente Zabbix 2.

Você também pode usar o repositório de exemplo como modelo ou guia para criar seus próprios plugins.

O que você criará

Durante este tutorial, você adicionará um novo plug-in carregável MyIP. O plug-in implementará uma métrica chamada myip, que retorna o endereço IP externo do host em que o Zabbix Agent 2 está sendo executado.

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 caminho/para/plugins/myip # Mude para o diretório do seu plugin
go mod init myip
  1. Instale a dependência obrigatória do Zabbix Go SDK (golang.zabbix.com/sdk):
go get golang.zabbix.com/sdk@$LATEST_COMMIT_HASH

Substitua $LATEST_COMMIT_HASH pelo hash de commit HEAD mais recente do golang.zabbix.com/sdk repositório na ramificação de lançamento apropriada.

Por exemplo:

go get golang.zabbix.com/sdk@af85407

Observe que o versionamento golang.zabbix.com/sdk não é suportado atualmente, 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 tenham uma estrutura consistente.

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

Comece definindo o fluxo de execução principal 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 de execução básico para o plugin. A função run conterá posteriormente a lógica central do plugin.

  1. Explore as interfaces do plugin.

Um plugin do agente Zabbix 2 deve ser representado por uma estrutura que implementa interfaces do pacote golang.zabbix.com/sdk/plugin:

  • Assessor - define métodos essenciais que todos os plugins devem implementar, como definir o nome do plugin e lidar com timeouts de chaves de itens.
  • Uma ou mais das seguintes interfaces funcionais de plugin:
  • Exportador - realiza uma consulta e retorna um valor (ou valores), nada ou um erro; frequentemente usado em conjunto com a interface Coletor.
  • Coletor - gerencia a coleta periódica de dados.
  • Executor - define os procedimentos de inicialização e desligamento do plugin.
  • Observador - permite implementar consultas de métricas independentes, ignorando o agendador interno do agente; útil para monitoramento baseado em traps ou eventos.
  • Configurador - define como o plugin lê e aplica suas configurações.

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

  1. Crie a estrutura do plugin.

Agora, importe o pacote plugin e crie uma estrutura myIP que incorpore a estrutura plugin.Base:

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

type myIP struct {
plugin.Base
}

A estrutura myIP atualmente atende à interface Accessor. Um método para implementar uma das interfaces funcionais do plugin, o Exporter, será adicionado posteriormente neste tutorial.

Etapa 3: Definir chaves de item

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

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

// Registre a chave de item `myip`.
err := plugin.RegisterMetrics(
p,
"MyIP", // Nome do plugin
"myip", // Nome da chave do item
"Retorna o endereço IP do host.", // Descrição da chave do item
)
if err != nil {
return errs.Wrap(err, "falha ao registrar métricas")
}

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", "Descrição da métrica um.", "metric.two", "Descrição da métrica dois.")

Etapa 4: Configurar o manipulador

O manipulador facilita a comunicação entre o agente e o plugin.

  1. Importe o pacote container:
import "golang.zabbix.com/sdk/plugin/container"
  1. Insira o código da função run() para criar e configurar um manipulador:
func run() error {
p := &myIP{}

// Registre a chave de item `myip`.
err := plugin.RegisterMetrics(
p,
"MyIP", // Nome do plugin
"myip", // Nome da chave do item
"Retorna o endereço IP do host.", // Descrição da chave do item
)
if err != nil {
return errs.Wrap(err, "falha ao registrar métricas")
}

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

// Configura o registro para encaminhar logs do plugin para o agente.
// 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 término seja recebida do agente.
err = h.Execute()
if err != nil {
return errs.Wrap(err, "falha ao executar o manipulador do plugin")
}

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 coletada.
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 solicitações HTTP e leitura de respostas:
import(
"io"
"net/http"
)
  1. Implemente o método Export para a estrutura 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 `chave` fornecida é suportada.
if key != "myip" {
return nil, errs.Errorf("chave de item desconhecida %q", key)
}

// O log será encaminhado para o log do agente 2.
p.Infof(
"solicitação recebida para manipular 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
}

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 agente Zabbix 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 parâmetro de configuração name (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 este:

myip [s|192.0.2.0]

Com isso, você criou um plugin carregável simples para o agente Zabbix 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 do item `myip`.
    err := plugin.RegisterMetrics(
        p,
        "MyIP",   // Nome do plugin
        "myip",   // Nome da chave do item
        "Retorna o endereço IP do host.", // Descrição da chave do item
    )
    if err != nil {
        return errs.Wrap(err, "falha ao registrar métricas")
    }
    // Cria um novo manipulador.
    h, err := container.NewHandler("MyIP") // Nome do plugin
    if err != nil {
        return errs.Wrap(err, "falha ao criar novo manipulador")
    }

    // Configura o registro para encaminhar logs do plugin para o agente.
    // 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 agente seja recebida.
    err = h.Execute()
    if err != nil {
        return errs.Wrap(err, "falha ao executar o manipulador do plugin")
    }
    return nil
}

func (p *myIP) Export(
    key string, params []string, context plugin.ContextProvider,
) (any, error) {
    // O plugin pode usar uma lógica de coleta de dados diferente com base no parâmetro `key`.  
    // Esta implementação verifica apenas 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 agente 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, "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
}