Stwórz wtyczkę (samouczek)

To jest samouczek, który krok po kroku pokaże jak utworzyć prostą ładowalną wtyczkę dla agenta Zabbix 2.

Możesz też używać naszego przykładowego repozytorium jako szablonu lub przewodnika do tworzenia własnych wtyczek.

Co stworzysz

Ten samouczek pokazuje, jak utworzyć nową wtyczkę MyIP. Wtyczka zaimplementuje pojedynczy klucz pozycji, myip, który zwraca zewnętrzny adres IP hosta, na którym uruchomiony jest agent Zabbix 2.

Krok 1: Konfiguracja

  1. Plugin jest standardowym modułem Go. Zacznij od zainicjowania pliku go.mod w katalogu pluginu, aby śledzić zależności pluginu:
cd path/to/plugins/myip # Przełącz się do katalogu swojego pluginu
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 repository w odpowiedniej gałęzi wydania. Na przykład:

go get golang.zabbix.com/sdk@af85407

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

W razie potrzeby można zainstalować dodatkowe zależności za pomocą go get.

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

Teraz wstępna konfiguracja jest zakończona, a plugin jest gotowy do dalszego rozwoju.

Krok 2: Struktura pluginu

Moduł golang.zabbix.com/sdk, zainstalowany w poprzednim kroku, zapewnia niezbędne ramy 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 poniższy 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ę, która implementuje interfejsy z pakietu golang.zabbix.com/sdk/plugin:

  • Accessor - definiuje podstawowe metody, które wszystkie pluginy muszą implementować, takie jak ustawianie nazwy pluginu i obsługa limitów czasu dla 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 zaimplementować te interfejsy samodzielnie albo użyć domyślnych, dostarczonych przez Zabbix Go SDK, modyfikując je w razie potrzeby. Ten samouczek korzysta z 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 interfejs Accessor. Metoda implementująca jeden z funkcjonalnych interfejsów pluginu, Exporter, zostanie dodana później w samouczku.

Krok 3: Zdefiniuj klucze pozycji

Twój plugin potrzebuje kluczy pozycji, aby zbierać dane i przekazywać je do serwer lub proxy Zabbix.

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

    // Zarejestruj klucz pozycji `myip`.
    err := plugin.RegisterMetrics(
        p,
        "MyIP",                           // Nazwa pluginu
        "myip",                           // Nazwa klucza pozycji
        "Zwraca adres IP hosta.",         // Opis klucza pozycji
    )
    if err != nil {
        return errs.Wrap(err, "nie udało się zarejestrować metryk")
    }

    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", "Opis metryki pierwszej.", "metric.two", "Opis metryki drugiej.")

Krok 4: Skonfiguruj handler

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

  1. Zaimportuj pakiet container:
import "golang.zabbix.com/sdk/plugin/container"
  1. Wewnątrz funkcji run() dodaj kod, aby utworzyć i skonfigurować handler:
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 handler.
    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 wykonanie 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
}

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 dla klucza pozycji.
) (any, error)
  1. Zaimportuj wymagane pakiety do obsługi żą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) {
    // Wtyczka może używać różnych metod 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óć wynik.

    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

To powinno utworzyć plik wykonywalny myip w bieżącym katalogu.

  1. Skonfiguruj agent Zabbix 2, 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 myip utworzonego w kroku 5.

Nazwa pluginu w nazwie parametru konfiguracji (MyIP w tym samouczku) musi być zgodna z nazwą pluginu zdefiniowaną w funkcji plugin.RegisterMetrics().

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

Wynik powinien zawierać zewnętrzny adres IP twojego hosta i wyglądać podobnie do tego:

myip                                          [s|192.0.2.1]

W ten sposób utworzyłeś prosty, ładowalny plugin dla agenta Zabbix 2. Gratulacje!

Kompletny 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 elementu `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, "nie udało się zarejestrować metryk")
}

// Utwórz nowy program obsługi.
h, err := container.NewHandler("MyIP") // Nazwa wtyczki
if err != nil {
return errs.Wrap(err, "nie udało się utworzyć nowego programu obsługi")
}

// Skonfiguruj rejestrowanie, aby przekazywać logi z wtyczki do agenta.
// Dostępne za pośrednictwem p.Logger.Infof, p.Logger.Debugf itp.
p.Logger = h

// Rozpocznij wykonywanie wtyczki.
// Blokuje do momentu otrzymania żądania zakończenia od agenta.
err = h.Execute()
if err != nil {
return errs.Wrap(err, "nie udało się wykonać obsługi wtyczki")
}

return nil
}

func (p *myIP) Export(
key string, params []string, context plugin.ContextProvider,
) (any, error) {
// Wtyczka może używać innej logiki zbierania danych w oparciu o parametr `key`. 
// Ta implementacja weryfikuje tylko, czy podany `key` jest obsługiwany. 
if key != "myip" {
return nil, errs.Errorf("nieznany klucz elementu %q", key)
}

// Dziennik zostanie przesłany do dziennika agenta 2.
p.Infof(
"otrzymano żądanie obsługi klucza %q z %d parametrami",
key,
len(params),
)

// Zbierz dane i zwróć je.

resp, err := http.Get("https://api.ipify.org")
if err != nil {
return nil, errs.Wrap(err, "nie udało się pobrać adresu IP")
}

defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, errs.Wrap(err, "nie udało się odczytać treści odpowiedzi")
}

return string(body), nil
}