Dezvoltarea pluginului pentru Grafana: istoria ghinionului acumulat

Salut tuturor! Cu câteva luni în urmă, am lansat în producție noul nostru proiect open-source — un plugin Grafana pentru monitorizarea Kubernetes, pe care l-am numit DevOpsProdigy KubeGraf. Codul sursă al pluginului este disponibil în repozitoriul public de pe GitHub. Iar în acest articol, dorim să împărtășim cu voi povestea despre cum am creat pluginul, ce instrumente am folosit și cu ce obstacole ne-am confruntat în procesul de dezvoltare. Să începem!

Partea 0 — introducere: cum am ajuns în această situație?

Ideea de a scrie propriul nostru plugin pentru Grafana ne-a venit complet întâmplător. Compania noastră se ocupă de mai bine de 10 ani cu monitorizarea proiectelor web de diferite niveluri de complexitate. În acest timp, am acumulat o mare expertiză, cazuri interesante și experiență în utilizarea diferitelor sisteme de monitorizare. Și, la un moment dat, ne-am pus întrebarea: „Există un instrument magic pentru monitorizarea Kubernetes, care pur și simplu să poată fi „instalat și uitat”?... Standardul industrial pentru monitorizarea k8s este, desigur, combinația Prometheus + Grafana. Și pentru acest stack există o gamă largă de instrumente gata de utilizare: prometheus-operator, seturi de tablouri kubernetes-mixin, grafana-kubernetes-app.

Cel mai interesant dintre acestea pentru noi s-a dovedit a fi pluginul grafana-kubernetes-app, dar acesta nu a mai fost întreținut de mai bine de un an și, în plus, nu poate funcționa cu versiunile noi ale node-exporter și kube-state-metrics. Și la un moment dat, am decis: „Să nu facem oare o soluție proprie?”

Ce idei am decis să implementăm în pluginul nostru:

  • vizualizarea „hartă a aplicației”: o reprezentare convenabilă a aplicațiilor din cluster, grupate după namespaces, deployments…;
  • vizualizarea relațiilor de tip „deployment — service (+ports)”.
  • vizualizarea distribuției aplicațiilor din cluster pe nodurile clusterului.
  • colectarea metricilor și a informațiilor din mai multe surse: Prometheus și k8s api server.
  • monitorizarea atât a părții infrastructurale (utilizarea timpului CPU, memorie, subsistem de stocare, rețea), cât și a logicii aplicațiilor — starea de sănătate a podurilor, numărul de replici disponibile, informații despre trecerea testelor de liveness/readyness.

Partea 1: Ce este un „plugin pentru Grafana”?

Din punct de vedere tehnic, un plugin pentru Grafana este un controler angular, care este stocat în directorul data al Grafana (/var/grafana/plugins/<your_plugin_name>/dist/module.js) și poate fi încărcat ca modul SystemJS. De asemenea, în acest director ar trebui să existe un fișier plugin.json, care să conțină toate metainformațiile despre pluginul dumneavoastră: numele, versiunea, tipul pluginului, linkurile către repository/site/licență, dependențele și așa mai departe.

Dezvoltarea pluginului pentru Grafana: istoria ghinionului acumulat
module.ts

Dezvoltarea pluginului pentru Grafana: istoria ghinionului acumulat
plugin.json

După cum se vede în captură de ecran, am specificat plugin.type = app. Deoarece pluginurile pentru Grafana pot fi de trei tipuri:

panel: cel mai comun tip de pluginuri — reprezintă un panou pentru vizualizarea unor metrici, utilizat pentru construirea diferitelor tablouri de bord.
datasource: plugin-conector pentru o sursă de date (de exemplu, Prometheus-datasource, ClickHouse-datasource, ElasticSearch-datasource).
app: plugin care vă permite să construiți propria aplicație frontend în interiorul Grafana, să creați propriile pagini html și să accesați manual sursa de date pentru vizualizarea diferitelor date. De asemenea, pot fi utilizate ca dependențe pluginuri de alte tipuri (datasource, panel) și diferite tablouri de bord.

Dezvoltarea pluginului pentru Grafana: istoria ghinionului acumulat
Exemplu de dependențe ale pluginului cu type = app.

Ca limbaj de programare, se poate folosi atât JavaScript, cât și TypeScript (am ales să ne oprim asupra acestuia). Șabloanele pentru pluginuri hello-world de orice tip le puteți găsi la linkul: în acest repository este prezentat un număr mare de starter-pack-uri (există chiar și un exemplu experimental de plugin pe React) cu constructori preinstalați și configurați.

Partea 2: pregătirea mediului local

Pentru a lucra la plugin, avem, desigur, nevoie de un cluster kubernetes cu toate instrumentele preinstalate: prometheus, node-exporter, kube-state-metrics, grafana. Mediul trebuie să se configureze rapid, ușor și fără efort, iar pentru a asigura reload-ul rapid, directorul de date al Grafana trebuie să fie montat direct de pe mașina dezvoltatorului.

Cel mai convenabil, din punctul nostru de vedere, mod de lucru local cu kubernetes este minikube. Pasul următor este să instalăm combinația Prometheus + Grafana folosind prometheus-operator. În acest articol este descris în detaliu procesul de instalare a prometheus-operator pe minikube. Pentru a activa persistența, trebuie să instalați parametrul persistence: true în fișierul charts/grafana/values.yaml, să adăugați propriul dvs. PV și PVC și să le specificați în parametrul persistence.existingClaim

Scriptul final de lansare minikube arată așa:

minikube start --kubernetes-version=v1.13.4 --memory=4096 --bootstrapper=kubeadm --extra-config=scheduler.address=0.0.0.0 --extra-config=controller-manager.address=0.0.0.0
minikube mount 
/home/sergeisporyshev/Projects/Grafana:/var/grafana --gid=472 --uid=472 --9p-version=9p2000.L

Partea 3: dezvoltarea efectivă

Modelul obiectelor

Ca pregătire pentru implementarea plugin-ului, am decis să descriem toate entitățile de bază Kubernetes cu care vom lucra sub formă de clase TypeScript: pod, deployment, daemonset, statefulset, job, cronjob, service, node, namespace. Fiecare dintre aceste clase moștenește de la clasa comună BaseModel, care descrie constructorul, destructorul, metodele pentru actualizare și comutarea vizibilității. În fiecare dintre clase sunt descrise relațiile înnăscute cu alte entități, de exemplu, lista pod-urilor pentru entitatea de tip deployment.

import {Pod} from "./pod";
import {Service} from "./service";
import {BaseModel} from './traits/baseModel';

export class Deployment extends BaseModel{
   pods: Array;
   services: Array;

   constructor(data: any){
       super(data);
       this.pods = [];
       this.services = [];
   }
}

Folosind getter-e și setter-e, putem afișa sau seta metricile necesare pentru entități într-un mod convenabil și lizibil. De exemplu, afișarea formatată a cpu-ului allocat nod-ului:

get cpuAllocatableFormatted(){
   let cpu = this.data.status.allocatable.cpu;
   if(cpu.indexOf('m') > -1){
       cpu = parseInt(cpu)/1000;
   }
   return cpu;
}

Pagini

Lista tuturor paginilor plugin-ului nostru este descrisă inițial în pluing.json la secțiunea de dependențe:

Dezvoltarea pluginului pentru Grafana: istoria ghinionului acumulat

În blocul pentru fiecare pagină, trebuie să specificăm DENUMIREA PAGINII (aceasta va fi ulterior convertită în slug, prin care pagina va fi accesibilă); numele componentei responsabile pentru funcționarea acestei pagini (lista componentelor este exportată în module.ts); specificarea rolului utilizatorului pentru care este disponibilă utilizarea acestei pagini și setările de navigare pentru bara laterală.

În componenta responsabilă pentru funcționarea paginii, trebuie să stabilim templateUrl, oferind calea către fișierul html cu markup. În interiorul controller-ului, prin injecția de dependență, putem accesa 2 servicii Angular importante:

  • backendSrv — serviciul care asigură interacțiunea cu serverul API Grafana;
  • datasourceSrv — serviciul care asigură interacțiunea locală cu toate sursele de date instalate în Grafana dvs. (de exemplu, metoda .getAll() — returnează lista tuturor surselor de date instalate; .get() — returnează obiectul-instanță al unei surse de date specifice.

Dezvoltarea pluginului pentru Grafana: istoria ghinionului acumulat

Dezvoltarea pluginului pentru Grafana: istoria ghinionului acumulat

Dezvoltarea pluginului pentru Grafana: istoria ghinionului acumulat

Partea 4: sursa de date

Din perspectiva Grafana, un datasource este un plugin exact ca toate celelalte: are un punct de intrare, module.js, și un fișier cu metainformații, plugin.json. Când dezvoltăm un plugin de tip app, putem interacționa atât cu datasource-urile existente (de exemplu, prometheus-datasource), cât și cu cele proprii, pe care le putem stoca direct în directorul plugin-ului (dist/datasource/*) sau le putem instala ca dependență. În cazul nostru, datasource-ul vine împreună cu codul plugin-ului. Este, de asemenea, esențial să existe un șablon config.html și un controler ConfigCtrl, care vor fi utilizați pentru pagina de configurare a instanței datasource-ului și controlerul Datasource, în care se implementează logica de funcționare a datasource-ului.

În pluginul KubeGraf, din perspectiva interfeței utilizatorului, un datasource reprezintă o instanță a clusterei kubernetes, în care sunt implementate următoarele funcționalități (codul sursă este disponibil la link):

  • preluarea datelor din api-server-ul k8s (obținerea listei de namespace-uri, deployment-uri…)
  • proxisarea cererilor către prometheus-datasource (care este ales în setările plugin-ului pentru fiecare cluster specific) și formatarea răspunsurilor pentru utilizarea datelor atât în paginile statice, cât și în panourile de bord.
  • actualizarea datelor pe paginile statice ale plugin-ului (cu un interval de timp stabilit pentru refresh rate).
  • prelucrarea cererilor pentru generarea listei de template-uri în grafana-dashboards (metoda .metriFindQuery())

Dezvoltarea pluginului pentru Grafana: istoria ghinionului acumulat

Dezvoltarea pluginului pentru Grafana: istoria ghinionului acumulat

Dezvoltarea pluginului pentru Grafana: istoria ghinionului acumulat

  • testarea conexiunii cu clusterul k8s final.
testDatasource(){
   let url = '/api/v1/namespaces';
   let _url = this.url;
   if(this.accessViaToken)
       _url += '/__proxy';
   _url += url;
   return this.backendSrv.datasourceRequest({
       url: _url,
       method: "GET",
       headers: {"Content-Type": 'application/json'}
   })
       .then(response => {
           if (response.status === 200) {
               return {status: "success", message: "Sursa de date este OK", title: "Succes"};
           }else{
               return {status: "error", message: "Sursa de date nu este OK", title: "Eroare"};
           }
       }, error => {
           return {status: "error", message: "Sursa de date nu este OK", title: "Eroare"};
       })
}

Un aspect deosebit de interesant, din punctul nostru de vedere, este realizarea mecanismului de autentificare și autorizare pentru datasource. De regulă, din cutie pentru configurarea accesului la sursa finală de date putem utiliza componenta încorporată Grafana — datasourceHttpSettings. Cu ajutorul acestei componente putem configura accesul la sursa de date http, specificând url-ul și setările de bază pentru autentificare/autorizare: nume utilizator-parolă sau client-cert/client-key. Pentru a implementa posibilitatea configurării accesului prin bearer-token (standard de facto pentru k8s), a fost necesar să „manipulăm” puțin.

Pentru rezolvarea acestei probleme, putem utiliza mecanismul încorporat Grafana „Plugin Routes” (mai multe detalii pe pagina oficială de documentație). În setările datasource-ului nostru putem declara un set de reguli de rutare, care vor fi procesate de serverul proxy grafana. De exemplu, pentru fiecare endpoint în parte există posibilitatea de a seta antete sau url-uri cu opțiuni de șablonare, datele pentru care pot fi preluate din câmpurile jsonData și secureJsonData (pentru stocarea parolelor sau tokenurilor într-o formă criptată). În exemplul nostru, solicitările de tip /__proxy/api/v1/namespaces vor fi proxificate la un url de tip
/api/v1/namespaces cu setarea antetului Authorization: Bearer.

Dezvoltarea pluginului pentru Grafana: istoria ghinionului acumulat

Dezvoltarea pluginului pentru Grafana: istoria ghinionului acumulat

Desigur, pentru a lucra cu serverul api k8s avem nevoie de un utilizator cu acces de tip readonly, manifeste pentru crearea căruia le puteți găsi de asemenea în codul sursă al pluginului.

Partea 5: lansare

Dezvoltarea pluginului pentru Grafana: istoria ghinionului acumulat

După ce veți scrie propriul dvs. plugin pentru Grafana, vă veți dori cu siguranță să îl publicați în mod deschis. În Grafana există o bibliotecă de pluginuri, disponibilă la adresa grafana.com/grafana/plugins

Pentru ca pluginul dvs. să fie disponibil în magazinul oficial, trebuie să faceți un PR în acest repository, adăugând în fișierul repo.json un conținut de tip:

Dezvoltarea pluginului pentru Grafana: istoria ghinionului acumulat

unde version — versiunea pluginului dvs., url — linkul către repository, iar commit — hash-ul commit-ului, pe baza căruia va fi disponibilă versiunea specifică a pluginului.

Și la final veți vedea o imagine minunată de tip:

Dezvoltarea pluginului pentru Grafana: istoria ghinionului acumulat

Datele pentru aceasta vor fi extrase automat din Readme.md, Changelog.md și fișierul plugin.json cu descrierea pluginului.

Partea 6: în loc de concluzii

Nu am oprit dezvoltarea pluginului nostru după lansare. Acum lucrăm la monitorizarea corectă a utilizării resurselor nodurilor din cluster, implementarea de noi funcționalități pentru îmbunătățirea experienței utilizatorului și, de asemenea, ne ocupăm de o cantitate mare de feedback primit după instalarea pluginului de la clienții noștri, precum și din problemele raportate pe GitHub (dacă ne lăsați o problemă sau un pull request, voi fi foarte fericit 🙂 ).

Sperăm că acest articol vă va ajuta să înțelegeți un instrument minunat precum Grafana și, poate, să scrieți propriul plugin.

Mulțumesc!)

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster