zabbix3.2のLoadable modulesをGoで開発できる環境を作る

Zabbixでは、自作したモジュールをZabbix ServerやZabbix Agentからロードすることが可能な仕様になっています。
そこで、ここでは `Go` を使ってロード可能モジュール(Loadable modules)を作ってみます。
Zabbix3.2のモジュールを作った時に少しハマったのでメモしておきます :-)

# 環境

| 項目   | バージョン |
|:-------|:-----------|
| OS     | CentOS 7.2 |
| Zabbix | 3.2        |
| Go     | 1.8.1      |

# GoのZabbixモジュールアダプタ

## g2z

Zabbixのネイティブモジュールが作成できるGoバイディングである `g2z` を使用します。

https://github.com/cavaliercoder/g2z

# ドキュメント

https://godoc.org/github.com/cavaliercoder/g2z

# インストール

## パッケージインストール

(1) 必要なパッケージをインストールします。

```bash
[root@localhost ~]# yum -y install git gcc
```

## g2zインストール

(1) `g2z` をインストールします。Zabbixは3.2を使うのでバージョン3を指定しています。

```bash
[root@localhost ~]# go get -v gopkg.in/cavaliercoder/g2z.v3
gopkg.in/cavaliercoder/g2z.v3
# gopkg.in/cavaliercoder/g2z.v3
In file included from $WORK/gopkg.in/cavaliercoder/g2z.v3/_obj/_cgo_export.c:3:0:
go/src/gopkg.in/cavaliercoder/g2z.v3/module.go: 関数 ‘append_metric’ 内:
go/src/gopkg.in/cavaliercoder/g2z.v3/module.go:44:2: 警告: 組み込み関数 ‘memcpy’ の互換性がない暗黙的な宣言です [デフォルトで有効]
  memcpy(list, n, sizeof(ZBX_METRIC));
  ^
# gopkg.in/cavaliercoder/g2z.v3
go/src/gopkg.in/cavaliercoder/g2z.v3/module.go: 関数 ‘append_metric’ 内:
go/src/gopkg.in/cavaliercoder/g2z.v3/module.go:44:2: 警告: 組み込み関数 ‘memcpy’ の互換性がない暗黙的な宣言です [デフォルトで有効]
  memcpy(list, n, sizeof(ZBX_METRIC));
  ^
```

# g2zの修正

## 修正しなくてはいけない理由

Zabbix 3.2から `ZBX_MODULE_API_VERSION_ONE` から `ZBX_MODULE_API_VERSION` に変更されています。
そのため、g2zをインストールしてモジュールをビルドしZabbixへ組み込んだあとに起動すると以下のエラーが発生します。

```
5603:20170529:003940.573 using configuration file: /etc/zabbix/zabbix_agentd.conf
5603:20170529:003940.577 unsupported module "example.so" version: 1 <- サポートしていないバージョンなので起動できない
5603:20170529:003940.577 loading modules failed, exiting...
```

Zabbixのソースコードを見てみましょう。

```c
[root@localhost SOURCES]# grep -n 'ZBX_MODULE_API_VERSION' ./zabbix-3.2.6/include/module.h
29:#define ZBX_MODULE_API_VERSION	2 <- バージョンが 2 になっていることが確認できます
32:#define ZBX_MODULE_API_VERSION_ONE	ZBX_MODULE_API_VERSION <- 古いのは新しいので上書きされています
35:/* #if ZBX_MODULE_API_VERSION == X                                                     */
```

3.2以上でロード可能モジュールを開発する場合は、g2zでもこの修正が必要になります。

## 修正手順

(1) g2zの `module.h` の `ZBX_MODULE_API_VERSION` を `2` に修正します。

```bash
[root@localhost ~]# vi go/src/gopkg.in/cavaliercoder/g2z.v3/module.h
(snip)
#define ZBX_MODULE_API_VERSION_ONE      2
```

これが、一番簡単な修正方法かと思います。
または `module.h` に `ZBX_MODULE_API_VERSION` を新しく追加して `go/src/gopkg.in/cavaliercoder/g2z.v3/module.go` の `zbx_module_api_version` 関数内を修正しても大丈夫だと思います。

```go
//export zbx_module_api_version
func zbx_module_api_version() int {
        return C.ZBX_MODULE_API_VERSION_ONE <- ここ
}
```

ちなみに、本家にはプルリクがきている模様

https://github.com/cavaliercoder/g2z/pull/8

# モジュールのビルド及び組み込み

ここでは、g2zの例にあるソースをビルドしてZabbix Agentへ組み込んでみます。

(1) 以下のソースコードを使用します。

https://godoc.org/github.com/cavaliercoder/g2z#example-RegisterStringItem

```go
package main

import (
    g2z "gopkg.in/cavaliercoder/g2z.v3"
    "strings"
)

func main() {
    panic("THIS_SHOULD_NEVER_HAPPEN")
}

func init() {
    g2z.RegisterStringItem("go.echo", "Hello,world", Echo)
}

func Echo(request *g2z.AgentRequest) (string, error) {
    return strings.Join(request.Params, " "), nil
}
```

(2) ビルドします。

```bash
[root@localhost ~]# go build -buildmode=c-shared -o example.so example.go
# gopkg.in/cavaliercoder/g2z.v3
In file included from $WORK/gopkg.in/cavaliercoder/g2z.v3/_obj/_cgo_export.c:3:0:
go/src/gopkg.in/cavaliercoder/g2z.v3/module.go: 関数 ‘append_metric’ 内:
go/src/gopkg.in/cavaliercoder/g2z.v3/module.go:44:2: 警告: 組み込み関数 ‘memcpy’ の互換性がない暗黙的な宣言です [デフォルトで有効]
  memcpy(list, n, sizeof(ZBX_METRIC));
  ^
# gopkg.in/cavaliercoder/g2z.v3
go/src/gopkg.in/cavaliercoder/g2z.v3/module.go: 関数 ‘append_metric’ 内:
go/src/gopkg.in/cavaliercoder/g2z.v3/module.go:44:2: 警告: 組み込み関数 ‘memcpy’ の互換性がない暗黙的な宣言です [デフォルトで有効]
  memcpy(list, n, sizeof(ZBX_METRIC));
  ^
```

(3) モジュールを任意の場所へ保存します。ここでは `/etc/zabbix/modules` 配下へ保存します。

```bash
[root@localhost ~]# mkdir /etc/zabbix/modules
[root@localhost ~]# mv example.so /etc/zabbix/modules/

```

(4) Zabbix Agentへ組み込みます。

```bash
[root@localhost ~]# vi /etc/zabbix/zabbix_agentd.conf
(snip)
### Option: LoadModulePath
#       Full path to location of agent modules.
#       Default depends on compilation options.
#
# Mandatory: no
# Default:
# LoadModulePath=${libdir}/modules
LoadModulePath=/etc/zabbix/modules

### Option: LoadModule
#       Module to load at agent startup. Modules are used to extend functionality of the agent.
#       Format: LoadModule=<module.so>
#       The modules must be located in directory specified by LoadModulePath.
#       It is allowed to include multiple LoadModule parameters.
#
# Mandatory: no
# Default:
# LoadModule=
LoadModule=example.so
```

(5) Zabbix Agentを起動します。

```bash
[root@localhost ~]# systemctl start zabbix-agent
[root@localhost ~]# systemctl status zabbix-agent
● zabbix-agent.service - Zabbix Agent
   Loaded: loaded (/usr/lib/systemd/system/zabbix-agent.service; disabled; vendor preset: disabled)
   Active: active (running) since 月 2017-05-29 00:50:05 JST; 1min 59s ago
(snip)
```

# 動作確認

(1) 動作確認をしてみます。

```bash
[root@localhost ~]# zabbix_get -s 127.0.0.1 -k 'go.echo['test', 'hoge']'
test hoge
```

(2) Discoveryのソースコード例を組み込んで実行してみました。

```bash
[root@localhost ~]# zabbix_get -s 127.0.0.1 -k 'go.discovery'
{
	"data":[
	{
		"{#INDEX}":"0"},
	{
		"{#INDEX}":"1"},
	{
		"{#INDEX}":"2"},
	{
		"{#INDEX}":"3"},
	{
		"{#INDEX}":"4"}]}
```

これで、3.2のモジュールが開発できるようになりました :-)
(独り言)3.4はいつでるんやろ...

# 参考文献

http://qiita.com/ike_dai/items/b4e92a6403b7e1467d2d

# 追記

Goルーチンの処理が入る場合は動かないようです。
https://github.com/cavaliercoder/g2z/issues/5
実際にやってみたら動かなかった(´･_･`)