Writing watcher Zabbix Agent2 MQTT plugin in Go

Vitaly Zhuravlev
5 min readDec 12, 2019

--

Zabbix 4.4 has brought us a wind of change
by introducing new platform for data collection. Thanks to Golang implementation and lessons learned from old zabbix agent, the new platform promises performance boost thanks to Golang great concurrency and ability to implement own plugins with ease even for occasional coders like me.

I already had a warm up by implementing the simplest form of plugin called Exporter — a plugin type that basically does good old zabbix passive check. You will find plenty of Exporter examples among zabbix built-in checks, but here is mine, a plugin implementing serial port access:

Ability to write Exporter plugin is already a step forward for Zabbix, as we can implement something not available out of the box, without any external scripts we would otherwise have to invoke and depend on.

But there is more, as completely new types of plugins can be implemented which were not available before.

For example, now you can use Zabbix agent to open a TCP connection and listen for incoming data as long as we need it. Yes, no timeouts, no blocking of other collection tasks.

To do that we need to implement Watcher interface in our plugin. And let’s just do it with some practical meaning, by implementing MQTT protocol, which is popular in IoT solutions nowadays. Such plugin would be able to receive messages published to subscribed MQTT topic and that very moment pushes it to the Zabbix server.

Oh, and if you want to know more about MQTT, I highly recommend reading this guide:

Watcher Plugin in a Nutshell

Back to our Zabbix plugin, we start our development by checking out zabbix git source code(but don’t forget to install go and build-essentials):

git clone https://git.zabbix.com/scm/zbx/zabbix.git --depth 1 zabbix-agent2
cd zabbix-agent2

Next, navigate to src/go/plugins/debug dir. There you will find some examples of different plugin types you can use as blueprints. For our MQTT plugin we will take trapper.go .

Copy it to src/go/plugins/mqtt and rename to mqtt.go.

Edit file src/go/plugins/plugins.go by appending _ "zabbix.com/plugins/mqtt" to it:

package pluginsimport (
_ "zabbix.com/plugins/log"
_ "zabbix.com/plugins/systemrun"
_ "zabbix.com/plugins/zabbix/async"
_ "zabbix.com/plugins/zabbix/stats"
_ "zabbix.com/plugins/zabbix/sync"
_ "zabbix.com/plugins/mqtt"
)

Ok, now a bit tricky part, in mqtt.go we need to implement some specific interfaces and their methods, for our plugin to properly work with watcher manager. They are:

  • EventSource interface (Subscribe(), Unsubscribe(), URI() )

describes single event source — listen port for traps, file name for file content monitoring etc. In our case this will be MQTT client connected to MQTT broker.

  • EventProvider interface (EventSourceByKey(), EventSourceByURI() )

provides methods to get event source by item key or event source URI. The event source can be either cached by provider or created a new one upon every request. In the second case it would be almost like a wrapper to bridge watch Manager with event generator.

  • EventFilter interface (Convert())

finally, you can define how to properly convert received data to item values as well as you can provide additional data filters.

But again, if you feel that all these puzzles you, just look through examples.

And what about MQTT implementation? Paho client library https://github.com/eclipse/paho.mqtt.golang will handle this for us, so we don’t have to reinvent the wheel.

Plugin configuration

Another thing worth mentioning is that plugins can have its own optional configuration stored in zabbix_agent2.conf configuration file if required.

# MQTT username and password:Plugins.MQTTSubscribe.Username=<username here>
Plugins.MQTTSubscribe.Password=<password here>
# MQTT Connection timeout in seconds. If not set, global Timeout is usedPlugins.MQTTSubscribe.Timeout=5

This is implemented as follows:

There are Configure and Validate methods that process and check private plugin options, you can also use ‘conf’ tag metadata:

// The metadata has format [name=<name>,][optional,][range=<range>,][default=<default value>]
// where:
// <name> - the parameter name,
// optional - set if the value is optional,
// <range> - the allowed range <min>:<max>, where <min>, <max> values are optional,
// <default value> - the default value. If specified it must always be the last tag.

MQTT plugin in action

Build and run agent (full example here)

./bootstrap.sh
pushd .
cd src/go/
go mod vendor
popd
./configure --enable-agent2 --enable-static
make

You will then find a new agent with the plugin included in src/go/bin dir.

In zabbix_agent2.conf:

  • set ServerActive= to IP of your Zabbix server or proxy
  • set Hostname= to host name you will use in Zabbix.

Then test your plugin by running:

./zabbix-agent2 -c ../conf/zabbix_agent2.conf

In Zabbix, set new item like:

mqtt.subscribe[<MQTT broker URL>,<MQTT topic>]

for example:

mqtt.subscribe[tcp://192.168.1.1:1883,devices/wb-ms-thls_25/controls/Temperature]

The item must be of Type = Zabbix agent(active).
Also note, that update interval is ignored, values will be received once published to the MQTT broker. Still, try to set the update interval as closely as possible to the data update frequency, so you get proper graphs in Zabbix.

Lessons learned

New Zabbix Agent 2 is very promising for extending Zabbix with new collectors, powerful standard Go library, as well as many external libraries, can be used to quickly implement what you need. Unlike C loadable modules, more people will be able to handle it. If you are not familiar with Go, just go (hah) through Tour of Go and try to implement simple Exporter plugin first by using debug plugins as a reference.

Watcher currently available as an active check only in Zabbix, and this practically means that such plugin cannot serve multiple hosts. Plugins must be built into the agent source. Both will be probably addressed in future releases.

Refs

--

--