Создание плагина

Эта страница предназначена для тех, кто заинтересован в разработке собственного плагина (независимого сборщика данных) для Zabbix агент 2.

Пользовательский плагин позволяет расширить функциональность Zabbix агент 2 сверх предоставляемых встроенных плагинов и загружаемых плагинов.

Каждый плагин представляет собой пакет Go, который определяет структуру и реализует один или несколько интерфейсов (Exporter, Configurator, Runner). Дополнительную информацию см. в разделах Интерфейсы плагинов и Схема соединений.

Это руководство поможет вам создать пользовательский загружаемый плагин.

Дополнительные рекомендации также можно найти в репозитории:

Что вы создадите

Это пошаговое руководство по созданию простого загружаемого плагина с именем MyIP. Этот плагин будет реализовывать один ключ элемента данных (myip), возвращающий внешний IP-адрес узла сети агента Zabbix.

Шаг 1: Настройка

  1. Плагин представляет собой стандартный модуль Go. Начните с инициализации файла go.mod в каталоге плагина, чтобы отслеживать зависимости плагина:
cd path/to/plugins/myip # Перейдите в каталог вашего плагина
go mod init myip
  1. Установите обязательную зависимость Zabbix Go SDK (golang.zabbix.com/sdk):
go get golang.zabbix.com/sdk@$LATEST_COMMIT_HASH

Замените $LATEST_COMMIT_HASH на хеш последнего коммита HEAD из репозитория golang.zabbix.com/sdk в соответствующей ветке релиза. Например:

go get golang.zabbix.com/sdk@af85407

Обратите внимание, что управление версиями для golang.zabbix.com/sdk в настоящее время не поддерживается, но это может измениться в будущем.

Дополнительные зависимости при необходимости можно установить с помощью go get.

  1. Создайте пустой файл main.go для исходного кода плагина:
touch main.go

Теперь начальная настройка завершена, и плагин готов к разработке.

Шаг 2: Структура плагина

Модуль golang.zabbix.com/sdk, установленный на предыдущем шаге, предоставляет необходимую основу для разработки плагинов и обеспечивает единообразную структуру всех плагинов.

  1. Настройте базовый поток выполнения.

Начните с определения основного потока выполнения плагина. Добавьте следующий код в main.go:

package main

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

func run() error {
    return nil
}

Это задает базовый поток выполнения для плагина. Позже функция run будет содержать основную логику плагина.

  1. Изучите интерфейсы плагина.

Плагин Zabbix агент 2 должен быть представлен структурой, реализующей интерфейсы из пакета golang.zabbix.com/sdk/plugin:

  • Accessor — определяет основные методы, которые должны реализовывать все плагины, например установку имени плагина и обработку тайм-аутов ключей элементов данных.
  • Один или несколько из следующих функциональных интерфейсов плагина:
    • Exporter — выполняет опрос и возвращает значение (или значения), ничего или ошибку; часто используется вместе с интерфейсом Collector.
    • Collector — управляет периодическим сбором данных.
    • Runner — определяет процедуры запуска и остановки плагина.
    • Watcher — позволяет реализовать независимый опрос метрик в обход внутреннего планировщика агента; полезен для мониторинга на основе ловушек или событий.
    • Configurator — определяет, как плагин считывает и применяет свои настройки конфигурации.

Вы можете либо реализовать эти интерфейсы самостоятельно, либо использовать стандартные реализации, предоставляемые Zabbix Go SDK, изменяя их по мере необходимости.
В этом руководстве используются стандартные реализации.

  1. Создайте структуру плагина.

Теперь импортируйте пакет plugin и создайте структуру myIP, которая встраивает структуру plugin.Base:

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

type myIP struct {
    plugin.Base
}

В настоящее время структура myIP удовлетворяет интерфейсу Accessor. Позже в этом руководстве будет добавлен метод для реализации одного из функциональных интерфейсов плагина — Exporter.

Шаг 3: Определите ключи элементов данных

Вашему плагину нужны ключи элементов данных для сбора данных и их передачи на сервер Zabbix или прокси.

  1. Импортируйте пакет errs для обработки ошибок:
import "golang.zabbix.com/sdk/errs"
  1. Зарегистрируйте ключи элементов данных с помощью функции plugin.RegisterMetrics() внутри функции 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
}

Чтобы зарегистрировать несколько ключей элементов данных, повторите параметры имя метрики и описание для каждой метрики.
Например:

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

Шаг 4: Настройка обработчика

Обработчик обеспечивает взаимодействие между агентом и плагином.

  1. Импортируйте пакет container:
import "golang.zabbix.com/sdk/plugin/container"
  1. Внутри функции 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")
    }

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

Шаг 5: Реализуйте сбор данных

Сбор данных выполняется через интерфейс Exporter, который описывает метод Export:

func Export(
  key string,             // Ключ элемента данных для сбора.
  params []string,        // Аргументы, передаваемые ключу элемента данных (`myip[arg1, arg2]`).
  context ContextProvider // Метаданные о сборе данных ключа элемента данных.
) (any, error)
  1. Импортируйте необходимые пакеты для HTTP-запросов и чтения ответа:
import (
    "io"
    "net/http"
)
  1. Реализуйте метод Export для структуры myIP:
func (p *myIP) Export(
    key string, params []string, context plugin.ContextProvider,
) (any, error) {
    // Плагин может использовать различную логику сбора данных в зависимости от параметра `key`.
    // Эта реализация только проверяет, что указанный `key` поддерживается.
    if key != "myip" {
        return nil, errs.Errorf("unknown item key %q", key)
    }

    // Журнал будет перенаправлен в журнал agent 2.
    p.Infof(
        "received request to handle %q key with %d parameters",
        key,
        len(params),
    )

    // Соберите данные и верните их.

    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
}

Шаг 6: Сборка и настройка плагина

  1. Чтобы собрать плагин, выполните:
go mod tidy
go build

Это должно создать исполняемый файл myip в текущем каталоге.

  1. Настройте агент Zabbix 2 на использование плагина:
echo "Plugins.MyIP.System.Path=$PATH_TO_THE_MYIP_PLUGIN_EXECUTABLE" > /etc/zabbix_agent2.d/plugins.d/myip.conf

Замените $PATH_TO_THE_MYIP_PLUGIN_EXECUTABLE на путь к файлу myip, созданному на шаге 5.

Имя плагина в имени параметра конфигурации (MyIP в этом руководстве) должно совпадать с именем плагина, определённым в функции plugin.RegisterMetrics().

  1. Чтобы протестировать плагин и его элемент данных myip, выполните:
zabbix_agent2 -c /etc/zabbix_agent2.conf -t myip

Вывод должен содержать внешний IP-адрес вашего узла сети и выглядеть примерно так:

myip                                          [s|192.0.2.0]

На этом всё — вы создали простой загружаемый плагин для агента Zabbix 2. Поздравляем!

Полный исходный код

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

    // Регистрируем ключ элемента данных `myip`.
    err := plugin.RegisterMetrics(
        p,
        "MyIP",                           // Имя плагина
        "myip",                           // Имя ключа элемента данных
        "Returns the host's IP address.", // Описание ключа элемента данных
    )
    if err != nil {
        return errs.Wrap(err, "failed to register metrics")
    }

    // Создаем новый обработчик.
    h, err := container.NewHandler("MyIP") // Имя плагина
    if err != nil {
        return errs.Wrap(err, "failed to create new handler")
    }

    // Настраиваем журналирование для перенаправления журналов из плагина в агент.
    // Доступно через p.Logger.Infof, p.Logger.Debugf и т.д.
    p.Logger = h

    // Запускаем выполнение плагина.
    // Блокирует выполнение до получения запроса на завершение от агента.
    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) {
    // Плагин может использовать различную логику сбора данных в зависимости от параметра `key`.  
    // Эта реализация только проверяет, что предоставленный `key` поддерживается. 
    if key != "myip" {
        return nil, errs.Errorf("unknown item key %q", key)
    }

    // Журнал будет перенаправлен в журнал агента 2.
    p.Infof(
        "received request to handle %q key with %d parameters",
        key,
        len(params),
    )

    // Собираем данные и возвращаем их.

    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
}