创建插件(教程)

这是一个分步教程,介绍如何为 Zabbix agent 2 create 一个简单的可加载插件。

您还可以使用 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

2。安装必需的依赖项 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 安装其他依赖项。

3。为插件源代码创建一个空的 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"

2。使用plugin.RegisterMetrics()函数在run()函数中注册监控项键:

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"

2。在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请求和响应读取所需的包:

import ( "io" "net/http" ) 2. 为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
       }