Tworzenie wtyczki

Ta strona jest przeznaczona dla osób zainteresowanych tworzeniem własnej wtyczki (niezależnego kolektora danych) dla agenta Zabbix 2.

Niestandardowa wtyczka pozwala rozszerzyć funkcjonalność agenta Zabbix 2 poza dostarczane wbudowane wtyczki i ładowalne wtyczki.

Każda wtyczka jest pakietem Go, który definiuje strukturę i implementuje jeden lub kilka interfejsów (Exporter, Configurator, Runner). Więcej informacji można znaleźć w sekcjach Interfejsy wtyczek oraz Diagram połączeń.

Ten przewodnik pomoże Ci utworzyć niestandardową ładowalną wtyczkę.

Dodatkowe wskazówki można również znaleźć w repozytorium:

Co utworzysz

To jest samouczek krok po kroku dotyczący tworzenia prostego ładowalnego pluginu o nazwie MyIP. Ten plugin zaimplementuje pojedynczy klucz pozycji (myip), zwracający zewnętrzny adres IP hosta agenta Zabbix.

Krok 1: Konfiguracja

  1. Wtyczka jest standardowym modułem Go. Zacznij od zainicjowania pliku go.mod w katalogu wtyczki, aby śledzić zależności wtyczki:
cd path/to/plugins/myip # Przejdź do katalogu swojej wtyczki
go mod init myip
  1. Zainstaluj obowiązkową zależność Zabbix Go SDK (golang.zabbix.com/sdk):
go get golang.zabbix.com/sdk@$LATEST_COMMIT_HASH

Zastąp $LATEST_COMMIT_HASH najnowszym hashem commita HEAD z repozytorium golang.zabbix.com/sdk w odpowiedniej gałęzi wydania. Na przykład:

go get golang.zabbix.com/sdk@af85407

Pamiętaj, że wersjonowanie golang.zabbix.com/sdk nie jest obecnie obsługiwane, ale może się to zmienić w przyszłości.

Dodatkowe zależności można instalować w razie potrzeby za pomocą go get.

  1. Utwórz pusty plik main.go dla kodu źródłowego wtyczki:
touch main.go

Początkowa konfiguracja jest teraz zakończona i wtyczka jest gotowa do dalszego tworzenia.

Krok 2: Struktura pluginu

Moduł golang.zabbix.com/sdk, zainstalowany w poprzednim kroku, zapewnia niezbędny framework do tworzenia pluginów i gwarantuje, że wszystkie pluginy mają spójną strukturę.

  1. Skonfiguruj podstawowy przepływ wykonywania.

Zacznij od zdefiniowania głównego przepływu wykonywania pluginu. Dodaj następujący kod do main.go:

package main

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

func run() error {
    return nil
}

To ustanawia podstawowy przepływ wykonywania dla pluginu. Funkcja run będzie później zawierać główną logikę pluginu.

  1. Poznaj interfejsy pluginu.

Plugin Zabbix agent 2 powinien być reprezentowany przez strukturę implementującą interfejsy z pakietu golang.zabbix.com/sdk/plugin:

  • Accessor — definiuje podstawowe metody, które muszą implementować wszystkie pluginy, takie jak ustawianie nazwy pluginu i obsługa limitów czasu kluczy pozycji.
  • Jeden lub więcej z następujących funkcjonalnych interfejsów pluginu:
    • Exporter — wykonuje odpytywanie i zwraca wartość (lub wartości), nic albo błąd; często używany razem z interfejsem Collector.
    • Collector — zarządza okresowym zbieraniem danych.
    • Runner — definiuje procedury uruchamiania i zamykania pluginu.
    • Watcher — umożliwia implementację niezależnego odpytywania metryk, z pominięciem wewnętrznego harmonogramu agenta; przydatny w monitorowaniu opartym na pułapkach lub zdarzeniach.
    • Configurator — definiuje sposób, w jaki plugin odczytuje i stosuje swoje ustawienia konfiguracyjne.

Możesz samodzielnie zaimplementować te interfejsy albo użyć domyślnych interfejsów dostarczanych przez Zabbix Go SDK, modyfikując je w razie potrzeby. W tym samouczku użyto domyślnych implementacji.

  1. Utwórz strukturę pluginu.

Teraz zaimportuj pakiet plugin i utwórz strukturę myIP, która osadza strukturę plugin.Base:

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

type myIP struct {
    plugin.Base
}

Struktura myIP obecnie spełnia wymagania interfejsu Accessor. Metoda implementująca jeden z funkcjonalnych interfejsów pluginu, Exporter, zostanie dodana w dalszej części samouczka.

Krok 3: Zdefiniuj klucze pozycji

Twoja wtyczka potrzebuje kluczy pozycji do zbierania danych i przekazywania ich do serwera Zabbix lub proxy.

  1. Zaimportuj pakiet errs do obsługi błędów:
import "golang.zabbix.com/sdk/errs"
  1. Zarejestruj klucze pozycji za pomocą funkcji plugin.RegisterMetrics() wewnątrz funkcji 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
}

Aby zarejestrować kilka kluczy pozycji, powtórz parametry nazwa metryki i opis dla każdej metryki.
Na przykład:

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

Krok 4: Skonfiguruj handler

Handler ułatwia komunikację między agent a wtyczką.

  1. Zaimportuj pakiet container:
import "golang.zabbix.com/sdk/plugin/container"
  1. W funkcji run() dodaj kod tworzący i konfigurujący 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
}

Krok 5: Implementacja zbierania danych

Zbieranie danych odbywa się za pośrednictwem interfejsu Exporter, który opisuje metodę Export:

func Export(
  key string,             // Klucz pozycji do zebrania.
  params []string,        // Argumenty przekazane do klucza pozycji (`myip[arg1, arg2]`).
  context ContextProvider // Metadane dotyczące zbierania danych klucza pozycji.
) (any, error)
  1. Zaimportuj wymagane pakiety do żądań HTTP i odczytu odpowiedzi:
import (
    "io"
    "net/http"
)
  1. Zaimplementuj metodę Export dla struktury myIP:
func (p *myIP) Export(
    key string, params []string, context plugin.ContextProvider,
) (any, error) {
    // Plugin może używać różnej logiki zbierania danych w zależności od parametru `key`.
    // Ta implementacja jedynie sprawdza, czy podany `key` jest obsługiwany.
    if key != "myip" {
        return nil, errs.Errorf("unknown item key %q", key)
    }

    // Log zostanie przekazany do logu agent 2.
    p.Infof(
        "received request to handle %q key with %d parameters",
        key,
        len(params),
    )

    // Zbierz dane i zwróć je.

    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
}

Krok 6: Zbuduj i skonfiguruj plugin

  1. Aby zbudować plugin, uruchom:
go mod tidy
go build

Powinno to utworzyć plik wykonywalny myip w bieżącym katalogu.

  1. Skonfiguruj Zabbix agent 2 tak, aby używał pluginu:
echo "Plugins.MyIP.System.Path=$PATH_TO_THE_MYIP_PLUGIN_EXECUTABLE" > /etc/zabbix_agent2.d/plugins.d/myip.conf

Zastąp $PATH_TO_THE_MYIP_PLUGIN_EXECUTABLE ścieżką do pliku myip utworzonego w kroku 5.

Nazwa pluginu w nazwie parametru konfiguracyjnego (MyIP w tym samouczku) musi odpowiadać nazwie pluginu zdefiniowanej w funkcji plugin.RegisterMetrics().

  1. Aby przetestować plugin i jego pozycja myip, uruchom:
zabbix_agent2 -c /etc/zabbix_agent2.conf -t myip

Dane wyjściowe powinny zawierać zewnętrzny adres IP twojego hosta i wyglądać podobnie do tego:

myip                                          [s|192.0.2.0]

W ten sposób utworzono prosty ładowalny plugin dla Zabbix agent 2. Gratulacje!

Pełny kod źródłowy

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

    // Zarejestruj klucz pozycji `myip`.
    err := plugin.RegisterMetrics(
        p,
        "MyIP",                           // Nazwa wtyczki
        "myip",                           // Nazwa klucza pozycji
        "Zwraca adres IP hosta.",         // Opis klucza pozycji
    )
    if err != nil {
        return errs.Wrap(err, "failed to register metrics")
    }

    // Utwórz nowy program obsługi.
    h, err := container.NewHandler("MyIP") // Nazwa wtyczki
    if err != nil {
        return errs.Wrap(err, "failed to create new handler")
    }

    // Skonfiguruj logowanie, aby przekazywać logi z wtyczki do agenta.
    // Dostępne przez p.Logger.Infof, p.Logger.Debugf itd.
    p.Logger = h

    // Uruchom wykonywanie wtyczki.
    // Blokuje do momentu otrzymania żądania zakończenia od agenta.
    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) {
    // Wtyczka może używać różnej logiki zbierania danych w zależności od parametru `key`.  
    // Ta implementacja jedynie sprawdza, czy podany `key` jest obsługiwany. 
    if key != "myip" {
        return nil, errs.Errorf("unknown item key %q", key)
    }

    // Log zostanie przekazany do logu agenta 2.
    p.Infof(
        "received request to handle %q key with %d parameters",
        key,
        len(params),
    )

    // Zbierz dane i zwróć je.

    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
}