Développement d'un plugin pour Grafana : une histoire de grands coups

Salut tout le monde! Il y a quelques mois, nous avons lancé en production notre nouveau projet open source - le plugin Grafana pour surveiller Kubernetes, que nous avons appelé DevOpsProdigy KubeGraf. Le code source du plugin est disponible sur dépôt public sur GitHub. Et dans cet article, nous souhaitons partager avec vous l'histoire de la façon dont nous avons créé le plugin, les outils que nous avons utilisés et les pièges que nous avons rencontrés au cours du processus de développement. Allons-y!

Partie 0 - introduction : comment en sommes-nous arrivés là ?

L’idée d’écrire notre propre plugin pour Grafan nous est venue par hasard. Notre entreprise suit depuis plus de 10 ans des projets web de différents niveaux de complexité. Au cours de cette période, nous avons accumulé une grande expertise, des cas intéressants et une expérience dans l’utilisation de divers systèmes de surveillance. Et à un moment donné, nous nous sommes demandés : « Existe-t-il un outil magique pour surveiller Kubernetes, de sorte que, comme on dit, « configurez-le et oubliez-le » ? Combinaison Prométhée + Grafana. Et comme solutions prêtes à l'emploi pour cette pile, il existe un large éventail d'outils de différents types : prometheus-operator, un ensemble de tableaux de bord kubernetes-mixin, grafana-kubernetes-app.

Le plugin grafana-kubernetes-app nous a semblé être l'option la plus intéressante, mais il n'est plus supporté depuis plus d'un an et, de plus, ne peut pas fonctionner avec les nouvelles versions de node-exporter et kube-state-metrics. Et à un moment donné, nous avons décidé : « Ne devrions-nous pas prendre notre propre décision ?

Quelles idées nous avons décidé de mettre en œuvre dans notre plugin :

  • visualisation de la « carte des applications » : présentation pratique des applications du cluster, regroupées par espaces de noms, déploiements... ;
  • visualisation des connexions comme « déploiement - service (+ports) ».
  • visualisation de la distribution des applications de cluster sur les nœuds du cluster.
  • collecte de métriques et d'informations provenant de plusieurs sources : Prometheus et le serveur api k8s.
  • surveillance à la fois de la partie infrastructure (utilisation du temps CPU, de la mémoire, du sous-système de disque, du réseau) et de la logique de l'application - pods d'état de santé, nombre de répliques disponibles, informations sur la réussite des tests d'activité/préparation.

Partie 1 : Qu'est-ce qu'un « plugin Grafana » ?

D'un point de vue technique, le plugin pour Grafana est un contrôleur angulaire, qui est stocké dans le répertoire de données Grafana (/var/grafana/plugins/ /dist/module.js) et peut être chargé en tant que module SystemJS. Ce répertoire devrait également contenir un fichier plugin.json contenant toutes les méta-informations sur votre plugin : nom, version, type de plugin, liens vers le référentiel/site/licence, dépendances, etc.

Développement d'un plugin pour Grafana : une histoire de grands coups
module.ts

Développement d'un plugin pour Grafana : une histoire de grands coups
plugin.json

Comme vous pouvez le voir sur la capture d'écran, nous avons spécifié plugin.type = app. Car les plugins pour Grafana peuvent être de trois types :

panneau: le type de plugin le plus courant - il s'agit d'un panneau permettant de visualiser toutes les métriques, utilisé pour créer divers tableaux de bord.
source de données: connecteur de plugin vers une source de données (par exemple, Prometheus-datasource, ClickHouse-datasource, ElasticSearch-datasource).
appli: Un plugin qui vous permet de créer votre propre application front-end dans Grafana, de créer vos propres pages html et d'accéder manuellement à la source de données pour visualiser diverses données. De plus, des plugins d'autres types (source de données, panneau) et divers tableaux de bord peuvent être utilisés comme dépendances.

Développement d'un plugin pour Grafana : une histoire de grands coups
Exemples de dépendances de plugin avec type=app.

Vous pouvez utiliser à la fois JavaScript et TypeScript comme langage de programmation (nous l'avons choisi). Préparations pour les plugins hello-world de tout type que vous pouvez trouver par lien: ce référentiel contient un grand nombre de starter-packs (il existe même un exemple expérimental de plugin dans React) avec des builders préinstallés et configurés.

Partie 2 : préparer l’environnement local

Pour travailler sur le plugin, nous avons naturellement besoin d'un cluster kubernetes avec tous les outils préinstallés : prometheus, node-exporter, kube-state-metrics, grafana. L'environnement doit être configuré rapidement, facilement et naturellement, et pour garantir le rechargement à chaud, le répertoire de données Grafana doit être monté directement depuis la machine du développeur.

Le moyen le plus pratique, à notre avis, de travailler localement avec Kubernetes est minikube. L'étape suivante consiste à installer la combinaison Prometheus + Grafana à l'aide de prometheus-operator. DANS cet article Le processus d'installation de prometheus-operator sur minikube est décrit en détail. Pour activer la persistance, vous devez définir le paramètre persistance : vrai dans le fichier charts/grafana/values.yaml, ajoutez vos propres PV et PVC et spécifiez-les dans le paramètre persistence.existingClaim

Notre script de lancement final du minikube ressemble à ceci :

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

Partie 3 : développement réel

Modèle objet

En préparation de l'implémentation du plugin, nous avons décidé de décrire toutes les entités de base Kubernetes avec lesquelles nous travaillerons sous forme de classes TypeScript : pod, déploiement, démonset, statefulset, travail, cronjob, service, nœud, espace de noms. Chacune de ces classes hérite de la classe commune BaseModel, qui décrit le constructeur, le destructeur, les méthodes de mise à jour et de changement de visibilité. Chacune des classes décrit des relations imbriquées avec d'autres entités, par exemple une liste de pods pour une entité de type déploiement.

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

Avec l'aide des getters et des setters, nous pouvons afficher ou définir les métriques d'entité dont nous avons besoin sous une forme pratique et lisible. Par exemple, sortie formatée des nœuds CPU allouables :

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

Nos Pages

Une liste de toutes nos pages de plugins est initialement décrite dans notre pluing.json dans la section dépendances :

Développement d'un plugin pour Grafana : une histoire de grands coups

Dans le bloc de chaque page il faut indiquer le NOM DE LA PAGE (il sera ensuite converti en un slug par lequel cette page sera accessible) ; le nom du composant responsable du fonctionnement de cette page (la liste des composants est exportée vers module.ts) ; indiquant le rôle d'utilisateur pour lequel le travail avec cette page est disponible et les paramètres de navigation pour la barre latérale.

Dans le composant responsable du fonctionnement de la page, nous devons définir templateUrl, en y passant le chemin d'accès au fichier html avec balisage. À l'intérieur du contrôleur, grâce à l'injection de dépendances, nous pouvons accéder à jusqu'à 2 services angulaires importants :

  • backendSrv - un service qui permet une interaction avec le serveur API Grafana ;
  • datasourceSrv - un service qui fournit une interaction locale avec toutes les sources de données installées dans votre Grafana (par exemple, la méthode .getAll() - renvoie une liste de toutes les sources de données installées ; .get( ) - renvoie un objet instance d'une source de données spécifique.

Développement d'un plugin pour Grafana : une histoire de grands coups

Développement d'un plugin pour Grafana : une histoire de grands coups

Développement d'un plugin pour Grafana : une histoire de grands coups

Partie 4 : source de données

Du point de vue de Grafana, datasource est exactement le même plugin que tous les autres : il a son propre point d'entrée module.js, il y a un fichier avec des méta informations plugin.json. Lors du développement d'un plugin avec type = app, nous pouvons interagir à la fois avec les sources de données existantes (par exemple, prometheus-datasource) et les nôtres, que nous pouvons stocker directement dans le répertoire du plugin (dist/datasource/*) ou installer en tant que dépendance. Dans notre cas, la source de données est livrée avec le code du plugin. Il est également nécessaire d'avoir un modèle config.html et un contrôleur ConfigCtrl, qui seront utilisés pour la page de configuration de l'instance de source de données et le contrôleur Datasource, qui implémente la logique de votre source de données.

Dans le plugin KubeGraf, du point de vue de l'interface utilisateur, la source de données est une instance d'un cluster Kubernetes qui implémente les fonctionnalités suivantes (le code source est disponible lien):

  • collecte de données à partir du serveur API k8s (obtention d'une liste d'espaces de noms, de déploiements...)
  • proxy des requêtes vers prometheus-datasource (qui est sélectionné dans les paramètres du plugin pour chaque cluster spécifique) et formatage des réponses pour utiliser les données à la fois dans les pages statiques et dans les tableaux de bord.
  • mise à jour des données sur les pages de plugin statiques (avec un taux de rafraîchissement défini).
  • traitement des requêtes pour générer une feuille de modèle dans grafana-dashboards (méthode metriFindQuery())

Développement d'un plugin pour Grafana : une histoire de grands coups

Développement d'un plugin pour Grafana : une histoire de grands coups

Développement d'un plugin pour Grafana : une histoire de grands coups

  • test de connexion avec le cluster k8s final.
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 autre point intéressant, à notre avis, est la mise en œuvre d'un mécanisme d'authentification et d'autorisation pour la source de données. En règle générale, nous pouvons utiliser le composant Grafana intégré datasourceHttpSettings pour configurer l'accès à la source de données finale. À l'aide de ce composant, nous pouvons configurer l'accès à la source de données http en spécifiant l'URL et les paramètres d'authentification/autorisation de base : login-password ou client-cert/client-key. Afin d'implémenter la possibilité de configurer l'accès à l'aide d'un jeton de porteur (la norme de facto pour les k8), nous avons dû procéder à quelques ajustements.

Pour résoudre ce problème, vous pouvez utiliser le mécanisme intégré Grafana « Plugin Routes » (plus de détails sur page de documentation officielle). Dans les paramètres de notre source de données, nous pouvons déclarer un ensemble de règles de routage qui seront traitées par le serveur proxy grafana. Par exemple, pour chaque point de terminaison individuel, il est possible de définir des en-têtes ou des URL avec possibilité de modèle, dont les données peuvent être extraites des champs jsonData et secureJsonData (pour stocker des mots de passe ou des jetons sous forme cryptée). Dans notre exemple, des requêtes telles que /__proxy/api/v1/espaces de noms sera proxy vers l'url du formulaire
/api/v8/namespaces avec l'en-tête Authorization: Bearer.

Développement d'un plugin pour Grafana : une histoire de grands coups

Développement d'un plugin pour Grafana : une histoire de grands coups

Naturellement, pour travailler avec le serveur API k8s, nous avons besoin d'un utilisateur avec un accès en lecture seule, des manifestes de création que vous pouvez également trouver dans code source du plugin.

Partie 5 : sortie

Développement d'un plugin pour Grafana : une histoire de grands coups

Une fois que vous aurez écrit votre propre plugin Grafana, vous souhaiterez naturellement le rendre public. Dans Grafana, c'est une bibliothèque de plugins disponible ici grafana.com/grafana/plugins

Pour que votre plugin soit disponible sur la boutique officielle, vous devez faire un PR en ce référentielen ajoutant du contenu comme celui-ci au fichier repo.json :

Développement d'un plugin pour Grafana : une histoire de grands coups

où version est la version de votre plugin, url est un lien vers le référentiel et commit est le hachage du commit pour lequel une version spécifique du plugin sera disponible.

Et à la sortie, vous verrez une magnifique image comme :

Développement d'un plugin pour Grafana : une histoire de grands coups

Les données correspondantes seront automatiquement extraites de votre fichier Readme.md, Changelog.md et du fichier plugin.json avec la description du plugin.

Partie 6 : au lieu de conclusions

Nous n'avons pas arrêté de développer notre plugin après sa sortie. Et maintenant, nous travaillons à surveiller correctement l'utilisation des ressources des nœuds de cluster, à introduire de nouvelles fonctionnalités pour améliorer l'UX, et également à récolter une grande quantité de commentaires reçus après l'installation du plugin à la fois par nos clients et par les personnes sur GitHub (si vous quittez votre problème ou pull request, je serai très heureux :)

Nous espérons que cet article vous aidera à comprendre un outil aussi merveilleux que Grafana et, peut-être, à écrire votre propre plugin.

Merci!)

Source: habr.com

Ajouter un commentaire