Ciao a tutti! Qualche mese fa abbiamo lanciato in produzione il nostro nuovo progetto open-source — un plugin Grafana per il monitoraggio di Kubernetes, che abbiamo chiamato . Il codice sorgente del plugin è disponibile su . In questo articolo vogliamo condividere con voi la storia di come abbiamo creato il plugin, quali strumenti abbiamo utilizzato e quali insidie abbiamo incontrato durante lo sviluppo. Andiamo!
Parte 0 — introduzione: come siamo arrivati fin qui?
L'idea di creare un nostro plugin per Grafana è nata del tutto per caso. La nostra azienda è attiva da oltre 10 anni nel monitoraggio di progetti web di varia complessità. In questo tempo abbiamo accumulato una vasta esperienza, interessanti casi studio e competenze nell'uso di diversi sistemi di monitoraggio. A un certo punto ci siamo posti la domanda: «Esiste uno strumento magico per monitorare Kubernetes, che si possa semplicemente installare e dimenticare?»... Lo standard del settore per il monitoraggio di k8s è da tempo la combinazione Prometheus + Grafana. Ci sono molti strumenti di diversi tipi disponibili come soluzioni pronte per questo stack: prometheus-operator, una serie di dashboard kubernetes-mixin, grafana-kubernetes-app.
L'opzione che ci è sembrata più interessante è stata il plugin grafana-kubernetes-app, ma non è supportato da oltre un anno e, inoltre, non è compatibile con le nuove versioni di node-exporter e kube-state-metrics. A un certo punto abbiamo deciso: «Perché non creare una nostra soluzione?»
Quali idee abbiamo deciso di implementare nel nostro plugin:
- visualizzazione della «mappa dell'applicazione»: una rappresentazione conveniente delle applicazioni nel cluster, raggruppate per namespace, deployment…;
- visualizzazione delle relazioni del tipo «deployment — service (+ports)».
- visualizzazione della distribuzione delle applicazioni del cluster sui nodi del cluster.
- raccolta di metriche e informazioni da più fonti: Prometheus e k8s api server.
- monitoraggio sia della parte infrastrutturale (utilizzo del tempo della CPU, memoria, sottosistema disco, rete), sia della logica delle applicazioni — stato di salute dei pod, numero di repliche disponibili, informazioni sui controlli di liveness/readiness.
Parte 1: Cos'è un «plugin per Grafana»?
Dal punto di vista tecnico, un plugin per Grafana è un controller Angular memorizzato nella directory data di Grafana (/var/grafana/plugins/<your_plugin_name>/dist/module.js) e può essere caricato come modulo SystemJS. In questa directory deve trovarsi anche un file plugin.json, che contiene tutte le metainformazioni sul tuo plugin: nome, versione, tipo di plugin, link al repository/sito/licenza, dipendenze e così via.

module.ts

plugin.json
Come si può vedere nello screenshot, abbiamo specificato plugin.type = app. Poiché i plugin per Grafana possono essere di tre tipi:
panel: il tipo di plugin più comune — consiste in un pannello per visualizzare metriche, utilizzato per costruire vari dashboard.
datasource: plugin connettore per una fonte di dati (ad esempio, Prometheus-datasource, ClickHouse-datasource, ElasticSearch-datasource).
app: plugin che consente di costruire la propria applicazione frontend all'interno di Grafana, creare le proprie pagine html e accedere manualmente alla fonte dati per visualizzare diverse informazioni. Possono essere utilizzati anche plugin di altri tipi (datasource, panel) e vari dashboard come dipendenze.

Esempio di dipendenze di un plugin con type = app.
Come linguaggio di programmazione si possono usare sia JavaScript che TypeScript (noi abbiamo scelto quest'ultimo). I template per i plugin hello-world di qualsiasi tipo possono essere : in questo repository è presente un'ampia gamma di starter-pack (c'è persino un esempio sperimentale di plugin in React) con builder preinstallati e configurati.
Parte 2: preparazione dell'ambiente locale
Per lavorare con il plugin, avremo naturalmente bisogno di un cluster Kubernetes con tutti gli strumenti preinstallati: Prometheus, Node Exporter, Kube State Metrics e Grafana. L'ambiente deve configurarsi in modo rapido, semplice e senza sforzo, e per garantire il hot-reload, la directory dei dati di Grafana deve essere montata direttamente dalla macchina dello sviluppatore.
A nostro avviso, il modo più comodo per lavorare localmente con Kubernetes è . Il passo successivo è installare l'insieme Prometheus + Grafana utilizzando prometheus-operator. In è descritto dettagliatamente il processo di installazione di prometheus-operator su Minikube. Per abilitare la persistenza, è necessario impostare il parametro persistence: true nel file charts/grafana/values.yaml, aggiungere il proprio PV e PVC e specificarli nel parametro persistence.existingClaim.
Lo script finale per avviare Minikube appare così:
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.LParte 3: sviluppo diretto
Modello ad oggetti
In preparazione per l'implementazione del plugin, abbiamo deciso di descrivere tutte le entità di base di Kubernetes, con cui lavoreremo sotto forma di classi TypeScript: pod, deployment, daemonset, statefulset, job, cronjob, service, node, namespace. Ciascuna di queste classi eredita da una classe comune BaseModel, in cui sono descritti il costruttore, il distruttore e i metodi per l'aggiornamento e la commutazione della visibilità. In ogni classe sono descritte le relazioni incorporate con altre entità, ad esempio, l'elenco dei pod per l'entità di tipo 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 = [];
}
}Con gettori e settori possiamo visualizzare o impostare le metriche necessarie delle entità in un modo semplice e leggibile. Ad esempio, l'output formattato dell'allocatable cpu del nodo:
get cpuAllocatableFormatted(){
let cpu = this.data.status.allocatable.cpu;
if(cpu.indexOf('m') > -1){
cpu = parseInt(cpu)/1000;
}
return cpu;
}Pagine
L'elenco di tutte le pagine del nostro plugin è descritto inizialmente nel nostro pluing.json nella sezione dipendenze:

Nella sezione dedicata a ciascuna pagina, dobbiamo specificare il NOME DELLA PAGINA (che verrà poi convertito in uno slug tramite cui sarà accessibile); il nome del componente responsabile di questa pagina (l'elenco dei componenti è esportato in module.ts); e l'indicazione del ruolo utente per il quale è disponibile il lavoro con questa pagina e le impostazioni di navigazione per la barra laterale.
Nel componente responsabile della pagina, dobbiamo impostare templateUrl, passando lì il percorso del file html con il markup. All'interno del controller, tramite dependency injection, possiamo accedere a 2 importanti servizi Angular:
- backendSrv — il servizio che consente l'interazione con l'api-server di Grafana;
- datasourceSrv — il servizio che consente l'interazione locale con tutte le datasource installate nella vostra Grafana (ad esempio, il metodo .getAll() restituisce un elenco di tutte le datasource installate; .get() restituisce l'oggetto istanza della specifica datasource.



Parte 4: datasource
Dal punto di vista di Grafana, un datasource è un plugin come tutti gli altri: ha il proprio punto d'ingresso module.js e un file di metainformazione plugin.json. Quando sviluppiamo un plugin con type = app, possiamo interagire sia con i datasource già esistenti (come prometheus-datasource) che con i nostri, che possiamo memorizzare direttamente nella directory del plugin (dist/datasource/*) o installare come dipendenza. Nel nostro caso, il datasource viene fornito insieme al codice del plugin. È essenziale avere anche un modello config.html e un controller ConfigCtrl, che saranno utilizzati per la pagina di configurazione dell'istanza del datasource e del controller Datasource, in cui viene implementata la logica operativa del tuo datasource.
Nel plugin KubeGraf, dal punto di vista dell'interfaccia utente, un datasource rappresenta un'istanza di un cluster Kubernetes, in cui sono implementate le seguenti funzionalità (il codice sorgente è disponibile ):
- recupero dati dall'api-server k8s (ottenimento dell'elenco dei namespace, dei deployment...)
- il proxy delle richieste in prometheus-datasource (che viene selezionato nelle impostazioni del plugin per ogni singolo cluster) e la formattazione delle risposte per utilizzare i dati sia nelle pagine statiche che nei dashboard.
- l'aggiornamento dei dati nelle pagine statiche del plugin (con un tempo di refresh rate impostato).
- la gestione delle richieste per la formazione della lista dei template nei grafana-dashboards (metodo .metriFindQuery())



- 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 aspetto interessante, a nostro avviso, è l'implementazione del meccanismo di autenticazione e autorizzazione per il datasource. Di norma, per configurare l'accesso alla fonte di dati finale, possiamo utilizzare il componente integrato di Grafana — datasourceHttpSettings. Grazie a questo componente, possiamo impostare l'accesso a una fonte di dati http, specificando l'url e le impostazioni di autenticazione/ autorizzazione: nome utente-password, oppure client-cert/client-key. Per realizzare la possibilità di configurare l'accesso tramite bearer token (de facto standard per k8s), è stato necessario fare un po' di "manipolazione".
Per risolvere questo problema, si può utilizzare il meccanismo integrato di Grafana "Plugin Routes" (maggiori dettagli sulla ). Nelle impostazioni del nostro datasource, possiamo dichiarare un insieme di regole di routing che saranno elaborate dal server proxy di Grafana. Ad esempio, per ogni singolo endpoint esiste la possibilità di impostare intestazioni o url con la possibilità di templating, i dati per i quali possono essere prelevati dai campi jsonData e secureJsonData (per memorizzare password o token in forma criptata). Nel nostro esempio, le richieste di tipo /__proxy/api/v1/namespaces saranno proxied su un URL del tipo
/api/v1/namespaces impostando l'intestazione Authorization: Bearer .


Naturalmente, per interagire con l'api-server k8s è necessario un utente con accessi readonly, dei manifesti per crearne uno puoi trovarli anche nel .
Parte 5: rilascio

Dopo aver scritto il tuo plugin per Grafana, naturalmente, vorrai pubblicarlo in modalità open source. In Grafana esiste una libreria di plugin, disponibile al link
Per rendere il tuo plugin disponibile nel negozio ufficiale, è necessario fare una PR in , aggiungendo al file repo.json un contenuto del tipo:

dove version è la versione del tuo plugin, url è il link al repository, e commit è l'hash del commit che renderà disponibile una specifica versione del plugin.
E alla fine vedrai un'immagine meravigliosa del tipo:

I dati per essa saranno automaticamente estratti dal tuo Readme.md, Changelog.md e file plugin.json con la descrizione del plugin.
Parte 6: invece delle conclusioni
Non abbiamo interrotto lo sviluppo del nostro plugin dopo il rilascio. Attualmente stiamo lavorando su un monitoraggio accurato dell'utilizzo delle risorse dei nodi del cluster, implementando nuove funzionalità per migliorare l'UX e gestendo un gran numero di feedback ricevuti dopo le installazioni del plugin sia dai nostri clienti che attraverso le segnalazioni su GitHub (se lasci un tuo issue o pull request, sarò molto felice 🙂 ).
Speriamo che questo articolo ti aiuti a capire questo straordinario strumento chiamato Grafana e, magari, a scrivere il tuo plugin.
Grazie!)
Fonte: habr.com
