Real time metriky prostredníctvom Prometheus & Grafana

Ak ste niekedy robili serióznu webovú aplikáciu, určite ste sa stretli s požiadavkou na jej monitoring, resp. sledovanie rôznych aplikačných a runtime metrík v čase. Sledovanie v čase umožňuje objavovanie rôznych patternov používania aplikácie (napr. nízky traffic počas víkendov a sviatkov), alebo napr. aj vizualizáciu využitia CPU, diskového priestoru, RAM a podobne. Napríklad, ak z grafov využitia RAM vidno, že využitie stále narastá a klesne iba po reštarte aplikácie zrejme sme objavili memory leak. Možných dôvodov na zavedenie zbierania aplikačných a runtime metrík je mnoho.

Existuje viacero nástrojov na monitoring SW aplikácií, napr. Zabbix a spol. Nástroje tohoto typu sa však zameriavajú prevažne na runtime monitoring, t.j. sledovanie využitia diskového priestoru, CPU, RAM a pod, ale nie sú príliš vhodné na aplikačný monitoring a odpovede na otázky koľko používateľov je momentálne prihlásených v aplikácii, aký je počet requestov na REST-ové API, atď.

V tomto poste si ukážeme, akým spôsobom je možné v reálnom čase sledovať runtime a hlavne aplikačné metriky prostredníctvom nástrojov Prometheus a Grafana. Ako príklad nám poslúži Opendata API systému ITMS2014+.

Prometheus

Naše riešenie na monitoring pozostáva z dvoch nástrojov. Jadrom riešenia je Prometheus, čo je vo svojej podstate databáza časových (multi dimenzionálnych) radov. Takúto databázu si je možné predstaviť ako súbor pomenovaných metrík, každá s časovou pečiatkou a k nej množinou dvojíc kľúč=hodnota predstavujúcich jednotlivé sledované veličiny. Prometheus umožňuje pomerne rozsiahle možnosti alertingu pri definovaných udalostiach (ale o tom možno iný blogpost), obsahuje vlastný dopytovací jazyk a tiež možnosti na vizualizáciu časových radov. Možnosti vizualizácie sú ale pomerne “hračkárske”, a preto sa spolu s Prometheus-om väčšinou používa práve spomínaná Grafana.

Prometheus na rozdiel od väčšiny iných monitorovacích softvérov funguje na PULL prístupe. To znamená, že každá z monitorovaných aplikácií vystavuje HTTP endpoint zo sledovanými metrikami a Prometheus si metriky sťahuje v definovaných intervaloch.

Grafana

Grafana je (univerzálnym) nástrojom na vizualizáciu a analýzu dát. Grafana nemá vlastnú databázu, ale je to v princípe frontend k populárnym zdrojom dát ako sú napr. Prometheus, InfluxDB, Graphite, ElasticSearch a iným. Grafana umožňuje vytváranie a zdieľanie custom grafov a dashboardov, čo si ukážeme o chvíľu.

Publikovanie metrík z aplikácie

Na to, aby Prometheus mohol pravidelne sťahovať z vašej aplikácie metriky, je potrebné vystaviť HTTP endpoint, ktorý po zavolaní vráti aktuálne hodnoty metrík, t.j. vašu aplikáciu treba inštrumentovať. Prometheus podporuje dva formáty encodingu metrík - Plain Text alebo Protocol Buffers. Našťastie to nie je vôbec zložité, pretože Prometheus poskytuje sadu klientských knižníc pre všetky hlavné programovacie jazyky: Java, Go, Python, Ruby, Scala, C++, Erlang, Elixir, Node.js, PHP, Rust, Lisp Haskell a ďalšie.

Ako bolo spomenuté, ako modelový prípad popíšem vystavovanie metrík z Opendata API, čo je aplikácia napísaná v jazyku Go. Siahneme preto po oficiálnej Prometheus Go klientskej knižnici. Zakomponovanie knižnice je veľmi jednoduché a pozostáva z troch krokov.

V provom kroku treba medzi importy zaradiť Prometheus knižnicu:

 1package main
 2
 3import (
 4
 5    // ...
 6    "github.com/prometheus/client_golang/prometheus"
 7    "github.com/prometheus/client_golang/prometheus/promhttp"
 8    // ...
 9)
10
11// ...

V druhom kroku vystavíme metriky (používam excelentný Gorilla mux an HTTP middleware Negroni):

1r := mux.NewRouter()
2r.Path("/metrics").Handler(promhttp.Handler())
3n := negroni.New(negroni.NewRecovery())
4n.UseHandler(r)

Tu nás zaujíma iba riadok číslo 2, kde sme povedali, že endpoint /metrics bude spracovaný Prometheus handlerom, ktorý sa postará o to, aby zavolanie tejto URL vrátilo výsledok zrozumiteľný Prometheus-u. Teda niečo veľmi podobné nasledujúcemu výstupu:

go_goroutines 11
go_memstats_alloc_bytes 7.949552e+06
go_memstats_buck_hash_sys_bytes 2.542265e+06
go_memstats_frees_total 7.49226407e+08
go_memstats_gc_sys_bytes 1.568768e+06
go_memstats_heap_alloc_bytes 7.949552e+06
go_memstats_last_gc_time_seconds 1.5020497859690137e+09
go_memstats_lookups_total 810225
go_memstats_mallocs_total 7.49277644e+08
go_memstats_mcache_inuse_bytes 4800
go_memstats_stack_sys_bytes 819200
go_memstats_sys_bytes 4.0659192e+07
process_open_fds 23
process_resident_memory_bytes 4.3012096e+07
process_start_time_seconds 1.50171016757e+09
process_virtual_memory_bytes 4.42384384e+08

Reálne však nevystavujem URL /metrics voľne čitateľnú svetu, ale za Basic Auth (cez HTTPS):

r.Path("/metrics").Handler(httpauth.SimpleBasicAuth("USERNAME_NOT_SHOWN", "PASSWORD_NOT_SHOWN")(promhttp.Handler()))

Aj keď sme pridali iba 3 riadky kódu, už teraz vieme sledovať runtime metriky aplikácie, t.j. koľko je aktívnych goroutín, alokáciu RAM, využitie CPU a pod., ako si ukážeme neskôr. Nikde sme však nepovedali, ktoré aplikačné metriky chceme sledovať (a ako).

V treťom kroku si preto ukážeme akým spôsobom pridať vlastnú aplikačnú metriku. V prípade Opendata API ma napríklad zaujímalo, ktoré REST-ové endpointy konzumenti volajú, ako často, a aké sú časy spracovania odpovedí.

Vždy, keď chceme vystaviť nejakú metriku, je potrebné vybrať jej typ. Prometheus poskytuje 4 typy metrík:

  • Counter - kumulatívna metrika, používa sa na zaznamenávanie hodnôt, ktoré sú neklesajúce, ako napr. počet reuqestov v čase
  • Gauge - používa sa na zaznamenávanie hodnôt, ktoré sa v čase ľubovoľne menia, napr. alokácia pamäte
  • Histogram - používa sa na zaznamenávanie hodnôt spadajúcich do definovaných intervalov a súčasne pre každú metriku eviduje sumu a počet sledovaní
  • Summary - podobný typ metriky ako histogram, netreba definovať intervaly ako v prípade histogramu, ale priamo na klientskej strane sa definujú kvantily, ktoré chceme sledovať. Istou nevýhodou je vyššia zátaž na klientskej strane, pretože kvantily treba prepočítať s každou sledovanou udalosťou.

V našom prípade chceme sledovať časy spracovania requestov za jednotlivé endpointy (a ich percentily) a počet requestov za časovú jednotku. Ako základ pre tieto metriky sme zvolili typ Histogram. Poďme sa pozrieť ako to vyzerá v kóde:

1httpDurationsHistogram := prometheus.NewHistogramVec(prometheus.HistogramOpts{
2	Name:    "http_durations_histogram_seconds",
3	Help:    "HTTP request latency distributions.",
4	Buckets: prometheus.ExponentialBuckets(0.0001, 1.5, 36),
5}, []string{"code", "version", "controller", "action"})
6prometheus.MustRegister(httpDurationsHistogram)

Týmto sme pridali metriku s názvom http_durations_histogram_seconds a povedali sme, že ju chceme sledovať pre 4 dimenzie:

  • code - HTTP návratový status kód
  • version - Verzia Opendata API
  • controller - Názov web controllera, ktorý spracúva HTTP request
  • action - Názov akcie v rámci web controllera

Pre typ metriky Histogram treba dopredu určiť intervaly sledovaných veličín, v našom prípade je to čas. Na riadku 3 sme preto vytvorili 36 sledovaných intervalov exponenciálne narastajúcich časov od 0.0001 až po 145 sekúnd. Pretože empiricky vieme povedať, že väčšina trvaní spracovania requestov bude do 30ms, tak nám maximálna hodnota 145 sekúnd s prehľadom postačuje.

No a nakoniec potrebujeme pre každý request na Opendata API server zaznamenať 4 sledované dimenzie a čas spracovania requestu. Tu máme dve možnosti - upraviť každý controller tak, aby zaznamenával spomínané metriky, alebo vytvoriť middleware, ktorým obalíme volanie controllera a logiku na zaznamenanie metrík zapúzdrime do tohto middleware-u. Samozrejme, vybrali sme si druhý, podstatne menej pracnejší spôsob:

 1func main() {
 2	// ...
 3
 4	api2 := goa.New("API v2")
 5	// Setup middleware
 6	api2.Use(middleware.RequestID())
 7	api2.Use(middleware.LogRequest(false))
 8	api2.Use(prometheusHandler(httpDurationsHistogram))
 9	api2.Use(middleware.Recover())
10
11	// ...
12}
13
14// ..
15
16func prometheusHandler(hist *prometheus.HistogramVec) goa.Middleware {
17	return func(h goa.Handler) goa.Handler {
18		return func(ctx context.Context, rw http.ResponseWriter, req *http.Request) error {
19			startedAt := time.Now()
20			err := h(ctx, rw, req)
21			version := "N/A"
22			split := strings.Split(req.URL.Path, "/")
23			if len(split) > 1 {
24				version = split[1]
25			}
26			resp := goa.ContextResponse(ctx)
27			hist.WithLabelValues(
28				strconv.Itoa(resp.Status), // code
29				version, // version
30				goa.ContextController(ctx), // controller
31				goa.ContextAction(ctx)). // action
32				Observe(float64(time.Since(startedAt)) / float64(time.Second)) // request duration seconds
33			return err
34		}
35	}
36}

Ako vidíme, middleware sme zapojili na riadku 8 a celý middleware je zhruba na 20 riadkoch Go kódu. Na riadku 27 až 31 napĺňame 4 sledované dimenzie a na riadku 32 aj sledovanú metriku - čas spracovania requestu v sekundách.

Konfigurácia

Keďže už máme všetko pripravené na strane aplikácie, ktorú chceme monitorovať, zostáva nám už len nakonfigurovať Prometheus a Grafan-u.

Minimálna konfigurácia pre Prometheus je zobrazená nižšie. Zaujíma nás hlavne interval, ako často sa budú sťahovať metriky (5s) a URL adresa odkiaľ sa budú metriky sťahovať (https://opendata.itms2014.sk/metrics).

global:
  scrape_interval:     5s
  evaluation_interval: 5s

scrape_configs:
  - job_name: 'opendata'
    metrics_path: /metrics
    scheme: https
    static_configs:
      - targets: ['opendata.itms2014.sk']
    basic_auth:
      username: 'USERNAME_NOT_SHOWN'
      password: 'PASSWORD_NOT_SHOWN'

Podobne, opäť minimálna konfigurácia Grafany:

#################################### Paths ####################################
[paths]
data = /var/db/grafana/
logs = /var/log/grafana/
plugins = /var/db/grafana/plugins

#################################### Server ####################################
[server]
protocol = http

# The http port  to use
http_port = 3000

# The public facing domain name used to access grafana from a browser
domain = metrics.itms.redbyte.eu
root_url = https://metrics.itms.redbyte.eu/

Poznámka: V predošlom konfiguráku Grafanu síce vystavujeme na NON TLS porte 3000, ale pred Grafana je ešte postavený Nginx, počúvajúci na TLS 443, zabezpečený Let’s Encrypt certifikátom.

Monitoring

Nakoniec sa dostávame ku kroku, kedy máme nachystané všetko potrebné. Na to, aby sme začali vytvárať grafické prehľady, je potrebné:

  1. Otvoriť web browser a prihlásiť sa do Grafany
  2. Pridať Prometheus data source
  3. Vytvoriť dashboardy
  4. Vytvoriť grafy

Príklad vytvorenia grafu, zobrazujúceho počet HTTP requestov za zvolený interval, je na nasledovnom obrázku.

Zdroj dát pre graf reqs/s

Podobným spôsobom sme vytvorili ďalšie grafy a umiestnili ich do 2 dashboardov tak ako zobrazujú nasledovné obrázky.

Aplikačné metriky opendata servera

Runtime metriky opendata servera

Záver

V tomto poste sme si na reálnom príklade ukázali, že aplikačný a runtime monitoring webovej aplikácie nemusí byť vôbec zložitý. Klientské knižnice projektu Prometheus nám umožňujú jednoduchým spôsobom vystaviť metriky z vašej webovej aplikácie, či je písaná v Java, Go, Ruby, Python a pod. Prometheus dokonca umožňuje vystaviť metriky aj z aplikácií, ktoré nie sú online (či už za firemným firewallom), alebo ide o dávkové aplikácie (napr. skripty). V tomto prípade sa dá použiť PUSH prístup, kedy aplikácia publikuje metriky do tzv. Push Gateway, odkiaľ už sú vystavené online tak, ako bolo popísané v tomto článku.

Na prehľadné browsovanie zozbieraných metrík potom poslúži nástroj Grafana, kde je možné vytvárať rôzne dashboardy a prehľady, ktoré sa dajú zdielať a dokonca sa dajú vytvárať statické snapshoty v čase - vieme si tak “odfotiť” zaujímavú situáciu a neskôr sa k nej vrátiť.