Lors du dernier Zabbix Summit 2019, avec la sortie de Zabbix 4.4, le nouveau Zabbix Agent 2 a été annoncé, dont la principale caractéristique est la possibilité d'écrire des plug-ins pour celui-ci dans la langue Go. Et beaucoup ont immédiatement commencé à se demander: mais comment, en fait, ces plugins écrivent-ils, comment sont-ils organisés? Où obtenir de la documentation et des exemples?
Dans cet article, je veux donner des réponses à ces questions et à d'autres. Tout est en ordre, mais si vous êtes de ceux qui se gâtent juste pour un combat, sautez la partie introductive en toute sécurité et allez vous entraîner ⎝◔◞ ◔⎠
Donc...

Quel genre de nouvel agent et pourquoi est-il apparu?
Si vous avez essayé d'écrire des plugins pour le premier agent Zabbix, ou du moins avez l'intention de le faire, alors vous avez probablement remarqué que vos options sont très limitées.
Le plugin pour le premier agent pouvait s'exécuter dans plusieurs processus différents, ne donnant pas au créateur un contrôle suffisant sur lui pour implémenter, par exemple, en utilisant des connexions persistantes, en maintenant l'état entre les vérifications, en recevant des pièges - il était difficile ou impossible de faire de telles choses.
. Go ( Zabbix Agent), . Go- — — . , Go- Zabbix Agent , .
, Go-:
- onfig ;
- "out-of-the-box";
- ;
- Windows ;
- .
Zabbix 4.4 , Zabbix 5.0 "production-ready".
, , " ". , Zabbix 4.4. , Go- , , Zabbix 5.0 - .
— ServerConnector, ServerListener Scheduler.
ServerConnector ( / ), items . .
ServerListener Scheduler. , .
Scheduler . Scheduler () , item'.
: ( Bulk Passive, ). , , .
. — PlantUML ¯\(ツ)/¯

: ServerConnector ResultCache, .

Scheduler , ServerConnector' ServerListener item'. , ResultWriter, .
items Zabbix , ServerConnector updateRequest , . , . , , .
:
:
- configuratorTask
- starterTask
- collectorTask
- watcherTask
- exporterTask (directExporterTask)
- stopperTask
(taskBase) , , .
exporterTask
ExporterTask ( bulk ). item, . Scheduler Export Exporter ResultWriter.
directExporterTask
directExporterTask ExporterTask , , ( ), , 1 . . — directExporterTask .
watcherTask
WatcherTask () . Watch Watcher, .
collectorTask
Scheduler Collect Collector Period() .
starterTask
Start Runner, .
stopperTask
Stop Runner, .
configuratorTask
Configure Configurator, , .
5 : Exporter, Watcher, Collector, Runner Configurator.
Exporter Watcher : Exporter pull , Watcher — push.
plugin.Exporter
type Exporter interface {
Export(key string, params []string, context ContextProvider) (result interface{}, err error)
}
Exporter — , , , . , . . , , . , , - .
, . . Go: , , , sync.Map . race-, .
Export() — 100 . , plugin.Base.SetCapacity.
func (b *Base) SetCapacity(capacity int)
, capacity . :
Plugins.<PluginName>.Capacity=1
plugin.Watcher
type Watcher interface {
Watch(requests []*Request, context ContextProvider)
}
Watcher , . , trapping, . use case — , , , . , , , , .
plugin.Collector
type Collector interface {
Collect() error
Period() int
}
Collector , . , Exporter’.
use case Collector — , , Zabbix .
Collector’ 2 :
- Collect ;
- Period .
Collector, , .
plugin.Runner
type Runner interface {
Start()
Stop()
}
Runner , ( Start), , ( Stop).
, , , - , , ..
() . , (Zabbix Server Proxy), . , , . , . , , , 24 .
plugin.Configurator
type Configurator interface {
Configure(globalOptions *GlobalOptions, privateOptions interface{})
Validate(privateOptions interface{}) error
}
Configurator , .
2 :
- Configure .
- Validate config . , , .
, , . .
Go- Zabbix .
. , — , , . internal/agent "plugin_" . , , UserParameters.
, , ( , , , ..) — . . go/plugins, .
Hello, world!
— Go, , . :
package packageName
import "zabbix.com/pkg/plugin"
type Plugin struct {
plugin.Base
}
var impl Plugin
func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider) (res interface{}, err error) {
return
}
func init() {
plugin.RegisterMetrics(&impl, "PluginName", "key", "Description.")
}
, bash python, ? ⎝^ω^⎠ , - .
, , .
:
Zabbix.
$ git clone https://git.zabbix.com/scm/zbx/zabbix.git --depth 1 zabbix-agent2
$ cd zabbix-agent2
$ git checkout -b feature/myplugin release/4.4
src/go/plugins/weather weather.go , .
, "zabbix.com/pkg/plugin".
package weather
import "zabbix.com/pkg/plugin"
, Base plugin. .
type Plugin struct {
plugin.Base
}
var impl Plugin
. , , :
- GET API (, wttr.in)
- .
Exporter.
func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider) (result interface{}, err error) {
if len(params) != 1 {
return nil, errors.New("Wrong parameters.")
}
res, err := p.httpClient.Get(fmt.Sprintf("https://wttr.in/~%s?format=%%t", params[0]))
if err != nil {
return nil, err
}
temp, err := ioutil.ReadAll(res.Body)
_ = res.Body.Close()
if err != nil {
return nil, err
}
return string(temp)[0 : len(temp)-4], nil
}
— , .
plugin.RegisterMetrics.
func RegisterMetrics(impl Accessor, name string, params ...string)
init ( ).
func init() {
plugin.RegisterMetrics(&impl, "Weather", "weather.temp", "Returns Celsius temperature.")
}
, .
package plugins
import (
_ "zabbix.com/plugins/kernel"
_ "zabbix.com/plugins/log"
_ "zabbix.com/plugins/weather"
)
, 3 : linux, darwin windows. , , , .
: , src/go/plugins/plugins_<platform>.go.
Go, .
Go 1.13.
, --enable-agent2 make.
$ cd <zabbix-source>
$ ./bootstrap.sh; ./configure --enable-agent2 --enable-static; make
, . “moscow” .
$ <zabbix-source>/src/go/bin/zabbix_agent2 -t weather.temp[moscow]
+1
. , go run, .
$ go run <zabbix-source>/src/go/cmd/zabbix_agent2/zabbix_agent2.go
, zabbix.com/pkg/log: Tracef, Debugf, Warningf, Infof, Errf, Critf. Plugin, log. , [<PluginName>] .
Go- . Plugins. , -, , , . : Plugins.<PluginName>.<Parameter>=<Value>. :
- , ;
- ;
- ;
- ;
- .
, Configurator. Timeout, HTTP .
, , Timeout 1 30 , ( ) .
, .
type PluginOptions struct {
Timeout int `conf:"optional,range=1:30"`
}
, conf. .
: [name=<name>,][optional,][range=<range>,][default=<default value>], :
- <name> — ( );
- optional — , ;
- <range> — <min>:<max>, <min> <max> ;
- <default value> — . .
Plugin , — http.Client, .
type Plugin struct {
plugin.Base
options PluginOptions
httpClient http.Client
}
Configurator. , 2 : Configure Validate.
func (p *Plugin) Configure(global *plugin.GlobalOptions, privateOptions interface{}) {
if err := conf.Unmarshal(privateOptions, &p.options); err != nil {
p.Errf("cannot unmarshal configuration options: %s", err)
}
if p.options.Timeout == 0 {
p.options.Timeout = global.Timeout
}
p.httpClient = http.Client{Timeout: time.Duration(p.options.Timeout) * time.Second}
}
func (p *Plugin) Validate(privateOptions interface{}) error {
return nil
}
conf.Unmarshal .
http.Get p.httpClient.Get.
res, err := p.httpClient.Get(fmt.Sprintf("https://wttr.in/~%s?format=%%t", params[0]))
if err != nil {
if err.(*url.Error).Timeout() {
return nil, errors.New("Request timeout.")
}
return nil, err
}
, :
Plugins.Weather.Timeout=1
, .
, - ? — . Timeout default, .. .
( , Validate Configure).
, . , , , , . Validate. , .
func (p *Plugin) Validate(privateOptions interface{}) error {
var opts PluginOptions
return conf.Unmarshal(privateOptions, &opts)
}
, , : "cannot create scheduling manager: invalid plugin Weather configuration: Cannot assign configuration: invalid parameter Plugins.Weather.Timeout at line 411: value out of range".
" ". runtime Validate Configure, . , - Configure — , . , Start Stop ( Runner).
: https://github.com/VadimIpatov/zabbix-weather-plugin.
Exporter . . , Collector Runner.
! - . , HTTP .
. "net/http/httptrace" ( Go 1.7).
type timeSample struct {
DnsLookup float64 `json:"dnsLookup"`
Connect float64 `json:"connect"`
TlsHandshake float64 `json:"tlsHandshake"`
FirstResponseByte float64 `json:"firstResponseByte"`
Rtt float64 `json:"rtt"`
}
func (p *Plugin) measureTime(url string) (timeSample, error) {
var (
sample timeSample
start, connect, dns, tlsHandshake time.Time
)
req, _ := http.NewRequest("GET", url, nil)
trace := &httptrace.ClientTrace{
DNSStart: func(_ httptrace.DNSStartInfo) {
dns = time.Now()
},
DNSDone: func(_ httptrace.DNSDoneInfo) {
sample.DnsLookup = float64(time.Since(dns) / time.Millisecond)
},
ConnectStart: func(_, _ string) {
connect = time.Now()
},
ConnectDone: func(net, addr string, err error) {
if err != nil {
p.Errf("unable to connect to host %s: %s", addr, err.Error())
}
sample.Connect = float64(time.Since(connect) / time.Millisecond)
},
GotFirstResponseByte: func() {
sample.FirstResponseByte = float64(time.Since(start) / time.Millisecond)
},
TLSHandshakeStart: func() {
tlsHandshake = time.Now()
},
TLSHandshakeDone: func(_ tls.ConnectionState, _ error) {
sample.TlsHandshake = float64(time.Since(tlsHandshake) / time.Millisecond)
},
}
ctx, cancel := context.WithTimeout(req.Context(), time.Duration(p.options.Timeout)*time.Second)
defer cancel()
req = req.WithContext(httptrace.WithClientTrace(ctx, trace))
start = time.Now()
if _, err := http.DefaultTransport.RoundTrip(req); err != nil {
return timeSample{}, err
}
sample.Rtt = float64(time.Since(start) / time.Millisecond)
return sample, nil
}
- . (Ring Buffer). , — github.com/VadimIpatov/gcircularqueue. , . Open Source Go — github.com/montanaflynn/stats. .
type Plugin struct {
plugin.Base
urls map[string]*urlUnit
sync.Mutex
options Options
}
type urlUnit struct {
url string
history *gcircularqueue.CircularQueue
accessed time.Time
modified time.Time
}
Start Stop Runner.
func (p *Plugin) Start() {
p.urls = make(map[string]*urlUnit)
}
func (p *Plugin) Stop() {
p.urls = nil
}
Collector.
func (p *Plugin) Collect() (err error) {
now := time.Now()
p.Lock()
for key, url := range p.urls {
if now.Sub(url.accessed) > maxInactivityPeriod {
p.Debugf("removed expired url %s", url.url)
delete(p.urls, key)
continue
}
res, err := p.measureTime(url.url)
if err != nil {
p.Errf(err.Error())
continue
}
url.history.Push(res)
if url.history.IsFull() {
_ = url.history.Shift()
}
url.modified = now
}
p.Unlock()
return
}
func (p *Plugin) Period() int {
return p.options.Interval
}
URL ( ), p.measureTime(url.url) . , url.modified.
URL , .
, Collector . Exporter.
func (p *Plugin) Export(key string, params []string, ctx plugin.ContextProvider) (result interface{}, err error) {
if len(params) != 1 {
return nil, errors.New("Wrong parameters.")
}
url, err := parseURL(params[0])
if err != nil {
return nil, err
}
switch key {
case keyHttpTraceStats:
if _, ok := p.urls[url]; !ok {
p.urls[url] = &urlUnit{
url: url,
history: gcircularqueue.NewCircularQueue(maxHistory),
}
}
p.Lock()
defer p.Unlock()
p.urls[url].accessed = time.Now()
if p.urls[url].history.Len() < minStatRange {
return
}
data := prepareData(p.urls[url].history.Elements())
jsonRes, err := json.Marshal(stat{
P99: timeSample{
DnsLookup: percentile(data[metricDnsLookup], p99),
Connect: percentile(data[metricConnect], p99),
TlsHandshake: percentile(data[metricTlsHandshake], p99),
FirstResponseByte: percentile(data[metricFirstResponseByte], p99),
Rtt: percentile(data[metricRtt], p99),
},
})
if err != nil {
p.Errf(err.Error())
return nil, errors.New("Cannot marshal JSON.")
}
value := string(jsonRes)
return plugin.Result{
Value: &value,
Ts: p.urls[url].modified,
}, nil
default:
return nil, plugin.UnsupportedMetricError
}
}
, Collect Export, .. .
( ):
$ zabbix_get -s zabbix.local -k "httptrace.stats[yoursite.com]"
{
"median": {
"dnsLookup": 13,
"connect": 28,
"tlsHandshake": 56,
"firstResponseByte": 126.5,
"rtt": 126.5
},
"p75": {
"dnsLookup": 20,
"connect": 31,
"tlsHandshake": 60,
"firstResponseByte": 138.5,
"rtt": 138.5
},
"p95": {
"dnsLookup": 22.5,
"connect": 35,
"tlsHandshake": 78.5,
"firstResponseByte": 159.5,
"rtt": 159.5
},
"p99": {
"dnsLookup": 50,
"connect": 51.5,
"tlsHandshake": 125.5,
"firstResponseByte": 266.5,
"rtt": 266.5
}
}
: https://github.com/VadimIpatov/zabbix-httptrace-plugin.
runtime metrics, . .
:
$ zabbix_agent2 -R metrics
...
[Weather]
active: true
capacity: 0/100
tasks: 0
weather.temp: Returns Celsius temperature.
...
— HTTP. StatusPort=, http://<ZabbixAgentHost>:<Port>/status.

?
. , :
- ( ).
- , .. .
- Zabbix. , Docker’ Mysql.
!
, , :
Templates guidelines — .
An official guide to making and managing great templates — Zabbix Summit .
Magic of the new zabbix agent — Zabbix Agent 2.
Zabbix Agent 2.
Zabbix Agent 2 ( : git.zabbix.com). , .
Weather.
HttpTrace.
Writing watcher Zabbix Agent2 MQTT plugin in Go — Watcher .
Go, " Go" , , A Tour of Go ʕ☉Ѡ☉ʔ
Zabbix Agent 2 — Zabbix . Go, , , C Loadable modules.
Stay tuned!
P.S. .