Desenvolvimento de um plugin para Grafana: uma história de figurões

Olá a todos! Há alguns meses, lançamos em produção nosso novo projeto de código aberto - o plugin Grafana para monitoramento de kubernetes, que chamamos DevOpsProdigy KubeGraf. O código fonte do plugin está disponível em repositório público no GitHub. E neste artigo queremos compartilhar com vocês a história de como criamos o plugin, quais ferramentas usamos e quais armadilhas encontramos durante o processo de desenvolvimento. Vamos!

Parte 0 – introdutória: como chegamos a este ponto?

A ideia de escrever nosso próprio plugin para Grafan surgiu por acaso. Nossa empresa acompanha projetos web de diversos níveis de complexidade há mais de 10 anos. Durante esse tempo, acumulamos um grande conhecimento, casos interessantes e experiência na utilização de diversos sistemas de monitoramento. E em algum momento nos perguntamos: “Existe uma ferramenta mágica para monitorar Kubernetes, para que, como dizem, “configure e esqueça”?”.. O padrão da indústria para monitorar k8s, é claro, tem sido há muito tempo o Combinação Prometeu + Grafana. E como soluções prontas para essa pilha, há um grande conjunto de vários tipos de ferramentas: prometheus-operator, um conjunto de painéis kubernetes-mixin, grafana-kubernetes-app.

O plugin grafana-kubernetes-app parecia ser a opção mais interessante para nós, mas não tem suporte há mais de um ano e, além disso, não funciona com novas versões do node-exporter e kube-state-metrics. E a certa altura decidimos: “Não deveríamos tomar a nossa própria decisão?”

Quais ideias decidimos implementar em nosso plugin:

  • visualização do “mapa de aplicações”: apresentação conveniente das aplicações no cluster, agrupadas por namespaces, implantações...;
  • visualização de conexões como “implantação - serviço (+portas)”.
  • visualização da distribuição de aplicativos de cluster entre nós de cluster.
  • coleta de métricas e informações de diversas fontes: Prometheus e servidor api k8s.
  • monitoramento tanto da parte da infraestrutura (uso de tempo de CPU, memória, subsistema de disco, rede) quanto da lógica do aplicativo - pods de status de integridade, número de réplicas disponíveis, informações sobre aprovação em testes de atividade/prontidão.

Parte 1: O que é um “plugin Grafana”?

Do ponto de vista técnico, o plugin para Grafana é um controlador angular, que fica armazenado no diretório de dados do Grafana (/var/grafana/plugins/ /dist/module.js) e pode ser carregado como um módulo SystemJS. Também neste diretório deve haver um arquivo plugin.json contendo todas as meta informações sobre o seu plugin: nome, versão, tipo de plugin, links para o repositório/site/licença, dependências e assim por diante.

Desenvolvimento de um plugin para Grafana: uma história de figurões
módulo.ts

Desenvolvimento de um plugin para Grafana: uma história de figurões
plugin.json

Como você pode ver na captura de tela, especificamos plugin.type = app. Porque os plugins para Grafana podem ser de três tipos:

painel: o tipo de plugin mais comum - é um painel para visualização de quaisquer métricas, utilizado para construir diversos dashboards.
fonte de dados: conector de plug-in para alguma fonte de dados (por exemplo, Prometheus-datasource, ClickHouse-datasource, ElasticSearch-datasource).
app: Um plugin que permite construir sua própria aplicação frontend dentro do Grafana, criar suas próprias páginas html e acessar manualmente a fonte de dados para visualizar diversos dados. Além disso, plug-ins de outros tipos (fonte de dados, painel) e vários painéis podem ser usados ​​como dependências.

Desenvolvimento de um plugin para Grafana: uma história de figurões
Exemplo de dependências de plugin com type=app.

Você pode usar JavaScript e TypeScript como linguagem de programação (nós escolhemos). Preparativos para plug-ins hello-world de qualquer tipo que você puder encontrar por link: este repositório contém um grande número de pacotes iniciais (há até um exemplo experimental de plugin no React) com construtores pré-instalados e configurados.

Parte 2: preparando o ambiente local

Para trabalhar no plugin, naturalmente precisamos de um cluster kubernetes com todas as ferramentas pré-instaladas: prometheus, node-exporter, kube-state-metrics, grafana. O ambiente deve ser configurado de forma rápida, fácil e natural, e para garantir o hot-reload, o diretório de dados do Grafana deve ser montado diretamente da máquina do desenvolvedor.

A maneira mais conveniente, em nossa opinião, de trabalhar localmente com kubernetes é minikubo. A próxima etapa é instalar a combinação Prometheus + Grafana usando o prometheus-operator. EM este artigo O processo de instalação do operador prometheus no minikube é descrito em detalhes. Para ativar a persistência, você deve definir o parâmetro persistência: verdadeiro no arquivo charts/grafana/values.yaml, adicione seu próprio PV e PVC e especifique-os no parâmetro persistence.existentClaim

Nosso script final de lançamento do minikube é assim:

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

Parte 3: desenvolvimento real

Modelo de Objeto

Na preparação para a implementação do plugin, decidimos descrever todas as entidades básicas do Kubernetes com as quais trabalharemos na forma de classes TypeScript: pod, implantação, daemonset, statefulset, trabalho, cronjob, serviço, nó, namespace. Cada uma dessas classes herda da classe BaseModel comum, que descreve o construtor, o destruidor, os métodos para atualizar e alternar a visibilidade. Cada uma das classes descreve relacionamentos aninhados com outras entidades, por exemplo, uma lista de pods para uma entidade do tipo implantação.

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

Com a ajuda de getters e setters, podemos exibir ou definir as métricas de entidade necessárias de uma forma conveniente e legível. Por exemplo, saída formatada de nós de CPU alocáveis:

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

PÁGINAS

Uma lista de todas as nossas páginas de plugins é inicialmente descrita em nosso pluing.json na seção de dependências:

Desenvolvimento de um plugin para Grafana: uma história de figurões

No bloco de cada página devemos indicar o NOME DA PÁGINA (será então convertido em um slug pelo qual esta página ficará acessível); o nome do componente responsável pelo funcionamento desta página (a lista de componentes é exportada para module.ts); indicando a função do usuário para o qual o trabalho com esta página está disponível e as configurações de navegação da barra lateral.

No componente responsável pelo funcionamento da página, devemos definir templateUrl, passando ali o caminho para o arquivo html com marcação. Dentro do controlador, através da injeção de dependência, podemos acessar até 2 importantes serviços angulares:

  • backendSrv - um serviço que fornece interação com o servidor API Grafana;
  • datasourceSrv - um serviço que fornece interação local com todas as fontes de dados instaladas em seu Grafana (por exemplo, o método .getAll() - retorna uma lista de todas as fontes de dados instaladas; .get( ) – retorna um objeto de instância de uma fonte de dados específica.

Desenvolvimento de um plugin para Grafana: uma história de figurões

Desenvolvimento de um plugin para Grafana: uma história de figurões

Desenvolvimento de um plugin para Grafana: uma história de figurões

Parte 4: fonte de dados

Do ponto de vista do Grafana, datasource é exatamente o mesmo plugin que todos os outros: tem seu próprio ponto de entrada module.js, existe um arquivo com meta informações plugin.json. Ao desenvolver um plugin com type = app, podemos interagir com fontes de dados existentes (por exemplo, prometheus-datasource) e as nossas próprias, que podemos armazenar diretamente no diretório do plugin (dist/datasource/*) ou instalar como uma dependência. No nosso caso, a fonte de dados vem com o código do plugin. Também é necessário ter um template config.html e um controlador ConfigCtrl, que será utilizado para a página de configuração da instância do datasource e o controlador Datasource, que implementa a lógica do seu datasource.

No plug-in KubeGraf, do ponto de vista da interface do usuário, a fonte de dados é uma instância de um cluster Kubernetes que implementa os seguintes recursos (o código-fonte está disponível по ссылке):

  • coletando dados do servidor API k8s (obtendo uma lista de namespaces, implantações...)
  • proxy de solicitações para prometheus-datasource (que é selecionado nas configurações do plug-in para cada cluster específico) e formatação de respostas para usar dados em páginas estáticas e em painéis.
  • atualização de dados em páginas estáticas de plugins (com uma taxa de atualização definida).
  • processando consultas para gerar uma planilha de modelo em painéis grafana (método metriFindQuery())

Desenvolvimento de um plugin para Grafana: uma história de figurões

Desenvolvimento de um plugin para Grafana: uma história de figurões

Desenvolvimento de um plugin para Grafana: uma história de figurões

  • teste de conexão com o 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"};
       })
}

Um outro ponto interessante, em nossa opinião, é a implementação de um mecanismo de autenticação e autorização para a fonte de dados. Normalmente, prontos para uso, podemos usar o componente integrado do Grafana datasourceHttpSettings para configurar o acesso à fonte de dados final. Usando este componente, podemos configurar o acesso à fonte de dados http especificando o url e as configurações básicas de autenticação/autorização: login-password ou client-cert/client-key. Para implementar a capacidade de configurar o acesso usando um token de portador (o padrão de fato para k8s), tivemos que fazer alguns ajustes.

Para resolver este problema, você pode usar o mecanismo integrado “Plugin Routes” do Grafana (mais detalhes em página de documentação oficial). Nas configurações da nossa fonte de dados, podemos declarar um conjunto de regras de roteamento que serão processadas pelo servidor proxy grafana. Por exemplo, para cada endpoint individual é possível definir cabeçalhos ou URLs com possibilidade de modelagem, cujos dados podem ser obtidos dos campos jsonData e secureJsonData (para armazenar senhas ou tokens em formato criptografado). Em nosso exemplo, consultas como /__proxy/api/v1/namespaces será proxy para a URL do formulário
/api/v8/namespaces com o cabeçalho Authorization: Bearer.

Desenvolvimento de um plugin para Grafana: uma história de figurões

Desenvolvimento de um plugin para Grafana: uma história de figurões

Naturalmente, para trabalhar com o servidor api k8s precisamos de um usuário com acesso somente leitura, manifestos para criação que você também pode encontrar em código fonte do plugin.

Parte 5: lançamento

Desenvolvimento de um plugin para Grafana: uma história de figurões

Depois de escrever seu próprio plugin Grafana, você naturalmente desejará torná-lo disponível publicamente. No Grafana esta é uma biblioteca de plugins disponível aqui grafana.com/grafana/plugins

Para que seu plugin esteja disponível na loja oficial, você precisa fazer um PR em este repositórioadicionando conteúdo como este ao arquivo repo.json:

Desenvolvimento de um plugin para Grafana: uma história de figurões

onde version é a versão do seu plugin, url é um link para o repositório e commit é o hash do commit para o qual uma versão específica do plugin estará disponível.

E na saída você verá uma foto maravilhosa como:

Desenvolvimento de um plugin para Grafana: uma história de figurões

Os dados serão obtidos automaticamente de seu Readme.md, Changelog.md e do arquivo plugin.json com a descrição do plugin.

Parte 6: em vez de conclusões

Não paramos de desenvolver nosso plugin após o lançamento. E agora estamos trabalhando para monitorar corretamente o uso dos recursos dos nós do cluster, introduzindo novos recursos para melhorar a UX, e também coletando uma grande quantidade de feedback recebido após a instalação do plugin tanto por nossos clientes quanto por pessoas no GitHub (se você sair seu problema ou pull request, ficarei muito feliz :)

Esperamos que este artigo ajude você a entender uma ferramenta tão maravilhosa como o Grafana e, talvez, a escrever seu próprio plugin.

Obrigado!)

Fonte: habr.com

Adicionar um comentário