Разработка плагина для Grafana: история набитых шишек

в 8:22, , рубрики: diy или сделай сам, github, Grafana, ITSumma, k8s, kubernetes, open source, Блог компании ITSumma, плагин, разработка, Управление продуктом

Всем привет! Несколько месяцев назад мы запустили в продакшн наш новый open-source проект — Grafana-плагин для мониторинга kubernetes, который назвали DevOpsProdigy KubeGraf. Исходный код плагина доступен в публичном репозитории на GitHub. А в этой статье мы хотим поделиться с вами историей о том, как мы создавали плагин, какие инструменты использовали и с какими подводными камнями столкнулись в процессе разработки. Погнали!

Часть 0 — вводная: как мы до этого докатились?

Идея написать свой собственный плагин для Grafan’ы у нас родилась совершенно случайно. Наша компания уже более 10 лет занимается мониторингом web-проектов различного уровня сложности. За это время мы наработали большой багаж экспертизы, интересных кейсов, опыта использования различных систем мониторинга. И в какой-то момент мы задались вопросом: «А существует ли волшебный инструмент для мониторинга Kubernetes, чтобы, как говорится, “поставил и забыл”»?.. Промстандартом для мониторинга k8s, естественно, давно является связка Prometheus + Grafana. И в качестве готовых решений для данного стэка существует большой набор различного рода инструментов: prometheus-operator, набор дашбордов kubernetes-mixin, grafana-kubernetes-app.

Наиболее интересным вариантом для нас показался плагин grafana-kubernetes-app, но он не поддерживается уже больше года и, к тому же, не умеет работать с новыми версиями node-exporter’а и kube-state-metrics’а. И в какой-то момент мы решили: «А не сделать ли нам свое собственное решение?»

Какие идеи мы решили реализовать в своем плагине:

  • визуализация «карты приложения»: удобное представление приложений в кластере, сгруппированных по namespace’ам, deployment’ам…;
  • визуализация связей вида «deployment — service (+ports)».
  • визуализация распределения приложений кластера по nod’ам кластера.
  • сбор метрик и информации из нескольких источников: Prometheus и k8s api server.
  • мониторинг как инфраструктурной части (использование процессорного времени, памяти, дисковой подсистемы, сети), так и логики приложений — health-status pod’ов, количество доступных реплик, информация о прохождении liveness/readyness-проб.

Часть 1: Что такое «плагин для Grafana»?

С технической точки зрения, плагин для Grafana — это angular-контроллер, который хранится в data-директории Grafan’ы (/var/grafana/plugins/<your_plugin_name>/dist/module.js) и можeт быть загружен как SystemJS-модуль. Также в этой директории должен находиться файл plugin.json, содержащий в себе всю метаинформацию о вашем плагине: название, версия, тип плагина, ссылки на репозиторий/сайт/лицензию, зависимости и так далее.

Разработка плагина для Grafana: история набитых шишек - 1
module.ts

Разработка плагина для Grafana: история набитых шишек - 2
plugin.json

Как видно на скриншоте, мы указали plugin.type = app. Ибо плагины для Grafana могут быть трех видов:

panel: самый распространенный тип плагинов — представляет собой панель для визуализации каких-либо метрик, используется для построения различных дашбордов.
datasource: плагин-коннектор до какого-либо источника данных (например, Prometheus-datasource, ClickHouse-datasource, ElasticSearch-datasource).
app: плагин, позволяющий вам построить свое собственное фронтенд-приложение внутри Grafana, создавать свои собственные html-страницы и вручную обращаться к datasource для визуализации различных данных. Также в качестве зависимостей могут использоваться плагины других типов (datasource, panel) и различные дашборды.

Разработка плагина для Grafana: история набитых шишек - 3
Пример зависимостей плагина с type = app.

В качестве языка программирования можно использовать как JavaScript, так и TypeScript (мы свой выбор остановили на нем). Заготовки для hello-world плагинов любого типа вы можете найти по ссылке: в данном репозитории представлено большое количество starter-pack’ов (есть даже экспериментальный пример плагина на React) с предустановленными и настроенными сборщиками.

Часть 2: подготовка локального окружения

Для работы над плагином нам, естественно, понадобится kubernetes-кластер со всеми предустановленными инструментами: prometheus, node-exporter, kube-state-metrics, grafana. Окружение должно сетапиться быстро, легко и непринужденно, а для обеспечения hot-reload data-директория Grafana должна монтироваться непосредственно с машины разработчика.

Самым удобным, на наш взгляд, способом локальной работы с kubernetes является minikube. Следующим шагом устанавливаем связку Prometheus + Grafana c помощью prometheus-operator. В данной статье подробно описан процесс установки prometheus-operator на minikube. Для включения персистентности необходимо установить параметр persistence: true в файле charts/grafana/values.yaml, добавить свой собственный PV и PVC и указать их в параметре persistence.existingClaim

Итоговый скрипт запуска minikube у нас выглядит вот так:

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

Часть 3: непосредственно разработка

Объектная модель

В качестве подготовки к реализации плагина мы решили описать все базовые сущности Kubernetes, с которыми мы будем работать в виде TypeScript-классов: pod, deployment, daemonset, statefulset, job, cronjob, service, node, namespace. Каждый из этих классов наследуется от общего класса BaseModel, в котором описаны конструктор, деструктор, методы для обновления и переключения видимости. В каждом из классов описаны вложенные отношения с другими сущностями, например, список pod’ов у сущности типа deployment.

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

C помощью getter’ов и setter’ов мы можем выводить или устанавливать нужные нам метрики сущностей в удобном и читабельном виде. Например отформатированный вывод allocatable cpu nod’ы:

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

Pages

Список всех страниц нашего плагина изначально описывается в нашем pluing.json в разделе зависимостей:

Разработка плагина для Grafana: история набитых шишек - 4

В блоке для каждой страницы мы должны указать НАЗВАНИЕ СТРАНИЦЫ (оно затем будет сконвертировано в slug, по которому эта страница будет доступна); название компонента, отвечающего за работу этой странице (список компонентов экспортится в module.ts); указание роли пользователя, для которого доступна работа с этой страницей и настройки навигации для боковой панели.

В компоненте, отвечающем за работу страницы, мы должны установить templateUrl, передав туда путь до html-файла с разметкой. Внутри контроллера, через dependency injection, мы можем получить доступ до 2-х важных angular-сервисов:

  • backendSrv — сервис, обеспечивающий взаимодействие с api-сервером графаны;
  • datasourceSrv — сервис, обеспечивающий локальное взаимодействие со всеми datasource, установленными в вашей Grafana (например, метод .getAll() — возвращает список всех установленных datasource’ов; .get(<nаme>) — возвращает объект-инстанс конкретного datasource.

Разработка плагина для Grafana: история набитых шишек - 5

Разработка плагина для Grafana: история набитых шишек - 6

Разработка плагина для Grafana: история набитых шишек - 7

Часть 4: datasource

С точки зрения Grafana, datasource представляет собой точно такой же плагин, как и все остальные: у него есть своя точка входа module.js, есть файл с метаинформацией plugin.json. При разработке плагина с type = app мы можем взаимодействовать как с уже существующими datasource’ами (например, prometheus-datasource), так и своими собственными, которые мы можем хранить непосредственно в директории плагина (dist/datasource/*) или устанавливать как зависимость. В нашем случае datasource поставляется вместе с кодом плагина. Также обязательно наличие шаблона config.html и контроллера ConfigCtrl, которые будут использоваться для страницы конфигурирования экземпляра datasource’а и контроллера Datasource, в котором реализуется логика работы вашего datasource’а.

В плагине KubeGraf, с точки зрения пользовательского интерфейса, datasource представляет собой экземпляр kubernetes-кластера, в котором реализованы следующие возможности (исходный код доступен по ссылке):

  • забор данных из api-server’а k8s (получение списка namespace’ов, deployment’ов…)
  • проксирование запросов в prometheus-datasource (который выбирается в настройках плагина для каждого конкретного кластера) и форматирование ответов для использования данных как в статичных страницах, так и в дашбордах.
  • обновление данных на статичных страницах плагина (с установленным временем refresh rate).
  • обработка запросов для формирования template-листа в grafana-dashboards (метод .metriFindQuery())

Разработка плагина для Grafana: история набитых шишек - 8

Разработка плагина для Grafana: история набитых шишек - 9

Разработка плагина для Grafana: история набитых шишек - 10

  • тест соединения с конечным k8s-кластером.
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"};
       })
}

Отдельным интересным моментом, на наш взгляд, является реализация механизма аутентификации и авторизации для datasource. Как правило, из коробки для конфигурации доступа до конечного источника данных мы можем использовать встроенный компонент Grafana — datasourceHttpSettings. С помощью этого компонента мы можем настроить доступ до http-источника данных, указав url и базовые настройки аутентификации/авторизации: логин-пароль, или client-cert/client-key. Для того чтобы реализовать возможность настройки доступа с помощью bearer-токена (дефакто стандарт для k8s), пришлось немного «похимичить».

Для решения этой задачи можно использовать встроенный механизм Grafana «Plugin Routes» (подробнее на официальной странице документации). В настройках нашего datasource’а мы можем объявить набор правил роутинга, которые будут обрабатываться proxy-сервером grafana. Например, для каждого отдельного endpoint’а существует возможность проставления заголовков или url с возможностью шаблонизирования, данные для которых могут браться из полей jsonData и secureJsonData (для хранения паролей или токенов в шифрованном виде). В нашем примере запросы вида /__proxy/api/v1/namespaces будут проксироваться на url вида
<your_k8s_api_url>/api/v1/namespaces с проставлением заголовка Authorization: Bearer .

Разработка плагина для Grafana: история набитых шишек - 11

Разработка плагина для Grafana: история набитых шишек - 12

Естественно, для работы с api-сервером k8s нам необходим пользователь с readonly доступами, манифесты для создания которого вы можете также найти в исходном коде плагина.

Часть 5: релиз

Разработка плагина для Grafana: история набитых шишек - 13

После того, как вы напишете свой собственный плагин для Grafana, вам, естественно, захочется выложить его в открытый доступ. В Grafana это библиотека плагинов, доступная по ссылке grafana.com/grafana/plugins

Для того чтобы ваш плагин был доступен в официальном сторе, вам необходимо сделать PR в этот репозиторий, добавив в файл repo.json содержимое вида:

Разработка плагина для Grafana: история набитых шишек - 14

где version — версия вашего плагина, url — ссылка на репозиторий, а commit — hash коммита, по которому будет доступна конкретная версия плагина.

И на выходе вы увидите замечательную картинку вида:

Разработка плагина для Grafana: история набитых шишек - 15

Данные для нее будут автоматически сграблены с вашего Readme.md, Changelog.md и файла plugin.json с описанием плагина.

Часть 6: вместо выводов

Мы не прекратили разработку нашего плагина после релиза. И сейчас работаем над корректным мониторингом использования ресурсов нод кластера, внедрением новых фич для повышения UX, а также разгребаем большое количества фидбека, полученного после установок плагина как нашими клиентами, так и из ишшуев на гитхабе (если вы оставите свое issue или pull request, я буду очень счастлив :-) ).

Надеемся что данная статья поможет вам разобраться в таком прекрасном инструменте как Grafana и, возможно, написать свой собственный плагин.

Спасибо!)

Автор: Сережа

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js