创建插件(教程)

这是一个分步教程,指导如何为 Zabbix agent 2 创建一个简单的可加载插件。

您也可以使用 示例仓库 作为模板或指南,来创建您自己的插件。

您将创建的内容

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

步骤 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 - 定义了所有插件必须实现的基本方法,例如设置插件名称和处理监控项键超时。
  • 以下功能插件接口之一或多个:
    • Exporter - 执行轮询并返回一个值(或多个值)、无值或错误;通常与 Collector 接口一起使用。
    • Collector - 管理数据的周期性收集。
    • Runner - 定义插件启动和关闭的程序。
    • Watcher - 允许实现独立的指标轮询,绕过 agent 内部的调度器;对于基于陷阱或事件驱动的监控很有用。
    • Configurator - 定义插件如何读取和应用其配置设置。

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

  1. 创建插件结构体。

现在,导入 plugin 包并创建一个 myIP 结构体,该结构体嵌入了 plugin.Base 结构体:

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{}
       
           // 注册 `myip` 监控项键值。
           err := plugin.RegisterMetrics(
               p,
               "MyIP",                           // 插件名称
               "myip",                           // 监控项键值名称
               "返回主机的 IP 地址。", // 监控项键值描述
           )
           if err != nil {
               return errs.Wrap(err, "注册监控项失败")
           }
       
           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() 函数内部添加代码以创建并设置处理器:
func run() error {
        p := &myIP{}
       
        // 注册 `myip` 监控项键。
        err := plugin.RegisterMetrics(
         p,
         ""MyIP"",                           // 插件名称
         ""myip"",                           // 监控项键名称
         ""返回主机的 IP 地址。"", // 监控项键描述
        )
        if err != nil {
         return errs.Wrap(err, ""注册监控项失败"")
        }
       
        // 创建一个新的处理器。
        h, err := container.NewHandler(""MyIP"") // 插件名称
        if err != nil {
         return errs.Wrap(err, ""创建新处理器失败"")
        }
       
        // 设置日志记录,将插件的日志转发给 agent 。
        // 可通过 p.Logger.Infof, p.Logger.Debugf 等访问。
        p.Logger = h
       
        // 启动插件执行。
        // 阻塞直到从 agent 接收到终止请求。
        err = h.Execute()
        if err != nil {
         return errs.Wrap(err, ""执行插件处理器失败"")
        }
       
        return nil
       }
       ```"
       
       
       ### 步骤 5: 实现数据收集
       
       数据收集通过导出器接口完成,该接口描述了 `Export` 方法:
       
       ```go
       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("未知监控项键 %q", key)
           }
       
           // 日志将转发到 agent 2 日志。
           p.Infof(
               "收到处理 %q 键和 %d 个参数的请求",
               key,
               len(params),
           )
       
           // 收集数据并返回。
       
           resp, err := http.Get("https://api.ipify.org")
           if err != nil {
               return nil, errs.Wrap(err, "获取 IP 地址失败")
           }
       
           defer resp.Body.Close()
       
           body, err := io.ReadAll(resp.Body)
           if err != nil {
               return nil, errs.Wrap(err, "读取响应体失败")
           }
       
           return string(body), nil
       }

步骤 6: 构建并配置插件

  1. 要构建插件,请运行:
go mod tidy
       go build

这将在当前目录中创建一个可执行文件 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 监控项,请运行:
zabbix_agent2 -c /etc/zabbix_agent2.conf -t myip

输出应包含主机的外部 IP 地址,看起来类似于以下内容:

myip                                          [s|192.0.2.0]

至此,您已为 Zabbix agent 2 创建了一个简单的可加载插件。 恭喜!

"### 完整源代码

```go 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, ""注册指标失败"") }

// 创建一个新的处理器。 h, err := container.NewHandler(""MyIP"") // 插件名称 if err != nil { return errs.Wrap(err, ""创建新处理器失败"") }

// 设置日志记录,将插件的日志转发到 agent 。 // 可通过 p.Logger.Infof, p.Logger.Debugf 等访问。 p.Logger = h

// 启动插件执行。 // 阻塞直到收到 agent 的终止请求。 err = h.Execute() if err != nil { return errs.Wrap(err, ""执行插件处理器失败"") }

return nil }

func (p *myIP) Export( key string, params []string, context plugin.ContextProvider, ) (any, error) { // 插件可以基于 key 参数使用不同的数据收集逻辑。 // 此实现仅验证提供的 key 是否受支持。 if key != ""myip"" { return nil, errs.Errorf(""未知监控项键 %q"", key) }

// 日志将转发到 agent 的日志。 p.Infof( ""收到处理 %q 键的请求,参数数量为 %d"", key, len(params), )

// 收集数据并返回。

resp, err := http.Get(""https://api.ipify.org"") if err != nil { return nil, errs.Wrap(err, ""获取IP地址失败"") }

defer resp.Body.Close()

body, err := io.ReadAll(resp.Body) if err != nil { return nil, errs.Wrap(err, ""读取响应体失败"") }

return string(body), nil } ```"