プラグインの作成(チュートリアル)

これは、Zabbixエージェント2用のシンプルでロード可能なプラグインを作成するためのステップバイステップのチュートリアルです。

独自のプラグインを作成するためのテンプレートまたはガイドとして、サンプルリポジトリを使用することもできます。

作成するもの

このチュートリアルでは、新しいロード可能なプラグインMyIPを作成する方法を説明します。 このプラグインは、Zabbixエージェント2が動作しているホストの外部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エージェント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",                           // アイテムキー名
               "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
       }

ステップ5: データ収集の実装

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

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)
           }
       
           // ログはエージェント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",                           // アイテムキー名
               "Returns the host's IP address.", // アイテムキーの説明
           )
           if err != nil {
               return errs.Wrap(err, "failed to register metrics")
           }
       
           // 新しいハンドラーを作成します。
           h, err := container.NewHandler("MyIP") // Plugin name
           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
       }