创建插件(教程)

本教程将逐步指导您如何为Zabbix agent 2create一个简单的可加载插件.

您也可以将example repository作为模板或指南来创建自己的插件.

你将创建的内容

本教程演示如何create一个新的可加载插件MyIP。 该插件将实现一个单一的监控项键值myip,用于返回运行Zabbix agent 2的主机的外部IP地址。

步骤 1:设置

  1. 插件是一个标准的Go模块。 首先在插件目录中初始化go.mod file以跟踪插件依赖项:
cd path/to/plugins/myip # Switch to your plugin directory
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 repository在相应发布分支中的最新HEAD提交哈希。 例如:

go get golang.zabbix.com/sdk@af85407

请注意,目前不支持golang.zabbix.com/sdk版本控制,但这可能会在未来改变。

可以使用go get根据需要安装其他依赖项。

  1. 为插件源代码创建一个空的main.go file:
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 - 定义所有插件必须实现的基本方法,例如设置插件名称和处理监控项键超时
  • 以下功能插件接口中的一个或多个:
    • Exporter - 执行轮询并返回值(或多个值)、空值或错误;常与Collector接口配合使用
    • Collector - 管理数据的定期收集
    • Runner - 定义插件启动和关闭流程
    • Watcher - 允许实现独立的指标轮询,绕过agent的内部调度器;适用于基于陷阱或事件驱动的监控
    • Configurator - 定义插件如何读取和应用其配置设置

您可以自行实现这些接口,或使用Zabbix Go SDK提供的默认实现并按需修改。 本教程使用默认实现。

  1. 创建插件结构体

现在,importplugin包并create嵌入plugin.Base结构体的myIP结构体:

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

type myIP struct {
    plugin.Base
}

当前myIP结构体满足Accessor接口要求。 本教程后续将添加实现功能插件接口之一Exporter的方法。

步骤 3:定义监控项键值

您的插件需要监控项密钥来收集数据并提供给Zabbix server或proxy.

  1. 导入errs包用于错误处理:
import "golang.zabbix.com/sdk/errs"
  1. run()函数中使用plugin.RegisterMetrics()函数注册监控项密钥:
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
}

要注册多个监控项密钥,请为每个指标重复参数metric namedescription. 例如:

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

步骤 4:设置处理器

该处理器用于促进agent与插件之间的通信.

  1. 导入container包:
import "golang.zabbix.com/sdk/plugin/container"
  1. run()函数内部添加代码以create并设置处理器:
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,             // The item key to collect.
  params []string,        // Arguments passed to the item key (`myip[arg1, arg2]`).
  context ContextProvider // Metadata about the item key data collection.
) (any, error)
  1. 导入HTTP请求和响应读取所需的包:
    "io"
    "net/http"
)
  1. myIP结构体实现Export方法:
func (p *myIP) Export(
    key string, params []string, context plugin.ContextProvider,
) (any, error) {
    // The plugin can use different data collection logic based on the `key` parameter.
    // This implementation only verifies that the provided `key` is supported.
    if key != "myip" {
        return nil, errs.Errorf("unknown item key %q", key)
    }

    // The log will get forwarded to the agent 2 log.
    p.Infof(
        "received request to handle %q key with %d parameters",
        key,
        len(params),
    )

    // Collect the data and return it.

    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. 构建插件时,run:
go mod tidy
go build

这将create在当前目录生成可执行文件myip

  1. 配置Zabbix agent 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 监控项,run:
zabbix_agent2 -c /etc/zabbix_agent2.conf -t myip

输出应包含您的主机的外部IP地址,类似如下:

myip                                          [s|192.0.2.0]

至此,您已为Zabbix agent 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{}

    // 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 from the agent is received.
    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) {
    // The plugin can use different data collection logic based on the `key` parameter.  
    // This implementation only verifies that the provided `key` is supported. 
    if key != "myip" {
        return nil, errs.Errorf("unknown item key %q", key)
    }

    // The log will get forwarded to the agent 2 log.
    p.Infof(
        "received request to handle %q key with %d parameters",
        key,
        len(params),
    )

    // Collect the data and return it.

    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
}