[comment]: # ({f238de62-e03ef403})
# 创建插件（教程）

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

您也可以将[example repository](https://git.zabbix.com/projects/AP/repos/example/browse)作为模板或指南来创建自己的插件.

[comment]: # ({/f238de62-e03ef403})

[comment]: # ({4aa59460-3b9b2b36})
### 你将创建的内容

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

[comment]: # ({/4aa59460-3b9b2b36})

[comment]: # ({bf3f1fff-47915903})
### 步骤 1：设置

1. 插件是一个标准的Go模块。
首先在插件目录中初始化`go.mod` file以跟踪插件依赖项：

```sh
cd path/to/plugins/myip # Switch to your plugin directory
go mod init myip
```
2. 安装必需的依赖项Zabbix Go SDK（`golang.zabbix.com/sdk`）：

```sh
go get golang.zabbix.com/sdk@$LATEST_COMMIT_HASH
```
将`$LATEST_COMMIT_HASH`替换为`golang.zabbix.com/sdk` [repository](https://git.zabbix.com/projects/AP/repos/plugin-support/commits)在相应发布分支中的最新`HEAD`提交哈希。
例如：

```sh
go get golang.zabbix.com/sdk@af85407
```
请注意，目前不支持`golang.zabbix.com/sdk`版本控制，但这可能会在未来改变。

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

3. 为插件源代码创建一个空的`main.go` file：

```sh
touch main.go
```
现在初始设置已完成，插件已准备好进行开发。

[comment]: # ({/bf3f1fff-47915903})

[comment]: # ({f8ebbdf8-a76d6ea7})
### 步骤 2：插件结构

上一步安装的`golang.zabbix.com/sdk`模块为插件开发提供了必要框架，并确保所有插件具有一致的结构。

1. 设置基本执行流程

首先定义插件的主要执行流程。将以下代码添加到`main.go`中：

```go
package main

func main() {
	err := run()
	if err != nil {
		panic(err)
	}
}

func run() error {
	return nil
}
```
这建立了插件的基本执行流程。
run函数后续将包含插件的核心逻辑。

2. 探索插件接口

Zabbix agent 2插件应通过实现`golang.zabbix.com/sdk/plugin`包中的接口的结构体来表示：

- *Accessor* - 定义所有插件必须实现的基本方法，例如设置插件名称和处理监控项键超时
- 以下功能插件接口中的一个或多个：
	- *Exporter* - 执行轮询并返回值（或多个值）、空值或错误；常与*Collector*接口配合使用
	- *Collector* - 管理数据的定期收集
	- *Runner* - 定义插件启动和关闭流程
	- *Watcher* - 允许实现独立的指标轮询，绕过agent的内部调度器；适用于基于陷阱或事件驱动的监控
	- *Configurator* - 定义插件如何读取和应用其配置设置

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

3. 创建插件结构体

现在，import*plugin*包并create嵌入`plugin.Base`结构体的`myIP`结构体：

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

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

[comment]: # ({/f8ebbdf8-a76d6ea7})

[comment]: # ({c3726cdb-4c521593})
### 步骤 3：定义监控项键值

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

1. 导入*errs*包用于错误处理:

```go
import "golang.zabbix.com/sdk/errs"
```
2. 在`run()`函数中使用`plugin.RegisterMetrics()`函数注册监控项密钥:

```go
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 name*和*description*.
例如:

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

[comment]: # ({/c3726cdb-4c521593})

[comment]: # ({8227eb99-b7f436a7})
### 步骤 4：设置处理器

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

1. 导入*container*包:

```go
import "golang.zabbix.com/sdk/plugin/container"
```
2. 在`run()`函数内部添加代码以create并设置处理器:

```go
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
}
```

[comment]: # ({/8227eb99-b7f436a7})

[comment]: # ({d6f0f4ab-c7541684})
### 步骤 5：实现数据收集

数据采集通过Exporter接口完成，该接口描述了`Export`方法：

```go
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`方法：

```go
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
}
```

[comment]: # ({/d6f0f4ab-c7541684})

[comment]: # ({c7bcafc2-f9325698})
### 第 6 步：构建并配置插件

1. 要构建插件，请运行：

```bash
go mod tidy
go build
```

这应该会在当前目录中创建一个可执行文件 `myip`。

2. 配置 Zabbix agent 2 使用该插件：

```sh
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()_ 函数中定义的插件名一致。

3. 要测试插件及其 `myip` 监控项，请运行：

```sh
zabbix_agent2 -c /etc/zabbix_agent2.conf -t myip
```

输出应包含主机的外部 IP 地址，并且看起来类似于这样：

```
myip                                          [s|192.0.2.1]
```

至此，你已经为 Zabbix agent 2 创建了一个简单的可加载插件。
恭喜！

[comment]: # ({/c7bcafc2-f9325698})

[comment]: # ({6aef1ccf-38f17bf9})
### 完整源代码

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

	// 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
}
```

[comment]: # ({/6aef1ccf-38f17bf9})

