Entwicklung eines Plugins für Grafana: eine Geschichte der Großen

Hallo zusammen! Vor einigen Monaten haben wir unser neues Open-Source-Projekt in Produktion gebracht – das Grafana-Plugin zur Überwachung von Kubernetes, das wir nannten DevOpsProdigy KubeGraf. Der Plugin-Quellcode ist verfügbar unter öffentliches Repository auf GitHub. Und in diesem Artikel möchten wir mit Ihnen die Geschichte darüber teilen, wie wir das Plugin erstellt haben, welche Tools wir verwendet haben und auf welche Fallstricke wir während des Entwicklungsprozesses gestoßen sind. Lass uns gehen!

Teil 0 – Einführung: Wie sind wir zu diesem Punkt gekommen?

Die Idee, ein eigenes Plugin für Grafan zu schreiben, kam uns ganz zufällig. Unser Unternehmen betreut seit mehr als 10 Jahren Webprojekte unterschiedlicher Komplexität. In dieser Zeit haben wir viel Fachwissen, interessante Fälle und Erfahrungen im Umgang mit verschiedenen Überwachungssystemen gesammelt. Und irgendwann fragten wir uns: „Gibt es ein magisches Tool zur Überwachung von Kubernetes, so dass man es sozusagen „einstellt und vergisst“?“ Der Industriestandard für die Überwachung von k8s ist natürlich schon lange das Prometheus + Grafana-Kombination. Und als vorgefertigte Lösungen für diesen Stack gibt es eine große Auswahl verschiedener Arten von Tools: Prometheus-Operator, eine Reihe von Kubernetes-Mixin-Dashboards und Grafana-Kubernetes-App.

Das Grafana-kubernetes-app-Plugin schien für uns die interessanteste Option zu sein, aber es wird seit mehr als einem Jahr nicht mehr unterstützt und funktioniert darüber hinaus nicht mit neuen Versionen von Node-Exporter und Kube-State-Metrics. Und irgendwann haben wir beschlossen: „Sollten wir nicht unsere eigene Entscheidung treffen?“

Welche Ideen wir in unserem Plugin umgesetzt haben:

  • Visualisierung der „Anwendungskarte“: komfortable Darstellung der Anwendungen im Cluster, gruppiert nach Namespaces, Bereitstellungen usw.;
  • Visualisierung von Verbindungen wie „Bereitstellung – Dienst (+Ports)“.
  • Visualisierung der Verteilung von Clusteranwendungen über Clusterknoten.
  • Sammlung von Metriken und Informationen aus mehreren Quellen: Prometheus und K8S-API-Server.
  • Überwachung sowohl des Infrastrukturteils (Nutzung von CPU-Zeit, Speicher, Festplatten-Subsystem, Netzwerk) als auch der Anwendungslogik – Gesundheitsstatus-Pods, Anzahl verfügbarer Replikate, Informationen über das Bestehen von Liveness-/Bereitschaftstests.

Teil 1: Was ist ein „Grafana-Plugin“?

Aus technischer Sicht handelt es sich bei dem Plugin für Grafana um einen Angular Controller, der im Grafana-Datenverzeichnis gespeichert ist (/var/grafana/plugins/ /dist/module.js) und kann als SystemJS-Modul geladen werden. Außerdem sollte sich in diesem Verzeichnis eine Datei namens „plugin.json“ befinden, die alle Metainformationen zu Ihrem Plugin enthält: Name, Version, Plugin-Typ, Links zum Repository/zur Site/zur Lizenz, Abhängigkeiten usw.

Entwicklung eines Plugins für Grafana: eine Geschichte der Großen
module.ts

Entwicklung eines Plugins für Grafana: eine Geschichte der Großen
Plugin.json

Wie Sie im Screenshot sehen können, haben wir „plugin.type = app“ angegeben. Denn es gibt drei Arten von Plugins für Grafana:

Tafel: die häufigste Art von Plugin – es handelt sich um ein Panel zur Visualisierung beliebiger Metriken, das zum Erstellen verschiedener Dashboards verwendet wird.
Datenquelle: Plugin-Connector zu einer Datenquelle (z. B. Prometheus-Datenquelle, ClickHouse-Datenquelle, ElasticSearch-Datenquelle).
App: Ein Plugin, mit dem Sie Ihre eigene Front-End-Anwendung in Grafana erstellen, Ihre eigenen HTML-Seiten erstellen und manuell auf die Datenquelle zugreifen können, um verschiedene Daten zu visualisieren. Auch Plugins anderer Typen (Datenquelle, Panel) und verschiedene Dashboards können als Abhängigkeiten verwendet werden.

Entwicklung eines Plugins für Grafana: eine Geschichte der Großen
Beispiel-Plugin-Abhängigkeiten mit type=app.

Sie können sowohl JavaScript als auch TypeScript als Programmiersprache verwenden (wir haben es ausgewählt). Vorbereitungen für Hello-World-Plugins jeglicher Art finde den Link: Dieses Repository enthält eine große Anzahl von Starterpaketen (es gibt sogar ein experimentelles Beispiel eines Plugins in React) mit vorinstallierten und konfigurierten Buildern.

Teil 2: Vorbereitung der lokalen Umgebung

Um an dem Plugin arbeiten zu können, 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 natürlich eingerichtet werden. Um ein Hot-Reload zu gewährleisten, sollte das Grafana-Datenverzeichnis direkt vom Computer des Entwicklers bereitgestellt werden.

Der bequemste Weg ist unserer Meinung nach, lokal mit Kubernetes zu arbeiten Minikube. Der nächste Schritt besteht darin, die Prometheus + Grafana-Kombination mit dem Prometheus-Operator zu installieren. IN dieser Artikel Der Prozess der Installation von Prometheus-Operator auf Minikube wird ausführlich beschrieben. Um die Persistenz zu aktivieren, müssen Sie den Parameter festlegen Beharrlichkeit: wahr Fügen Sie in der Datei charts/grafana/values.yaml Ihren eigenen PV und PVC hinzu und geben Sie diese im Parameter persistence.existingClaim an

Unser endgültiges Minikube-Startskript 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: Tatsächliche Entwicklung

Objektmodell

Zur Vorbereitung der Implementierung des Plugins haben wir beschlossen, alle grundlegenden Kubernetes-Entitäten, 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 BaseModel-Klasse, die den Konstruktor, Destruktor und Methoden zum Aktualisieren und Umschalten der Sichtbarkeit beschreibt. Jede der Klassen beschreibt verschachtelte Beziehungen mit anderen Entitäten, beispielsweise eine Liste von Pods für eine Entität vom Typ „Bereitstellung“.

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 = [];
   }
}

Mit Hilfe von Gettern und Settern können wir die benötigten Entitätsmetriken in einer bequemen und lesbaren Form anzeigen oder festlegen. Beispielsweise formatierte Ausgabe zuweisbarer CPU-Knoten:

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

Seiten

Eine Liste aller unserer Plugin-Seiten finden Sie zunächst in unserer pluing.json im Abschnitt „Abhängigkeiten“:

Entwicklung eines Plugins für Grafana: eine Geschichte der Großen

Im Block für jede Seite müssen wir den SEITENAMEN angeben (er wird dann in einen Slug umgewandelt, über den auf diese Seite zugegriffen werden kann); der Name der Komponente, die für den Betrieb dieser Seite verantwortlich ist (die Liste der Komponenten wird nach module.ts exportiert); Angabe der Benutzerrolle, für die die Arbeit mit dieser Seite verfügbar ist, und Navigationseinstellungen für die Seitenleiste.

In der Komponente, die für den Betrieb der Seite verantwortlich ist, müssen wir templateUrl festlegen und dort den Pfad zur HTML-Datei mit Markup übergeben. Innerhalb des Controllers können wir durch Abhängigkeitsinjektion auf bis zu zwei wichtige Winkeldienste zugreifen:

  • backendSrv – ein Dienst, der die Interaktion mit dem Grafana-API-Server ermöglicht;
  • datasourceSrv – ein Dienst, der lokale Interaktion mit allen in Ihrem Grafana installierten Datenquellen bereitstellt (z. B. die Methode .getAll() – gibt eine Liste aller installierten Datenquellen zurück; .get( ) – gibt ein Instanzobjekt einer bestimmten Datenquelle zurück.

Entwicklung eines Plugins für Grafana: eine Geschichte der Großen

Entwicklung eines Plugins für Grafana: eine Geschichte der Großen

Entwicklung eines Plugins für Grafana: eine Geschichte der Großen

Teil 4: Datenquelle

Aus der Sicht von Grafana ist die Datenquelle genau das gleiche Plugin wie alle anderen: Sie verfügt über einen eigenen Einstiegspunkt module.js, es gibt eine Datei mit Metainformationenplugin.json. Bei der Entwicklung eines Plugins mit type = app können wir sowohl mit vorhandenen Datenquellen (zum Beispiel prometheus-datasource) als auch mit unseren eigenen interagieren, die wir direkt im Plugin-Verzeichnis (dist/datasource/*) speichern oder als Abhängigkeit installieren können. In unserem Fall wird die Datenquelle mit dem Plugin-Code geliefert. Es ist außerdem erforderlich, über eine config.html-Vorlage und einen ConfigCtrl-Controller zu verfügen, der für die Konfigurationsseite der Datenquelleninstanz verwendet wird, sowie über den Datenquellen-Controller, der die Logik Ihrer Datenquelle implementiert.

Im KubeGraf-Plugin ist die Datenquelle aus Sicht der Benutzeroberfläche eine Instanz eines Kubernetes-Clusters, der die folgenden Funktionen implementiert (Quellcode ist verfügbar). Link):

  • Sammeln von Daten vom k8s-API-Server (Abrufen einer Liste von Namespaces, Bereitstellungen ...)
  • Weiterleiten von Anfragen an prometheus-datasource (die in den Plugin-Einstellungen für jeden spezifischen Cluster ausgewählt wird) und Formatieren von Antworten, um Daten sowohl in statischen Seiten als auch in Dashboards zu verwenden.
  • Aktualisieren von Daten auf statischen Plugin-Seiten (mit einer festgelegten Aktualisierungsrate).
  • Verarbeiten von Abfragen zum Generieren eines Vorlagenblatts in Grafana-Dashboards (MetriFindQuery()-Methode)

Entwicklung eines Plugins für Grafana: eine Geschichte der Großen

Entwicklung eines Plugins für Grafana: eine Geschichte der Großen

Entwicklung eines Plugins für Grafana: eine Geschichte der Großen

  • Verbindungstest 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: "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"};
       })
}

Ein weiterer interessanter Punkt ist unserer Meinung nach die Implementierung eines Authentifizierungs- und Autorisierungsmechanismus für die Datenquelle. Normalerweise können wir standardmäßig die integrierte Grafana-Komponente „datasourceHttpSettings“ verwenden, um den Zugriff auf die endgültige Datenquelle zu konfigurieren. Mit dieser Komponente können wir den Zugriff auf die http-Datenquelle konfigurieren, indem wir die URL und grundlegende Authentifizierungs-/Autorisierungseinstellungen angeben: Login-Passwort oder Client-Zertifikat/Client-Schlüssel. Um die Möglichkeit zu implementieren, den Zugriff mithilfe eines Bearer-Tokens (dem De-facto-Standard für k8s) zu konfigurieren, mussten wir einige Anpassungen vornehmen.

Um dieses Problem zu lösen, können Sie den integrierten Grafana-Mechanismus „Plugin Routes“ verwenden (weitere Details unter offizielle Dokumentationsseite). In den Einstellungen unserer Datenquelle können wir eine Reihe von Routing-Regeln deklarieren, die vom Grafana-Proxyserver verarbeitet werden. Beispielsweise können für jeden einzelnen Endpunkt Header oder URLs mit der Möglichkeit des Templatings festgelegt werden, deren Daten aus den Feldern jsonData und secureJsonData entnommen werden können (zur Speicherung von Passwörtern oder Token in verschlüsselter Form). In unserem Beispiel sind Abfragen wie /__proxy/api/v1/namespaces wird an die URL des Formulars weitergeleitet
/api/v8/namespaces mit dem Authorization: Bearer-Header.

Entwicklung eines Plugins für Grafana: eine Geschichte der Großen

Entwicklung eines Plugins für Grafana: eine Geschichte der Großen

Um mit dem k8s-API-Server arbeiten zu können, benötigen wir natürlich einen Benutzer mit schreibgeschütztem Zugriff. Manifeste zum Erstellen finden Sie ebenfalls in Plugin-Quellcode.

Teil 5: Veröffentlichung

Entwicklung eines Plugins für Grafana: eine Geschichte der Großen

Sobald Sie Ihr eigenes Grafana-Plugin geschrieben haben, möchten Sie es natürlich öffentlich verfügbar machen. In Grafana ist dies eine Bibliothek von Plugins, die hier verfügbar ist grafana.com/grafana/plugins

Damit Ihr Plugin im offiziellen Store verfügbar ist, müssen Sie eine PR erstellen dieses Repositorydurch Hinzufügen von Inhalten wie diesem zur repo.json-Datei:

Entwicklung eines Plugins für Grafana: eine Geschichte der Großen

Dabei ist „Version“ die Version Ihres Plugins, „URL“ ein Link zum Repository und „Commit“ der Hash des Commits, für den eine bestimmte Version des Plugins verfügbar sein wird.

Und am Ausgang sehen Sie ein wunderbares Bild wie:

Entwicklung eines Plugins für Grafana: eine Geschichte der Großen

Die Daten dafür werden automatisch aus Ihrer Readme.md-, Changelog.md- und der Plugin.json-Datei mit der Plugin-Beschreibung abgerufen.

Teil 6: statt Schlussfolgerungen

Wir haben die Entwicklung unseres Plugins nach der Veröffentlichung nicht gestoppt. Und jetzt arbeiten wir daran, die Ressourcennutzung von Cluster-Knoten korrekt zu überwachen, neue Funktionen zur Verbesserung von UX einzuführen und auch eine große Menge an Feedback zu sammeln, das wir nach der Installation des Plugins sowohl von unseren Kunden als auch von Leuten auf GitHub (falls Sie verlassen) erhalten haben Ihr Problem oder Ihre Pull-Anfrage, ich werde mich sehr freuen :)

Wir hoffen, dass dieser Artikel Ihnen hilft, ein so wunderbares Tool wie Grafana zu verstehen und vielleicht Ihr eigenes Plugin zu schreiben.

Danke!)

Source: habr.com

Kommentar hinzufügen