No Ășltimo Zabbix Summit 2019, juntamente com o lançamento do Zabbix 4.4, o novo Zabbix Agent 2 foi anunciado, cuja principal caracterĂstica Ă© a capacidade de escrever plug-ins para ele no idioma Go. E muitos imediatamente começaram a perguntar: mas como, de fato, esses plugins escrevem, como eles sĂŁo organizados? Onde obter documentação e exemplos?
Neste artigo, quero dar respostas a essas e algumas outras perguntas. Tudo em ordem, mas se vocĂȘ Ă© um daqueles que imediatamente entra em batalha, sinta-se Ă vontade para pular a parte introdutĂłria e continuar a praticar âââ ââ
Assim...

Que tipo de novo agente e por que ele apareceu?
Se vocĂȘ tentou escrever plug-ins para o primeiro Zabbix Agent ou pelo menos pretendia fazĂȘ-lo, provavelmente notou que suas opçÔes sĂŁo muito limitadas.
O plug-in do primeiro agente pode ser executado em vĂĄrios processos diferentes, nĂŁo dando ao criador controle suficiente sobre ele para implementar, por exemplo, usando conexĂ”es persistentes, mantendo o estado entre verificaçÔes, recebendo traps - era difĂcil ou impossĂvel fazer essas coisas.
. 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. .