Sviluppo di un plugin per Grafana: una storia di pezzi grossi

Ciao a tutti! Alcuni mesi fa abbiamo lanciato in produzione il nostro nuovo progetto open source: il plugin Grafana per il monitoraggio di Kubernetes, che abbiamo chiamato DevOpsProdigy KubeGraf. Il codice sorgente del plugin è disponibile su repository pubblico su GitHub. E in questo articolo vogliamo condividere con te la storia di come abbiamo creato il plugin, quali strumenti abbiamo utilizzato e quali insidie ​​abbiamo incontrato durante il processo di sviluppo. Andiamo!

Parte 0 - introduttiva: come siamo arrivati ​​a questo punto?

L'idea di scrivere il nostro plugin per Grafan ci è venuta quasi per caso. La nostra azienda monitora progetti web di vari livelli di complessità da oltre 10 anni. Durante questo periodo, abbiamo accumulato una grande quantità di competenze, casi interessanti ed esperienza nell'utilizzo di vari sistemi di monitoraggio. E ad un certo punto ci siamo chiesti: “Esiste uno strumento magico per monitorare Kubernetes, così che, come si suol dire, “impostalo e dimenticalo”?”.. Lo standard industriale per il monitoraggio di k8, ovviamente, è da tempo il Combinazione Prometeo + Grafana. E come soluzioni già pronte per questo stack, esiste un ampio set di vari tipi di strumenti: prometheus-operator, un set di dashboard kubernetes-mixin, grafana-kubernetes-app.

Il plugin grafana-kubernetes-app ci è sembrato l'opzione più interessante, ma non è supportato da più di un anno e, inoltre, non può funzionare con le nuove versioni di node-exporter e kube-state-metrics. E ad un certo punto abbiamo deciso: “Non dovremmo prendere la nostra decisione?”

Quali idee abbiamo deciso di implementare nel nostro plugin:

  • visualizzazione della “mappa delle applicazioni”: comoda presentazione delle applicazioni nel cluster, raggruppate per namespace, deploy...;
  • visualizzazione delle connessioni come “deployment - service (+ports)”.
  • visualizzazione della distribuzione delle applicazioni cluster tra i nodi del cluster.
  • raccolta di metriche e informazioni da diverse fonti: Prometheus e server API k8s.
  • monitoraggio sia della parte infrastrutturale (utilizzo del tempo della CPU, memoria, sottosistema disco, rete) che della logica applicativa: pod dello stato di integrità, numero di repliche disponibili, informazioni sul superamento dei test di liveness/readyness.

Parte 1: Cos'è un “plug-in Grafana”?

Da un punto di vista tecnico, il plugin per Grafana è un controller angolare, che viene memorizzato nella directory dei dati Grafana (/var/grafana/plugins/ /dist/module.js) e può essere caricato come modulo SystemJS. Inoltre in questa directory dovrebbe essere presente un file plugin.json contenente tutte le metainformazioni sul tuo plugin: nome, versione, tipo di plugin, collegamenti al repository/sito/licenza, dipendenze e così via.

Sviluppo di un plugin per Grafana: una storia di pezzi grossi
modulo.ts

Sviluppo di un plugin per Grafana: una storia di pezzi grossi
plugin.json

Come puoi vedere nello screenshot, abbiamo specificato plugin.type = app. Perché i plugin per Grafana possono essere di tre tipi:

pannello di eventi: il tipo di plugin più comune: è un pannello per la visualizzazione di eventuali metriche, utilizzato per costruire varie dashboard.
fonte di dati: connettore plug-in per alcune origini dati (ad esempio, Prometheus-datasource, ClickHouse-datasource, ElasticSearch-datasource).
App: Un plugin che ti consente di creare la tua applicazione frontend all'interno di Grafana, creare le tue pagine html e accedere manualmente all'origine dati per visualizzare vari dati. Inoltre, come dipendenze possono essere utilizzati plugin di altro tipo (origine dati, pannello) e vari dashboard.

Sviluppo di un plugin per Grafana: una storia di pezzi grossi
Dipendenze del plugin di esempio con type=app.

Puoi utilizzare sia JavaScript che TypeScript come linguaggio di programmazione (noi lo abbiamo scelto). Preparativi per plugin Hello-World di qualsiasi tipo possibile trova tramite link: questo repository contiene un gran numero di pacchetti iniziali (c'è anche un esempio sperimentale di plugin in React) con builder preinstallati e configurati.

Parte 2: preparare l'ambiente locale

Per lavorare sul plugin, abbiamo naturalmente bisogno di un cluster Kubernetes con tutti gli strumenti preinstallati: prometheus, node-exporter, kube-state-metrics, grafana. L'ambiente dovrebbe essere configurato in modo rapido, semplice e naturale e, per garantire il ricaricamento a caldo, la directory dei dati Grafana dovrebbe essere montata direttamente dal computer dello sviluppatore.

Il modo più conveniente, a nostro avviso, per lavorare localmente con Kubernetes è minikube. Il prossimo passo è installare la combinazione Prometheus + Grafana utilizzando prometheus-operator. IN questo articolo Il processo di installazione di Prometheus-Operator su Minikube è descritto in dettaglio. Per abilitare la persistenza, è necessario impostare il parametro persistenza: vero nel file charts/grafana/values.yaml, aggiungi i tuoi PV e PVC e specificali nel parametro persistence.existingClaim

Il nostro script finale di lancio di minikube è simile al seguente:

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

Parte 3: sviluppo reale

Modello oggetto

In preparazione all'implementazione del plugin, abbiamo deciso di descrivere tutte le entità Kubernetes di base con cui lavoreremo sotto forma di classi TypeScript: pod, deploy, daemonset, statefulset, job, cronjob, service, node, namespace. Ognuna di queste classi eredita dalla classe BaseModel comune, che descrive il costruttore, il distruttore, i metodi per l'aggiornamento e il cambio di visibilità. Ciascuna classe descrive relazioni nidificate con altre entità, ad esempio un elenco di pod per un'entità di tipo deploy.

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

export class Deployment extends BaseModel{
   pods: Array<Pod>;
   services: Array<Service>;

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

Con l'aiuto di getter e setter, possiamo visualizzare o impostare le metriche di entità di cui abbiamo bisogno in una forma comoda e leggibile. Ad esempio, output formattato di nodi CPU allocabili:

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

Pagine

Un elenco di tutte le pagine dei nostri plugin è inizialmente descritto nel nostro pluing.json nella sezione delle dipendenze:

Sviluppo di un plugin per Grafana: una storia di pezzi grossi

Nel blocco di ogni pagina dobbiamo indicare il NOME PAGINA (verrà poi convertito in uno slug attraverso il quale questa pagina sarà accessibile); il nome del componente responsabile del funzionamento di questa pagina (l'elenco dei componenti viene esportato in module.ts); indicando il ruolo utente per il quale è disponibile il lavoro con questa pagina e le impostazioni di navigazione per la barra laterale.

Nel componente responsabile del funzionamento della pagina, dobbiamo impostare templateUrl, passando lì il percorso del file html con markup. All'interno del controller, tramite dependency injection, possiamo accedere fino a 2 importanti servizi angolari:

  • backendSrv: un servizio che fornisce l'interazione con il server API Grafana;
  • datasourceSrv - un servizio che fornisce l'interazione locale con tutte le origini dati installate in Grafana (ad esempio, il metodo .getAll() - restituisce un elenco di tutte le origini dati installate; .get( ) - restituisce un oggetto istanza di un'origine dati specifica.

Sviluppo di un plugin per Grafana: una storia di pezzi grossi

Sviluppo di un plugin per Grafana: una storia di pezzi grossi

Sviluppo di un plugin per Grafana: una storia di pezzi grossi

Parte 4: fonte dei dati

Dal punto di vista di Grafana, datasource è esattamente lo stesso plugin di tutti gli altri: ha il proprio punto di ingresso module.js, c'è un file con meta informazioni plugin.json. Quando sviluppiamo un plugin con type = app, possiamo interagire sia con le origini dati esistenti (ad esempio, prometheus-datasource) sia con le nostre, che possiamo archiviare direttamente nella directory dei plugin (dist/datasource/*) o installare come dipendenza. Nel nostro caso, l'origine dati viene fornita con il codice del plugin. È inoltre necessario disporre di un modello config.html e di un controller ConfigCtrl, che verrà utilizzato per la pagina di configurazione dell'istanza dell'origine dati e del controller Datasource, che implementa la logica operativa dell'origine dati.

Nel plugin KubeGraf, dal punto di vista dell'interfaccia utente, l'origine dati è un'istanza di un cluster Kubernetes che implementa le seguenti funzionalità (il codice sorgente è disponibile collegamento):

  • raccogliere dati dal server API k8s (ottenendo un elenco di spazi dei nomi, distribuzioni...)
  • inoltro delle richieste a prometheus-datasource (che è selezionato nelle impostazioni del plugin per ciascun cluster specifico) e formattazione delle risposte per utilizzare i dati sia nelle pagine statiche che nei dashboard.
  • aggiornamento dei dati sulle pagine dei plugin statici (con una frequenza di aggiornamento impostata).
  • elaborazione delle query per generare un foglio modello in grafana-dashboards (metodo metriFindQuery())

Sviluppo di un plugin per Grafana: una storia di pezzi grossi

Sviluppo di un plugin per Grafana: una storia di pezzi grossi

Sviluppo di un plugin per Grafana: una storia di pezzi grossi

  • test di connessione con il cluster k8s finale.
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: "Data source is OK", title: "Success"};
           }else{
               return {status: "error", message: "Data source is not OK", title: "Error"};
           }
       }, error => {
           return {status: "error", message: "Data source is not OK", title: "Error"};
       })
}

Un punto interessante a parte, a nostro avviso, è l'implementazione di un meccanismo di autenticazione e autorizzazione per l'origine dati. In genere, immediatamente, possiamo utilizzare il componente Grafana integrato datasourceHttpSettings per configurare l'accesso all'origine dati finale. Utilizzando questo componente, possiamo configurare l'accesso all'origine dati http specificando l'URL e le impostazioni di autenticazione/autorizzazione di base: login-password o client-cert/client-key. Per implementare la possibilità di configurare l'accesso utilizzando un token al portatore (lo standard di fatto per k8), abbiamo dovuto modificarlo leggermente.

Per risolvere questo problema, è possibile utilizzare il meccanismo integrato di Grafana “Plugin Routes” (maggiori dettagli su pagina della documentazione ufficiale). Nelle impostazioni della nostra origine dati, possiamo dichiarare una serie di regole di routing che verranno elaborate dal server proxy grafana. Ad esempio, per ogni singolo endpoint è possibile impostare header o url con possibilità di template, i cui dati possono essere prelevati dai campi jsonData e secureJsonData (per la memorizzazione di password o token in forma crittografata). Nel nostro esempio, query come /__proxy/api/v1/namespaces verrà proxy all'URL del modulo
/api/v8/namespaces con l'autorizzazione: intestazione Bearer.

Sviluppo di un plugin per Grafana: una storia di pezzi grossi

Sviluppo di un plugin per Grafana: una storia di pezzi grossi

Naturalmente per lavorare con il server API k8s abbiamo bisogno di un utente con accesso in sola lettura, manifesti per la creazione che potete trovare anche in codice sorgente del plugin.

Parte 5: rilascio

Sviluppo di un plugin per Grafana: una storia di pezzi grossi

Dopo aver scritto il tuo plugin Grafana, vorrai naturalmente renderlo disponibile pubblicamente. In Grafana questa è una libreria di plugin disponibile qui grafana.com/grafana/plugins

Affinché il tuo plugin sia disponibile sullo store ufficiale, devi effettuare un PR questo depositoaggiungendo contenuto come questo al file repo.json:

Sviluppo di un plugin per Grafana: una storia di pezzi grossi

dove versione è la versione del tuo plugin, url è un collegamento al repository e commit è l'hash del commit per il quale sarà disponibile una versione specifica del plugin.

E all'output vedrai un'immagine meravigliosa come:

Sviluppo di un plugin per Grafana: una storia di pezzi grossi

I relativi dati verranno automaticamente prelevati dal file Readme.md, Changelog.md e dal file plugin.json con la descrizione del plugin.

Parte 6: invece delle conclusioni

Non abbiamo interrotto lo sviluppo del nostro plugin dopo il rilascio. E ora stiamo lavorando per monitorare correttamente l'uso delle risorse dei nodi del cluster, introducendo nuove funzionalità per migliorare la UX e anche raccogliendo una grande quantità di feedback ricevuti dopo l'installazione del plugin sia dai nostri clienti che dalle persone su GitHub (se lasci il tuo problema o richiesta pull, sarò molto felice :)

Speriamo che questo articolo ti aiuti a comprendere uno strumento meraviglioso come Grafana e, forse, a scrivere il tuo plugin.

Grazie!)

Fonte: habr.com

Aggiungi un commento