Ontwikkeling van een plug-in voor Grafana: een geschiedenis van grote namen

Dag Allemaal! Een paar maanden geleden hebben we ons nieuwe open-sourceproject in productie genomen: de Grafana-plug-in voor het monitoren van kubernetes, die we DevOpsProdigy KubeGraf. De broncode van de plug-in is beschikbaar op openbare repository op GitHub. En in dit artikel willen we het verhaal met u delen over hoe we de plug-in hebben gemaakt, welke tools we hebben gebruikt en welke valkuilen we tijdens het ontwikkelingsproces zijn tegengekomen. Laten we gaan!

Deel 0 – inleidend: hoe zijn we op dit punt gekomen?

Het idee om onze eigen plug-in voor Grafan te schrijven kwam vrij toevallig bij ons op. Ons bedrijf monitort al meer dan 10 jaar webprojecten van verschillende complexiteitsniveaus. In deze periode hebben wij een grote hoeveelheid expertise, interessante cases en ervaring opgebouwd met het gebruik van diverse monitoringsystemen. En op een gegeven moment vroegen we ons af: “Bestaat er een magisch hulpmiddel voor het monitoren van Kubernetes, zodat, zoals ze zeggen, “instellen en vergeten”?”.. De industriestandaard voor het monitoren van k8s is natuurlijk al lang de Prometheus + Grafana-combinatie. En als kant-en-klare oplossingen voor deze stapel is er een groot aantal verschillende soorten tools: prometheus-operator, een set kubernetes-mixin-dashboards, grafana-kubernetes-app.

De grafana-kubernetes-app plugin leek ons ​​de interessantste optie, maar wordt al ruim een ​​jaar niet meer ondersteund en kan bovendien niet werken met nieuwe versies van node-exporter en kube-state-metrics. En op een gegeven moment besloten we: “Moeten we niet onze eigen beslissing nemen?”

Welke ideeën we besloten te implementeren in onze plug-in:

  • visualisatie van de “applicatiekaart”: handige presentatie van applicaties in het cluster, gegroepeerd op naamruimten, implementaties...;
  • visualisatie van verbindingen zoals “implementatie - service (+poorten)”.
  • visualisatie van de distributie van clusterapplicaties over clusterknooppunten.
  • verzameling van statistieken en informatie uit verschillende bronnen: Prometheus en k8s API-server.
  • monitoring van zowel het infrastructuurgedeelte (gebruik van CPU-tijd, geheugen, schijfsubsysteem, netwerk) als applicatielogica - gezondheidsstatuspods, aantal beschikbare replica's, informatie over het slagen voor liveness/readyness-tests.

Deel 1: Wat is een “Grafana-plug-in”?

Vanuit technisch oogpunt is de plug-in voor Grafana een hoekcontroller, die is opgeslagen in de Grafana-gegevensmap (/var/grafana/plug-ins/ /dist/module.js) en kan worden geladen als een SystemJS-module. Ook in deze map zou er een plugin.json-bestand moeten staan ​​dat alle meta-informatie over uw plug-in bevat: naam, versie, plug-intype, links naar de repository/site/licentie, afhankelijkheden, enzovoort.

Ontwikkeling van een plug-in voor Grafana: een geschiedenis van grote namen
module.ts

Ontwikkeling van een plug-in voor Grafana: een geschiedenis van grote namen
plug-in.json

Zoals je in de schermafbeelding kunt zien, hebben we plugin.type = app opgegeven. Omdat plug-ins voor Grafana uit drie soorten kunnen bestaan:

paneel: het meest voorkomende type plug-in - het is een paneel voor het visualiseren van statistieken, gebruikt om verschillende dashboards te bouwen.
databron: plugin-connector voor een gegevensbron (bijvoorbeeld Prometheus-gegevensbron, ClickHouse-gegevensbron, ElasticSearch-gegevensbron).
gebruiken: Een plug-in waarmee u uw eigen frontend-applicatie binnen Grafana kunt bouwen, uw eigen html-pagina's kunt maken en handmatig toegang kunt krijgen tot de gegevensbron om verschillende gegevens te visualiseren. Ook kunnen plug-ins van andere typen (gegevensbron, paneel) en verschillende dashboards als afhankelijkheden worden gebruikt.

Ontwikkeling van een plug-in voor Grafana: een geschiedenis van grote namen
Voorbeeld van plug-in-afhankelijkheden met type=app.

Je kunt zowel JavaScript als TypeScript als programmeertaal gebruiken (wij hebben ervoor gekozen). Voorbereidingen voor hello-world plug-ins van welk type dan ook vind de link: deze repository bevat een groot aantal starter-packs (er is zelfs een experimenteel voorbeeld van een plug-in in React) met vooraf geïnstalleerde en geconfigureerde builders.

Deel 2: voorbereiding van de lokale omgeving

Om aan de plug-in te werken, hebben we uiteraard een kubernetes-cluster nodig met alle vooraf geïnstalleerde tools: prometheus, node-exporter, kube-state-metrics, grafana. De omgeving moet snel, gemakkelijk en natuurlijk worden opgezet, en om hot-reload te garanderen, moet de Grafana-gegevensmap rechtstreeks vanaf de machine van de ontwikkelaar worden aangekoppeld.

De handigste manier om naar onze mening lokaal met kubernetes te werken is minikube. De volgende stap is het installeren van de combinatie Prometheus + Grafana met behulp van de prometheus-operator. IN Dit artikel Het installatieproces van prometheus-operator op minikube wordt in detail beschreven. Om persistentie in te schakelen, moet u de parameter instellen volharding: waar in het charts/grafana/values.yaml bestand voegt u uw eigen PV en PVC toe en specificeert u deze in de persistentie.existingClaim parameter

Ons laatste minikube-startscript ziet er als volgt uit:

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

Deel 3: daadwerkelijke ontwikkeling

Objectmodel

Ter voorbereiding op de implementatie van de plug-in hebben we besloten om alle basis Kubernetes-entiteiten waarmee we zullen werken te beschrijven in de vorm van TypeScript-klassen: pod, deployment, daemonset, statefulset, job, cronjob, service, node, namespace. Elk van deze klassen erft van de gemeenschappelijke klasse BaseModel, die de constructor, destructor en methoden voor het bijwerken en schakelen van zichtbaarheid beschrijft. Elk van de klassen beschrijft geneste relaties met andere entiteiten, bijvoorbeeld een lijst met peulen voor een entiteit van het type implementatie.

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

Met behulp van getters en setters kunnen we de entiteitsstatistieken die we nodig hebben, in een handige en leesbare vorm weergeven of instellen. Bijvoorbeeld geformatteerde uitvoer van toewijsbare CPU-knooppunten:

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

Pages

Een lijst met al onze plug-inpagina's wordt in eerste instantie beschreven in onze pluing.json in de sectie Afhankelijkheden:

Ontwikkeling van een plug-in voor Grafana: een geschiedenis van grote namen

In het blok voor elke pagina moeten we de PAGINANAAM aangeven (deze wordt dan omgezet in een slug waarmee deze pagina toegankelijk zal zijn); de naam van de component die verantwoordelijk is voor de werking van deze pagina (de lijst met componenten wordt geëxporteerd naar module.ts); met vermelding van de gebruikersrol waarvoor werken met deze pagina beschikbaar is en navigatie-instellingen voor de zijbalk.

In de component die verantwoordelijk is voor de werking van de pagina, moeten we templateUrl instellen, waarbij we het pad naar het HTML-bestand met opmaak doorgeven. Binnen de controller hebben we via afhankelijkheidsinjectie toegang tot maximaal 2 belangrijke hoekdiensten:

  • backendSrv - een service die interactie biedt met de Grafana API-server;
  • datasourceSrv - een service die lokale interactie biedt met alle gegevensbronnen die in uw Grafana zijn geïnstalleerd (bijvoorbeeld de .getAll() -methode - retourneert een lijst met alle geïnstalleerde gegevensbronnen; .get( ) - retourneert een instanceobject van een specifieke gegevensbron.

Ontwikkeling van een plug-in voor Grafana: een geschiedenis van grote namen

Ontwikkeling van een plug-in voor Grafana: een geschiedenis van grote namen

Ontwikkeling van een plug-in voor Grafana: een geschiedenis van grote namen

Deel 4: gegevensbron

Vanuit het oogpunt van Grafana is datasource precies dezelfde plug-in als alle andere: het heeft zijn eigen toegangspunt module.js, er is een bestand met meta-informatie plugin.json. Bij het ontwikkelen van een plug-in met type = app kunnen we communiceren met zowel bestaande gegevensbronnen (bijvoorbeeld prometheus-datasource) als met onze eigen gegevensbronnen, die we rechtstreeks in de plug-inmap (dist/datasource/*) kunnen opslaan of als afhankelijkheid kunnen installeren. In ons geval wordt de gegevensbron geleverd met de plug-incode. Het is ook noodzakelijk om een ​​config.html-sjabloon en een ConfigCtrl-controller te hebben, die zullen worden gebruikt voor de configuratiepagina van de gegevensbroninstantie en de Datasource-controller, die de logica van uw gegevensbron implementeert.

In de KubeGraf-plug-in is de gegevensbron, vanuit het oogpunt van de gebruikersinterface, een exemplaar van een Kubernetes-cluster dat de volgende mogelijkheden implementeert (broncode is beschikbaar link):

  • gegevens verzamelen van de k8s api-server (een lijst met naamruimten, implementaties verkrijgen...)
  • proxyverzoeken naar prometheus-datasource (die wordt geselecteerd in de plug-ininstellingen voor elk specifiek cluster) en antwoorden opmaken om gegevens zowel op statische pagina's als in dashboards te gebruiken.
  • het bijwerken van gegevens op statische plug-inpagina's (met een ingestelde vernieuwingsfrequentie).
  • query's verwerken om een ​​sjabloonblad te genereren in grafana-dashboards (methode metriFindQuery())

Ontwikkeling van een plug-in voor Grafana: een geschiedenis van grote namen

Ontwikkeling van een plug-in voor Grafana: een geschiedenis van grote namen

Ontwikkeling van een plug-in voor Grafana: een geschiedenis van grote namen

  • verbindingstest met het laatste 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"};
       })
}

Een apart interessant punt is naar onze mening de implementatie van een authenticatie- en autorisatiemechanisme voor de databron. Normaal gesproken kunnen we kant-en-klaar de ingebouwde Grafana-component datasourceHttpSettings gebruiken om de toegang tot de uiteindelijke gegevensbron te configureren. Met behulp van dit onderdeel kunnen we de toegang tot de http-gegevensbron configureren door de URL en de basisinstellingen voor authenticatie/autorisatie op te geven: login-wachtwoord of client-cert/client-key. Om de mogelijkheid te implementeren om toegang te configureren met behulp van een bearer-token (de de facto standaard voor k8s), moesten we wat aanpassingen doen.

Om dit probleem op te lossen, kunt u het ingebouwde Grafana “Plugin Routes”-mechanisme gebruiken (meer details op officiële documentatiepagina). In de instellingen van onze gegevensbron kunnen we een reeks routeringsregels declareren die door de grafana-proxyserver worden verwerkt. Voor elk individueel endpoint is het bijvoorbeeld mogelijk om headers of urls in te stellen met de mogelijkheid tot templates, waarbij data uit de velden jsonData en secureJsonData kunnen worden gehaald (voor het opslaan van wachtwoorden of tokens in gecodeerde vorm). In ons voorbeeld zijn zoekopdrachten als /__proxy/api/v1/naamruimten wordt geproxyd naar de URL van het formulier
/api/v8/namespaces met de header Authorization: Bearer.

Ontwikkeling van een plug-in voor Grafana: een geschiedenis van grote namen

Ontwikkeling van een plug-in voor Grafana: een geschiedenis van grote namen

Om met de k8s API-server te werken hebben we uiteraard een gebruiker nodig met alleen-lezen toegang, manifesten om te maken die je ook kunt vinden in broncode van de plug-in.

Deel 5: loslaten

Ontwikkeling van een plug-in voor Grafana: een geschiedenis van grote namen

Zodra u uw eigen Grafana-plug-in heeft geschreven, wilt u deze uiteraard openbaar beschikbaar maken. In Grafana is dit een bibliotheek met plug-ins die hier beschikbaar zijn grafana.com/grafana/plugins

Om ervoor te zorgen dat uw plug-in beschikbaar is in de officiële winkel, moet u een PR indienen deze opslagplaatsdoor inhoud zoals deze toe te voegen aan het repo.json-bestand:

Ontwikkeling van een plug-in voor Grafana: een geschiedenis van grote namen

waarbij versie de versie van uw plug-in is, url een link naar de repository is, en commit de hash is van de commit waarvoor een specifieke versie van de plug-in beschikbaar zal zijn.

En aan de uitgang zie je een prachtige foto zoals:

Ontwikkeling van een plug-in voor Grafana: een geschiedenis van grote namen

De gegevens ervoor worden automatisch opgehaald uit uw Readme.md-, Changelog.md- en het plugin.json-bestand met de plug-inbeschrijving.

Deel 6: in plaats van conclusies

We zijn na de release niet gestopt met het ontwikkelen van onze plug-in. En nu werken we aan het correct monitoren van het gebruik van bronnen van clusterknooppunten, het introduceren van nieuwe functies om UX te verbeteren, en ook het binnenhalen van een grote hoeveelheid feedback die we hebben ontvangen na het installeren van de plug-in, zowel door onze klanten als van mensen op GitHub (als je weggaat uw probleem of pull-verzoek, ik zal heel blij zijn :)

We hopen dat dit artikel je zal helpen zo'n geweldig hulpmiddel als Grafana te begrijpen en misschien je eigen plug-in te schrijven.

Спасибо!)

Bron: www.habr.com

Voeg een reactie