Pengembangan plugin untuk Zabbix Agent 2

Pada Zabbix Summit 2019 yang terakhir, bersamaan dengan perilisan Zabbix 4.4, Zabbix Agent 2 yang baru diumumkan, fitur utamanya adalah kemampuan untuk menulis plug-in untuk itu dalam bahasa Go. Dan banyak yang langsung mulai bertanya: tetapi bagaimana, sebenarnya, plugin ini menulis, bagaimana mereka diatur? Di mana mendapatkan dokumentasi dan contoh?


Dalam artikel ini saya ingin memberikan jawaban untuk ini dan beberapa pertanyaan lainnya. Semuanya beres, tetapi jika Anda adalah salah satu dari mereka yang segera memasuki pertempuran, jangan ragu untuk melewatkan bagian pengantar dan lanjutkan berlatih βŽβ—”β—ž β—”βŽ 


Begitu...



Agen baru macam apa, dan mengapa dia muncul?


Jika Anda mencoba menulis plugin untuk Zabbix Agent pertama, atau setidaknya dimaksudkan untuk melakukannya, maka Anda mungkin mencatat bahwa opsi Anda sangat terbatas.


Plugin untuk agen pertama dapat berjalan dalam beberapa proses yang berbeda, tidak memberikan pencipta kontrol yang cukup untuk mengimplementasikannya, misalnya, menggunakan koneksi yang persisten, mempertahankan keadaan di antara cek, menerima perangkap - itu sulit atau tidak mungkin untuk melakukan hal-hal seperti itu.


. 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 , . , . , , .



:


  • ;
  • .
    . - , ( ) , .

:


  1. configuratorTask
  2. starterTask
  3. collectorTask
  4. watcherTask
  5. exporterTask (directExporterTask)
  6. 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) {
    // Write your code here
    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
#     master ,            
$ 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

. , , :


  1. GET API (, wttr.in)
  2. .

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.")
    }

    // https://github.com/chubin/wttr.in
    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.


// impl β€”    
// name β€”  
// params β€”       (key1, descr1, key2, descr2, keyN, descrN...)
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>. :


  1. , ;
  2. ;
  3. ;
  4. ;
  5. .

, Configurator. Timeout, HTTP .


, , Timeout 1 30 , ( ) .


, .


type PluginOptions struct {
    // Timeout is the maximum time for waiting when a request has to be done. Default value equals the global timeout.
    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)
    }

    // Set default value
    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 {
    // Nothing to validate
    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 // last access time
    modified time.Time // data collect 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 {
            // no data gathered yet
            return
        }

        data := prepareData(p.urls[url].history.Elements())

        jsonRes, err := json.Marshal(stat{
            // Median: timeSample{...},
            // P75:    timeSample{...},
            // P95:    timeSample{...},
            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. .


All Articles