Golangを使ってZabbixを拡張

今年のZabbix Advent Calendarもあと少し。
20日目はZabbixの機能を拡張するためにGolangを使ってみようというお話です。

実は個人的に今年はGo言語を使って何かツールとか作ってみようと目標を立て、1月から少しずつ勉強して触ってました。
一応、簡単ではありますが、こんな感じのものを公開中です。

* [zaws (ZabbixによるAWS監視ツール)](https://github.com/ike-dai/zaws)
* [Zabbix Anomaly Detector plugin(Zabbix監視結果を用いた変化点スコア監視ツール)](https://github.com/ike-dai/zabbix_anomaly)
* [Golang JobScheduler API library(OSSのジョブ管理ツールJobSchedulerのAPIライブラリ)](https://github.com/ike-dai/go-jobscheduler)

コードは公開していないですが、他にもElasticsearchと連携するGolang製のツール作ったり、GoでLINE Botと連携するツール作ったりちょこちょこ作れた気がします。

このようにGo言語はバックエンドで色々と活用するツールをサクッと作るのにとても便利だなと思っています。特にいろんな環境に配布する際に、1バイナリだけ展開すれば良かったり何かと都合が良いです。

参考）[インフラ運用管理ツールとGolang](http://www.slideshare.net/ikedai/golang-osslt)

ということで、前置きが長くなりましたが、Zabbixと連携してZabbixを更によくするためにGolangをどのように使えるかを紹介します。
今回紹介する方法は以下の3つ。

* ***GolangでZabbix APIを操作***
* ***GolangでZabbix Senderプロトコルを話してヒストリ登録***
* ***GolangでLoadable moduleを開発して拡張***

これぐらいのことができればかなりいろんな拡張対応が可能になります。

# GolangでZabbix APIを操作

Zabbixには、Zabbixの設定を行ったり、監視結果を取り出すためにAPIが実装されています。JSON-RPC形式のWebAPIです。このAPIをGolangのプログラムから操作してみます。
自分でHTTPのリクエストを送る処理をプログラムで書いても良いですが、すでに便利なライブラリが提供されています。

Zabbix社公式のものというのは今のところないですが、コミュニティベースで様々なものが公開されています。

* [https://github.com/AlekSi/zabbix](https://github.com/AlekSi/zabbix)
* [https://github.com/rday/zabbix](https://github.com/rday/zabbix)

上記の1つ目のAPIライブラリの方が実装されている機能も多く、ユーザも多くついているようなので本記事ではこちらのライブラリを使って操作方法を紹介します。

早速ですが、ライブラリを使ったソースコードの例は以下になります。

```go:zabbix-event.go
package main

import (
	"fmt"
	"github.com/AlekSi/zabbix"
	"strconv"
	"time"
)

func main() {
	url := "http://Zabbix管理画面ホスト名orIP/zabbix/api_jsonrpc.php"
	username := "Admin"
	password := "zabbix"

	from_time := time.Now().Unix() - 600
	event_count := GetZabbixEventCount(url, username, password, from_time)
	fmt.Printf("Number of events occurred in the last 10 min: %d\n", event_count)
}

// 直近xx分間に発生したイベントの数をAPI経由で取得する関数
func GetZabbixEventCount(url, username, password string, from_unixtime int64) (count int) {
	count = 0
	api := zabbix.NewAPI(url)
	_, _ = api.Login(username, password)
	response, _ := api.Call("event.get", zabbix.Params{"time_from": strconv.FormatInt(from_unixtime, 10), "output": "extend"})
	count = len(response.Result.([]interface{}))
	return count
}
```
このコードを動かすには以下のようにライブラリをまずは取得しておく必要があります。

```shell-session
$ go get github.com/AlekSi/zabbix
```

この状態で実行すると以下のように直近10分に発生したイベント数をZabbixAPI経由で取得して表示します。

```shell-session
$ go run zabbix-event.go
Number of events occurred in the last 10 min: 2
```

* .Loginで認証トークンを発行
* .Call(メソッド名, zabbix.Params)でZabbix API処理実行
* .Resultで値取り出し

この一連の流れでどのようなZabbixAPIでも対応可能です。
Host情報の取得やItem情報の取得などよく使うようなものはあらかじめメソッドとしてライブラリ側で実装されているので直接Call関数を呼び出さなくても良いようになっています。
対応している関数は[こちら](https://godoc.org/github.com/AlekSi/zabbix)を参照してください。

# GolangでZabbix Senderプロトコルを話してヒストリ登録

次に、Zabbix SenderをGoのプログラムから実行する方法です。Zabbix Senderは[こちら](https://www.zabbix.com/documentation/3.0/manual/concepts/sender)のドキュメントにも記載の通り、Zabbixに対して外から監視結果をpush型(Active型)で登録できる機能です。
一括で監視結果を登録したりするのに非常に便利です。

参考) [Zabbix Senderで複数の値を一括登録](http://d.hatena.ne.jp/ike-dai/20120622/1340360976)

通常、Zabbix Senderは上記の参考ページにもある通り、Zabbix社から提供されているユーティリティツールのzabbix_senderコマンドを使って処理を行うのですが、ツールを使わずに直接Zabbix SenderのプロトコルでTCP通信させることでも同じ挙動が実現できます。

プロトコルについては[このあたり](https://www.zabbix.org/wiki/Docs/protocols/zabbix_sender/2.0)に少し解説があります。

プログラムから呼び出すのであれば、zabbix_senderコマンドを都度実行するより、TCP通信を直接実装している方がオーバーヘッドも少なく効率よく処理可能です。
ここで紹介するのは、Golangでzabbix_senderプロトコルを話せる実装がされているライブラリです。

ZabbixAPI用のライブラリで紹介したものと同じAleksiさん作のライブラリです。

* [https://github.com/AlekSi/zabbix-sender](https://github.com/AlekSi/zabbix-sender)

使い方は以下です。

```go:zabbix-sender-sample.go
package main

import (
	"fmt"
	"github.com/AlekSi/zabbix-sender"
	"net"
)

func main() {
	data := map[string]interface{}{"go.test.trapper": 111}
	di := zabbix_sender.MakeDataItems(data, "demo-1")
	di = append(di, zabbix_sender.DataItem{Hostname: "demo-1", Key: "go.test.trapper", Value: "100"})
	di = append(di, zabbix_sender.DataItem{Hostname: "demo-1", Key: "go.test.trapper", Value: "89"})
	addr, _ := net.ResolveTCPAddr("tcp", "ZabbixServerのホスト名:10051")
	res, _ := zabbix_sender.Send(addr, di)
	fmt.Print(res)
}
```

```shell-session
$ go get github.com/AlekSi/zabbix-sender
```

実行すると以下のような感じで出力されます。

```
$ go run zabbix-sender-sample.go
&{success processed: 3; failed: 0; total: 3; seconds spent: 0.000064 3 0}
```

ここでは、demo-1というホストのgo.test.trapperというキーのアイテムに111,100,89の3つの監視結果をsenderで送付するサンプルコードとなっています。Zabbixに登録された監視結果をみると以下のように正しく登録されているのがわかります。

![golang-sender.png](https://qiita-image-store.s3.amazonaws.com/0/45851/9fc24e56-633f-9a99-3fb5-4b89837f9f4d.png)

[zaws](https://github.com/ike-dai/zaws)とか[Zabbix Anomaly Detector plugin](https://github.com/ike-dai/zabbix_anomaly)の中でも使っているので参考に御覧ください。

# GolangでLoadable moduleを開発して拡張

最後はLoadable moduleを用いた拡張の方法です。Loadable moduleとはZabbix ServerもしくはZabbix Agentの中に機能を追加実装するための機能です。
C言語で実装すればどんな処理を行う独自アイテムキーを実装できるというなかなか活用し甲斐のある機能です。
C言語で実装し、共有ライブラリを作成します。zabbix_server.confやzabbix_agentd.confの中で読み込む対象の.soファイルを指定して起動することで拡張されます。
細かい例としては以下の公式のドキュメントに紹介されているので参考にしてください。

https://www.zabbix.com/documentation/2.2/jp/manual/config/items/loadablemodules

通常カスタム監視をしたければZabbix AgentのUserParameterでコマンド実行した結果を監視するとか、Zabbix Serverから外部チェックスクリプトを実行した結果を関しするといったことが一般的ですが、これらの方法だと、性能的にあまりよくないです。そこでLoadable moduleで内部に処理を組み込んで実装することで有効に働きます。

サンプルとして以前C言語で実装したモジュールはこんな具合になります。

* [Zabbix Cloud module](https://github.com/ike-dai/zabbix-cloud-module)

個人的なスキルの問題もありますが、C言語で1から実装するとなると結構時間もかかって苦しいところがあります。Go言語とか別の言語でも実装できたらなーと思っていたところ、今年ラトビアで開催されたZabbixConferenceにてGo言語で実装したものをLoadable module化できるツールの情報がありました。
それがこちら

* [g2z (Zabbix module adapter for go)](https://github.com/cavaliercoder/g2z)

Go言語でLoadable moduleを実装できるというものです。
今回はこの使い方を紹介します。

このアダプタは、***Goのver.1.5で実装されたshared library機能***を使っているため、Go1.5以上が必須です。

単純にアイテムキーの引数で渡された文字列を返すだけのecho監視アイテムを実装するには以下のように書きます。
Zabbix3系向けのサンプルコードです。

```go:go-echo.go
package main

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

// mandatory library entry point, although it is never called.
func main() {
    panic("THIS_SHOULD_NEVER_HAPPEN")
}

// mandatory initialization function
func init() {
    g2z.RegisterStringItem("go.echo", "Hello world!", Echo)
}

// handler for 'go.echo' item
func Echo(request *g2z.AgentRequest) (string, error) {
    return strings.Join(request.Params, " "), nil
}
```
※[ここ](https://godoc.org/github.com/cavaliercoder/g2z)を参考に一部修正

重要なのは、init関数の中で、アイテムキー名に対してどの関数を呼び出すかの指定を定義している点です。
ここでは、文字列を返す***go.echo***というアイテムキーを定義し、そのアイテムキーの呼び出し時にはEchoという関数を処理するという記載になっています。

実際の処理についてはEcho関数の中で記載します。

アイテムキーの引数として渡された内容については[AgentRequest型](https://godoc.org/github.com/cavaliercoder/g2z#AgentRequest)で引き渡ってきます。
Echo関数の中ではAgentRequestの中のParamsに値が格納されています。
この例ではアイテムキーで渡されてきた複数の引数をスペース区切りで連結して文字列して返すというだけになっています。
ここにGoで自由に処理内容を記載すればいろんな拡張が可能になるという仕組みです。

このプログラムを実際にLoadable moduleとして組み込むには以下のように行います。

## 組み込み方
### ① goプログラムコードのbuild(共有ライブラリ作成)

まずはビルド処理です。以下のようにbuildmodeをc-sharedに設定しビルドします。
この時、GOOSやGOARCHの指定はLoadable moduleを組み込む先のOS環境にあわせて指定します。
例えばZabbix AgentがLinuxの64bitOS上で稼働しているなら以下のようにbuildします。

buildする前にg2zを取得しておきます。

```shell-session
$ go get gopkg.in/cavaliercoder/g2z.v3
```

この状態でbuild。

```shell-session
$ GOOS=linux GOARCH=amd64 go build -buildmode=c-shared -o go-echo.so go-echo.go
```
これでgo-echo.soファイルが生成されます。

### ② 共有ライブラリをZabbix Server or Zabbix Agent上に配置

次にこの.soファイルを組み込みたい先のサーバに配置します。
配置する先は、zabbix_server.confやzabbix_agentd.confの***LoadModulePath***のディレクトリです。

### ③ 共有ライブラリを読み込む設定

読み込んで有効化するには以下のように設定ファイル(zabbix_server.conf/zabbix_agentd.conf)を記載します。

```
LoadModulePath=/tmp/zabbix-modules
LoadModule=go-echo.so
```

### ④ Zabbix Server or Zabbix Agent再起動

あとは再起動すればOKです。
うまく読み込まれているとログに以下のような出力があるはずです。

```shell-session:zabbix_agentd.log
  9412:20161216:124214.661 Starting Zabbix Agent [Zabbix server]. Zabbix 3.0.0 (revision 58460).
  9412:20161216:124214.661 **** Enabled features ****
  9412:20161216:124214.661 IPv6 support:          YES
  9412:20161216:124214.661 TLS support:           YES
  9412:20161216:124214.661 **************************
  9412:20161216:124214.661 using configuration file: /etc/zabbix/zabbix_agentd.conf
  9412:20161216:124214.662 loaded modules: go-echo.so
```

### ⑤ 動作確認

Agentに組み込んだ場合には、zabbix_getとかを使って動作を確認してみると良いでしょう。

```shell-session
$ zabbix_get -s Agentのホスト名orIP -k go.echo["I","am","ike-dai."]
I am ike-dai.
```

# まとめ

いかがでしたでしょうか。ちょっと長い記事になってしまいましたが、Zabbixをちょっと拡張するにも色々な方法があり、かつGolangでもかんたんに実装できるようになっています。
拡張はあくまで必要に応じてであることはお忘れなく。***Zabbixを普通に使う分には決してこんなにややこしい拡張はしなくても十分使えます。***

よりいろんな情報を統合的にZabbixで監視したいといった場合には今回紹介したような拡張の仕方を参考に検討されてみてはいかがでしょうか？

明日は[fripper1214](http://qiita.com/fripper1214)さんです。よろしくお願いしますー