Entwicklung eines Plugins für Grafana: Die Geschichte der gesammelten Erfahrungen

Hallo zusammen! Vor einigen Monaten haben wir unser neues Open-Source-Projekt in die Produktion gebracht – ein Grafana-Plugin zur Überwachung von Kubernetes, das wir DevOpsProdigy KubeGrafgenannt haben. Der Quellcode des Plugins ist in einem öffentlichen Repository auf GitHubverfügbar. In diesem Artikel möchten wir unsere Geschichte darüber teilen, wie wir das Plugin erstellt haben, welche Werkzeuge wir verwendet haben und auf welche Stolpersteine wir während der Entwicklung gestoßen sind. Legen wir los!

Teil 0 – Einleitung: Wie sind wir dazu gekommen?

Die Idee, unser eigenes Plugin für Grafana zu entwickeln, kam uns ganz zufällig. Unser Unternehmen beschäftigt sich seit über 10 Jahren mit der Überwachung von Webprojekten unterschiedlicher Komplexität. In dieser Zeit haben wir ein großes Maß an Expertise, interessante Fälle und Erfahrungen mit verschiedenen Überwachungssystemen gesammelt. Und irgendwann stellten wir uns die Frage: „Gibt es ein magisches Werkzeug zur Überwachung von Kubernetes, das man, wie man so schön sagt, ‚einmal aufstellt und vergisst‘?“ Der Standard für die Überwachung von k8s ist seit langem die Kombination Prometheus + Grafana. Für diesen Stack gibt es eine große Auswahl an verschiedenen Tools: prometheus-operator, eine Sammlung von Dashboards kubernetes-mixin, grafana-kubernetes-app.

Die interessanteste Variante für uns schien das Plugin grafana-kubernetes-app zu sein, aber es wird seit über einem Jahr nicht mehr unterstützt und kann zudem nicht mit den neuen Versionen von node-exporter und kube-state-metrics arbeiten. Irgendwann entschieden wir uns: „Warum nicht unser eigenes Lösung entwickeln?“

Welche Ideen wollten wir in unserem Plugin umsetzen:

  • Visualisierung der ‚Anwendungskarte‘: eine bequeme Darstellung der Anwendungen im Cluster, gruppiert nach Namespaces, Deployments…;
  • Visualisierung von Beziehungen wie ‚Deployment – Service (+Ports)‘.
  • Visualisierung der Verteilung von Anwendungen im Cluster auf den Knoten des Clusters.
  • Sammeln von Metriken und Informationen aus mehreren Quellen: Prometheus und k8s API-Server.
  • Überwachung sowohl des infrastrukturellen Anteils (Nutzung von CPU-Zeit, Speicher, Speichersystem, Netzwerk) als auch der Anwendungslogik – health-status der Pods, Anzahl der verfügbaren Replikate, Informationen über das Bestehen von Liveness-/Readyness-Checks.

Teil 1: Was ist ein ‚Plugin für Grafana‘?

Aus technischer Sicht ist ein Plugin für Grafana ein Angular-Controller, der im data-Verzeichnis von Grafana gespeichert wird (/var/grafana/plugins/<your_plugin_name>/dist/module.js) und kann als SystemJS-Modul geladen werden. Außerdem sollte in diesem Verzeichnis eine Datei plugin.json vorhanden sein, die alle Metainformationen über Ihr Plugin enthält: Name, Version, Typ des Plugins, Links zu Repository/Site/Lizenz, Abhängigkeiten usw.

Entwicklung eines Plugins für Grafana: Die Geschichte der gesammelten Erfahrungen
module.ts

Entwicklung eines Plugins für Grafana: Die Geschichte der gesammelten Erfahrungen
plugin.json

Wie auf dem Screenshot zu sehen, haben wir plugin.type = app angegeben. Plugins für Grafana können drei Arten haben:

panel: Der am weitesten verbreitete Plugintyp – stellt ein Panel zur Visualisierung von Metriken dar, das zur Erstellung verschiedener Dashboards verwendet wird.
Datenquelle: Ein Connector-Plugin zu einer Datenquelle (z. B. Prometheus-Datenquelle, ClickHouse-Datenquelle, ElasticSearch-Datenquelle).
App: Ein Plugin, das es Ihnen ermöglicht, Ihre eigene Frontend-Anwendung innerhalb von Grafana zu erstellen, eigene HTML-Seiten zu erstellen und manuell auf die Datenquelle zuzugreifen, um verschiedene Daten zu visualisieren. Zudem können als Abhängigkeiten Plugins anderer Typen (Datenquelle, Panel) und verschiedene Dashboards verwendet werden.

Entwicklung eines Plugins für Grafana: Die Geschichte der gesammelten Erfahrungen
Beispiel für Plugin-Abhängigkeiten mit type = app.

Als Programmiersprache können sowohl JavaScript als auch TypeScript verwendet werden (wir haben uns für Letzteres entschieden). Vorlagen für Hello-World-Plugins jeglicher Art können Sie unter dem folgenden Link finden: In diesem Repository sind viele Starter-Packs vorhanden (es gibt sogar ein experimentelles Plugin-Beispiel auf React) mit vorinstallierten und konfigurierten Build-Tools.

Teil 2: Vorbereitung der lokalen Umgebung

Für die Arbeit an dem Plugin benötigen wir natürlich einen Kubernetes-Cluster mit allen vorinstallierten Tools: Prometheus, Node-Exporter, Kube-State-Metrics, Grafana. Die Umgebung sollte schnell, einfach und unkompliziert eingerichtet werden können, und für die Gewährleistung von Hot-Reload sollte das Datenverzeichnis von Grafana direkt vom Entwicklungsrechner gemountet werden.

Die bequemste lokale Arbeitsweise mit Kubernetes ist unserer Meinung nach minikube. Im nächsten Schritt installieren wir das Bundle Prometheus + Grafana mit Hilfe des Prometheus-Operators. In diesem Artikel ist der Prozess zur Installation des Prometheus-Operators auf Minikube ausführlich beschrieben. Um Persistenz zu aktivieren, muss der Parameter persistence: true in der Datei charts/grafana/values.yaml festgelegt werden, ebenso müssen Sie Ihren eigenen PV und PVC hinzufügen und diese im Parameter persistence.existingClaim angeben.

Das endgültige Skript zum Starten von Minikube sieht folgendermaßen aus:

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

Teil 3: Entwicklung

Objektmodell

Als Vorbereitung für die Implementierung des Plugins haben wir beschlossen, alle grundlegenden Kubernetes-Einheiten, mit denen wir arbeiten werden, in Form von TypeScript-Klassen zu beschreiben: pod, deployment, daemonset, statefulset, job, cronjob, service, node, namespace. Jede dieser Klassen erbt von der gemeinsamen Klasse BaseModel, in der der Konstruktor, Destruktor und Methoden zur Aktualisierung und Sichtbarkeitswechsel beschrieben sind. In jeder der Klassen sind die eingebetteten Beziehungen zu anderen Entitäten beschrieben, zum Beispiel die Liste der Pods bei der Entität des Typs 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 = [];
   }
}

Mit Hilfe von Getter und Setter können wir die benötigten Metriken der Entitäten in einem praktischen und lesbaren Format ausgeben oder setzen. Zum Beispiel die formatierte Ausgabe des allocatable CPU der Node:

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

Seiten

Die Liste aller Seiten unseres Plugins wird ursprünglich in unserer pluing.json im Abschnitt Abhängigkeiten beschrieben:

Entwicklung eines Plugins für Grafana: Die Geschichte der gesammelten Erfahrungen

Im Block für jede Seite müssen wir den NAME DER SEITE angeben (dieser wird dann in einen Slug umgewandelt, über den diese Seite verfügbar sein wird); den Namen der Komponente, die für die Arbeit mit dieser Seite verantwortlich ist (Liste der Komponenten wird in module.ts exportiert); die Rolle des Benutzers, für den die Arbeit mit dieser Seite verfügbar ist, und Einstellungen für die Navigation in der Seitenleiste.

In der Komponente, die für die Arbeit mit der Seite verantwortlich ist, müssen wir templateUrl festlegen und den Pfad zur html-Datei mit dem Markup übergeben. Innerhalb des Controllers können wir durch Dependency Injection auf zwei wichtige Angular-Dienste zugreifen:

  • backendSrv — Dienst, der die Interaktion mit dem API-Server von Grafana gewährleistet;
  • datasourceSrv — Dienst, der die lokale Interaktion mit allen in Ihrer Grafana installierten Datenquellen ermöglicht (z. B. gibt die Methode .getAll() eine Liste aller installierten Datenquellen zurück; .get() gibt das Objekt-Instanz einer bestimmten Datenquelle zurück).

Entwicklung eines Plugins für Grafana: Die Geschichte der gesammelten Erfahrungen

Entwicklung eines Plugins für Grafana: Die Geschichte der gesammelten Erfahrungen

Entwicklung eines Plugins für Grafana: Die Geschichte der gesammelten Erfahrungen

Teil 4: Datenquelle

Aus Sicht von Grafana stellt die Datenquelle ein Plugin dar, das genau wie alle anderen Plugins funktioniert: Es hat einen eigenen Einstiegspunkt module.js und eine Datei mit Metainformationen plugin.json. Bei der Entwicklung eines Plugins mit type = app können wir sowohl mit bestehenden Datenquellen (z.B. prometheus-datasource) als auch mit eigenen arbeiten, die wir direkt im Plugin-Verzeichnis (dist/datasource/*) speichern oder als Abhängigkeit installieren können. In unserem Fall wird die Datenquelle zusammen mit dem Plugin-Code bereitgestellt. Es ist auch erforderlich, ein Template config.html und den Controller ConfigCtrl zu haben, die für die Konfigurationsseite der Instanz der Datenquelle und den Controller Datasource verwendet werden, in dem die Logik für die Funktionsweise Ihrer Datenquelle implementiert ist.

Im KubeGraf-Plugin stellt die Datenquelle aus Sicht der Benutzeroberfläche eine Instanz eines Kubernetes-Clusters dar, in dem folgende Funktionen implementiert sind (der Quellcode ist verfügbar unter diesem Link):

  • Abrufen von Daten vom API-Server von k8s (Abrufen der Liste der Namespace, Deployments…)
  • Proxying von Anfragen an die prometheus-datasource (die in den Plugin-Einstellungen für jeden bestimmten Cluster ausgewählt wird) und das Formatieren von Antworten für die Verwendung der Daten sowohl in statischen Seiten als auch in Dashboards.
  • Aktualisierung von Daten auf den statischen Seiten des Plugins (mit festgelegtem Refresh-Intervall).
  • Verarbeitung von Anfragen zur Erstellung einer Template-Liste in Grafana-Dashboards (Methode .metriFindQuery())

Entwicklung eines Plugins für Grafana: Die Geschichte der gesammelten Erfahrungen

Entwicklung eines Plugins für Grafana: Die Geschichte der gesammelten Erfahrungen

Entwicklung eines Plugins für Grafana: Die Geschichte der gesammelten Erfahrungen

  • Test der Verbindung mit dem endgültigen k8s-Cluster.
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: "Datenquelle ist OK", title: "Erfolg"};
           }else{
               return {status: "error", message: "Datenquelle ist nicht OK", title: "Fehler"};
           }
       }, error => {
           return {status: "error", message: "Datenquelle ist nicht OK", title: "Fehler"};
       })
}

Ein interessanter Punkt ist unserer Meinung nach die Implementierung des Authentifizierungs- und Autorisierungsmechanismus für die Datenquelle. In der Regel können wir für die Konfiguration des Zugriffs auf die endgültige Datenquelle das integrierte Grafana-Komponenten datasourceHttpSettings verwenden. Mit dieser Komponente können wir den Zugang zu einer HTTP-Datenquelle einrichten, indem wir die URL und die grundlegenden Authentifizierungs-/Autorisierungseinstellungen angeben: Benutzername-Passwort oder client-cert/client-key. Um die Möglichkeit zu realisieren, den Zugang über ein Bearer-Token (de facto Standard für k8s) zu konfigurieren, musste man etwas „herumexperimentieren“.

Zur Lösung dieser Aufgabe kann der integrierte Grafana-Mechanismus „Plugin Routes“ verwendet werden (mehr dazu auf der offiziellen Dokumentationsseite). In den Einstellungen unserer Datenquelle können wir eine Reihe von Routing-Regeln deklarieren, die vom Proxy-Server Grafana verarbeitet werden. Beispielsweise besteht für jeden einzelnen Endpunkt die Möglichkeit, Header oder URLs mit der Möglichkeit zur Vorlagenbearbeitung festzulegen, wobei die Daten aus den Feldern jsonData und secureJsonData (zur Speicherung von Passwörtern oder Tokens in verschlüsselter Form). In unserem Beispiel werden Anfragen in der Form /__proxy/api/v1/namespaces an URLs der Form
/api/v1/namespaces mit dem Header Authorization: Bearer angehängt.

Entwicklung eines Plugins für Grafana: Die Geschichte der gesammelten Erfahrungen

Entwicklung eines Plugins für Grafana: Die Geschichte der gesammelten Erfahrungen

Selbstverständlich benötigen wir für die Arbeit mit dem k8s-API-Server einen Benutzer mit Lesezugriff, dessen Manifestdateien Sie ebenfalls im Quellcode des Plugins.

Teil 5: Veröffentlichung

Entwicklung eines Plugins für Grafana: Die Geschichte der gesammelten Erfahrungen

Nachdem Sie Ihr eigenes Plugin für Grafana erstellt haben, möchten Sie es natürlich der Öffentlichkeit zugänglich machen. In Grafana gibt es eine Plugin-Bibliothek, die unter der URL grafana.com/grafana/plugins

verfügbar ist. Damit Ihr Plugin im offiziellen Store verfügbar ist, müssen Sie einen PR in dieses Repository, hinzufügen Sie den Inhalt in die Datei repo.json in der Form:

Entwicklung eines Plugins für Grafana: Die Geschichte der gesammelten Erfahrungen

wobei version die Version Ihres Plugins, url der Link zum Repository und commit der Hash des Commits ist, unter dem eine bestimmte Version des Plugins verfügbar ist.

Und am Ende sehen Sie ein wunderbares Bild wie folgt:

Entwicklung eines Plugins für Grafana: Die Geschichte der gesammelten Erfahrungen

Die Daten hierfür werden automatisch aus Ihrer Readme.md, Changelog.md und der Datei plugin.json mit der Beschreibung des Plugins extrahiert.

Teil 6: anstelle von Schlussfolgerungen

Wir haben die Entwicklung unseres Plugins nach dem Release nicht eingestellt. Momentan arbeiten wir an einer korrekten Überwachung der Ressourcennutzung der Clusterknoten, der Einführung neuer Funktionen zur Verbesserung der Benutzererfahrung sowie der Bearbeitung des umfangreichen Feedbacks, das wir nach den Installationen des Plugins von unseren Kunden und aus Issues auf GitHub erhalten haben (wenn Sie Ihr Issue oder Ihren Pull Request hinterlassen, wäre ich sehr glücklich 🙂 ).

Wir hoffen, dass Ihnen dieser Artikel hilft, sich in diesem wunderbaren Tool wie Grafana zurechtzufinden und vielleicht sogar Ihr eigenes Plugin zu schreiben.

Danke!)

Quelle: habr.com

60GB SSD 8Gb DDR4