プラグインの作成

このページは、Zabbix agent 2 用の独自プラグイン(独立したデータコレクター)の開発に関心のある方を対象としています。

カスタムプラグインを使用すると、提供されている組み込みプラグインおよびロード可能なプラグインを超えて、Zabbix agent 2 の機能を拡張できます。

各プラグインは、構造を定義し、1つまたは複数のインターフェース(ExporterConfiguratorRunner)を実装する Go パッケージです。詳細については、プラグインインターフェースおよび接続図を参照してください。

このガイドは、カスタムのロード可能プラグインを作成するのに役立ちます。

追加のガイダンスについては、以下のリポジトリも参照してください。

作成するもの

これは、MyIP という名前のシンプルなロード可能プラグインを作成するためのステップバイステップのチュートリアルです。
このプラグインは、Zabbixエージェントホストの外部IPアドレスを返す単一のアイテムキー(myip)を実装します。

ステップ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 は、適切なリリースブランチにある golang.zabbix.com/sdkリポジトリの最新の HEAD コミットハッシュに置き換えてください。
例えば次のようになります:

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 agent 2 プラグインは、golang.zabbix.com/sdk/plugin パッケージのインターフェースを実装する構造体として表現する必要があります。

  • Accessor - すべてのプラグインが実装しなければならない基本的なメソッドを定義します。たとえば、プラグイン名の設定や、アイテムキーのタイムアウト処理などです。
  • 次の機能プラグインインターフェースのうち1つ以上:
    • Exporter - ポーリングを実行し、値(または複数の値)、何も返さない、またはエラーを返します。多くの場合、Collector インターフェースとあわせて使用されます。
    • Collector - データの定期的な収集を管理します。
    • Runner - プラグインの起動および停止手順を定義します。
    • Watcher - エージェントの内部スケジューラを介さずに、独立したメトリクスポーリングを実装できます。トラップベースまたはイベント駆動型の監視に役立ちます。
    • Configurator - プラグインが設定内容を読み取り、適用する方法を定義します。

これらのインターフェースは自分で実装することも、Zabbix Go SDK が提供するデフォルト実装を必要に応じて変更して使用することもできます。
このチュートリアルでは、デフォルト実装を使用します。

  1. プラグイン構造体を作成します。

次に、plugin パッケージをインポートし、plugin.Base 構造体を埋め込む myIP 構造体を作成します。

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

type myIP struct {
    plugin.Base
}

現在、myIP 構造体は Accessor インターフェースを満たしています。
機能プラグインインターフェースの1つである Exporter を実装するためのメソッドは、このチュートリアルの後半で追加します。

ステップ3: アイテムキーを定義する

プラグインがデータを収集してZabbixサーバーまたはプロキシに提供するには、アイテムキーが必要です。

  1. エラーハンドリングのために errs パッケージをインポートします。
import "golang.zabbix.com/sdk/errs"
  1. run() 関数内で plugin.RegisterMetrics() 関数を使用してアイテムキーを登録します。
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")
    }

    return nil
}

複数のアイテムキーを登録するには、各メトリクスごとに metric namedescription のパラメータを繰り返します。
例:

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

    // `myip` アイテムキーを登録します。
    err := plugin.RegisterMetrics(
        p,
        "MyIP",                           // プラグイン名
        "myip",                           // アイテムキー名
        "ホストのIPアドレスを返します。", // アイテムキーの説明
    )
    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
}

ステップ5: データ収集を実装する

データ収集はExporterインターフェースを介して行われ、このインターフェースはExportメソッドを定義します。

func Export(
  key string,             // 収集するアイテムキー。
  params []string,        // アイテムキーに渡される引数 (`myip[arg1, arg2]`)
  context ContextProvider // アイテムキーデータ収集に関するメタデータ。
) (any, error)
  1. HTTPリクエストとレスポンス読み取りに必要なパッケージをインポートします。
import (
    "io"
    "net/http"
)
  1. myIP構造体のExportメソッドを実装します。
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 を、ステップ5で作成した myip へのパスに置き換えてください。

設定パラメータ名内のプラグイン名(このチュートリアルでは 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",                           // アイテムキー名
        "ホストのIPアドレスを返します。", // アイテムキーの説明
    )
    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(
        "%q キーを %d 個のパラメータで処理するリクエストを受信しました",
        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
}