تطوير البرنامج المساعد لعامل Zabbix 2

في قمة Zabbix الأخيرة لعام 2019 ، جنبًا إلى جنب مع إصدار Zabbix 4.4 ، تم الإعلان عن عامل Zabbix الجديد 2 ، وتتمثل السمة الرئيسية في القدرة على كتابة المكونات الإضافية له بلغة Go. وبدأ الكثيرون يتساءلون على الفور: ولكن ، في الواقع ، كيف تكتب هذه الإضافات ، وكيف يتم ترتيبها؟ من أين تحصل على الوثائق والأمثلة؟


في هذه المقالة ، أريد أن أجيب على هذه الأسئلة وبعض الأسئلة الأخرى. كل شيء بالترتيب ، ولكن إذا كنت أحد أولئك الذين اقتحموا المعركة على الفور ، فلا تتردد في تخطي الجزء التمهيدي والمتابعة إلى الممارسة ⎝◔◞ ◔⎠


وبالتالي...



ما نوع الوكيل الجديد ولماذا ظهر؟


إذا حاولت كتابة مكونات إضافية لعامل Zabbix الأول ، أو على الأقل تهدف إلى القيام بذلك ، فمن المحتمل أنك لاحظت أن خياراتك محدودة للغاية.


يمكن تشغيل المكون الإضافي للعامل الأول في عدة عمليات مختلفة ، وعدم منح المنشئ تحكمًا كافيًا عليه لتنفيذ ، على سبيل المثال ، استخدام الاتصالات المستمرة ، والحفاظ على الحالة بين الفحوصات ، وتلقي الفخاخ - فقد كان من الصعب أو المستحيل القيام بمثل هذه الأشياء.


. 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