Loadable plugins

Overview

A loadable plugin for Zabbix agent 2 is a standalone application that the agent can work with. Zabbix sources and compilation of the agent are not required. The only requirement is to build the plugin in a desired way. The communication between Zabbix agent 2 and a loadable plugin as well as the metrics collection process are depicted in the connection diagram below.

The entire plugin building process includes three stages. First, Go code is installed, then plugin support packages and the environment for the installation are prepared, and finally the actual loadable plugin building is performed. This tutorial provides step-by-step instructions for each stage.

Stage 1: Installing the Go code

  1. In order to create a loadable plugin that functions in the same way as a built-in one, the Go code can be placed in this directory:
/usr/local/zabbix/go/plugins/myip
  1. Create the file main.go:
mkdir -p /usr/local/zabbix/go/plugins/myip
       cd /usr/local/zabbix/go/plugins/myip
       vi main.go

The code structure is almost the same as that of a built-in plugin. The only exception is the necessity to define a main() function. Thus, a standalone application is created, the package is named "main", and the list of imports might differ.

  1. Add the code to the file. This is the source code of a loadable plugin Myip. It implements one metric called myip, which returns the external IP address of the host where the agent is running (for further details, refer to Stage 2):
// This is the source code of plugin Myip. It implements 1 metric
       // called myip, which returns the external IP address of the host where
       // Zabbix agent 2 is running.
       
       package main
       
       // Packages to import.
       
       import (
           "fmt"
           "io/ioutil"
           "net/http"
           "git.zabbix.com/ap/plugin-support/plugin/container"
           "git.zabbix.com/ap/plugin-support/plugin"
       )
       
       // Plugin must define structure and embed plugin.Base structure.
       type Plugin struct {
           plugin.Base
       }
       
       // Create a new instance of the defined plugin structure
       var impl Plugin
       
       // Plugin must implement one or several plugin interfaces.
       func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider) (result interface{}, err error) {
           // You may use one of Critf, Errf, Infof, Warningf, Debugf, Tracef functions for logging.
           p.Infof("received request to handle %s key with %d parameters", key, len(params))
       
           // Fetch response from the specified URL, it should be just the IP address.
           resp, err := http.Get("https://api.ipify.org")
           if err != nil {
               // Plugin will return an error response if the request fails
               return nil, err
           }
       
           defer resp.Body.Close()
       
           body, err := ioutil.ReadAll(resp.Body)
           if err != nil {
               // Plugin will return an error response if it fails to read the response
               return nil, err
           }
       
           return string(body), nil
       }
       
       func init() {
           // Register the metric, specifying the plugin and metric details.
           // 1 - a pointer to plugin implementation
           // 2 - plugin name
           // 3 - metric name (item key)
           // 4 - metric description
           //
           // NB! The metric description must end with a period, otherwise Zabbix agent 2 will return an error and won't start!
           // Metric name (item key) and metric description can be repeated in a loop to register additional metrics.
           plugin.RegisterMetrics(&impl, "Myip", "myip", "Return the external IP address of the host where agent is running.")
       }
       
       // This is the main function, and it is required to compile the plugin.
       // The function creates a new plugin handler instance, assigns it to be used for logging by the plugin and then executes the plugin handler.
       func main() {
           h, err := container.NewHandler(impl.Name())
           if err != nil {
               panic(fmt.Sprintf("failed to create plugin handler %s", err.Error()))
           }
           impl.Logger = &h
       
           err = h.Execute()
           if err != nil {
               panic(fmt.Sprintf("failed to execute plugin handler %s", err.Error()))
           }
       }

Stage 2: Preparing plugin support packages and the environment

  1. Package definition. This is the name of your Go package. All the files in the package must have the same name. It implements one metric, called myip, which returns the external IP address of the host where Zabbix agent is running.
package main
  1. Package import. These are the packages that support the plugin. The following packages are used:
import (
           "fmt"
           "io/ioutil"
           "net/http"
           "git.zabbix.com/ap/plugin-support/plugin/container"
           "git.zabbix.com/ap/plugin-support/plugin"
       )
  1. Plugin structure. A plugin must define the structure and embed the plugin.Base structure. This grants the access to all the functionality of a plugin.
type Plugin struct {
           plugin.Base
       }
       
       var impl Plugin
  1. Plugin Export interface. A plugin must implement one or several plugin interfaces. This is where all the plugin functionality is handled. In this case, the Export interface is implemented.
func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider) (result interface{}, err error) {
  1. Logging. You can use one of the logging functions available to plugins: Critf, Errf, Infof, Warningf, Debugf, Tracef. These log messages will appear in the log of Zabbix agent 2.
p.Infof("received request to handle %s key with %d parameters", key, len(params))
  1. Core logic. Fetches the response from the specified URL and reads it, then returns the IP address as a response and closes the response. In case of an error when executing the GET request or when reading the response, the error is returned instead.
    resp, err := http.Get("https://api.ipify.org")
           if err != nil {
               return nil, err
           }
       
           defer resp.Body.Close()
       
           body, err := ioutil.ReadAll(resp.Body)
           if err != nil {
               return nil, err
           }
       
           return string(body), nil
       }
  1. Registering the metric. Init function is run when Zabbix agent 2 is started. It provides the plugin name, metrics, and interfaces it implements. The first parameter grants access to the plugin structure, which provides the information on all the available plugin interfaces. The second parameter defines the plugin name, which must be unique and not clash with any other already existing plugin name. The third parameter defines the metric name. This is the key used to gather data from a plugin. The fourth parameter is the description, which must start with a capital letter and end with a period.

The third and fourth parameters can be input multiple times in a row to register multiple metrics.

func init() {
           plugin.RegisterMetrics(&impl, "Myip", "myip", "Return the external IP address of the host where agent is running.")
       }

The metric description must end with a period, otherwise the agent will not start!

  1. This is the main function, which creates a new plugin handler instance, assigns it to be used for logging by the plugin and then executes the plugin handler.
func main() {
           h, err := container.NewHandler(impl.Name())
           if err != nil {
               panic(fmt.Sprintf("failed to create plugin handler %s", err.Error()))
           }
           impl.Logger = &h
       
           err = h.Execute()
           if err != nil {
               panic(fmt.Sprintf("failed to execute plugin handler %s", err.Error()))
           }
       }

Stage 3: Building the plugin

  1. Execute this bash script to create Go files for dependency handling and to download the dependencies automatically:
go mod init myip
       GOPROXY=direct go get git.zabbix.com/ap/plugin-support/plugin@branchname
       go mod tidy
       go build

Make sure to specify the correct branch name, i.e. replace branchname (see Line 2) with one of the following:

  • release/* - for the stable release branch, where "*" is the release version (i.e. 6.0)
  • master - for the master branch
  • <commit hash> - for the specific commit version (use the specific commit hash)

The output should be similar to this:

go: creating new go.mod: module myip
       go: to add module requirements and sums:
           go mod tidy
       go: finding module for package git.zabbix.com/ap/plugin-support/plugin/container
       go: finding module for package git.zabbix.com/ap/plugin-support/plugin
       go: found git.zabbix.com/ap/plugin-support/plugin in git.zabbix.com/ap/plugin-support v0.0.0-20220608100211-35b8bffd7ad0
       go: found git.zabbix.com/ap/plugin-support/plugin/container in git.zabbix.com/ap/plugin-support v0.0.0-20220608100211-35b8bffd7ad0
  1. Create an executable myip for the loadable plugin and specify the path to the plugin configuration file in the Plugins.Myip.System.Path parameter of Zabbix agent 2 configuration file (ensure that the /etc/zabbix_agent2.d/plugins.d path is correct):
echo 'Plugins.Myip.System.Path=/usr/local/zabbix/go/plugins/myip/myip' > /etc/zabbix_agent2.d/plugins.d/myip.conf

Ensure that the path is set correctly, using Myip. This is the name of the plugin, which was defined in the code when calling plugin.RegisterMetrics() function.

  1. Test the metric:
zabbix_agent2 -t myip

As a result, the external IP address of your host should be displayed.

In case of any errors, first make sure that the user zabbix has permissions to access /usr/local/zabbix/go/plugins/myip directory.

More information on loadable plugins

Loadable Zabbix agent 2 plugins are supported since Zabbix 6.0.0. These plugins are kept in a separate repository, however, they use a package shared with Zabbix agent 2.

Loadable plugins need to be compiled and built separately. Plugin configuration shall be provided in a separate Zabbix agent 2 configuration file in the same way as it is provided for built-in plugins.

Loadable plugins support the following interfaces:

  • Exporter (except the ContextProvider parameter);
  • Runner;
  • Configurator.

Watcher and Collector interfaces are not supported.

Zabbix agent 2 connects bidirectionally to the plugins using UNIX sockets on Linux and Named Pipes on Windows.

Several examples of loadable plugins are available in Zabbix source code and can be used as a reference.

Documentation

A final step of creating a good plugin is adding a Readme file with the following information:

  • A short description of the plugin
  • Plugin configuration parameters and steps required for configuring the connection with the monitored system
  • A list of supported keys
  • Known limitations/problems (if any)