Astuces de traitement métrique dans Kapacitor

Très probablement, personne ne se demande aujourd'hui pourquoi il doit collecter des mesures de service. L'étape logique suivante consiste à configurer l'alerte pour les mesures collectées, qui vous avertira de tout écart dans les données dans les canaux qui vous conviennent (courrier, Slack, Télégramme). Dans le service de réservation en ligne des hôtels Ostrovok.ru , toutes les mesures de nos services sont versées dans InfluxDB et affichées dans Grafana, une alerte de base y est également définie. Pour des tâches telles que "vous devez calculer quelque chose et le comparer", nous utilisons Kapacitor.


Kapacitor fait partie de la pile TICK qui peut gérer les métriques d'InfluxDB. Il peut connecter plusieurs dimensions entre elles (joindre), calculer quelque chose d'utile à partir des données reçues, réécrire le résultat dans InfluxDB, envoyer une alerte à Slack / Telegram / mail.

La pile entière a une documentation cool et détaillée, mais il y a toujours des choses utiles qui ne sont pas explicitement spécifiées dans les manuels. Dans cet article, j'ai décidé de rassembler un certain nombre de conseils utiles non évidents (la syntaxe de base TICKscipt est décrite ici ) et de montrer comment ils peuvent être appliqués, en utilisant un exemple de résolution de l'un de nos problèmes.

C'est parti!

float & int, erreurs de calcul


Problème absolument standard, il est résolu par les castes:

var alert_float = 5.0 var alert_int = 10 data|eval(lambda: float("value") > alert_float OR float("value") < float("alert_int")) 

Utilisation de default ()


Si la balise / le champ n'est pas rempli, des erreurs se produiront dans les calculs:

 |default() .tag('status', 'empty') .field('value', 0) 

remplir la jointure (intérieure vs extérieure)


Par défaut, la jointure supprimera des points où il n'y a pas de données (internes).
Avec fill ('null'), la jointure externe sera exécutée, après quoi vous devrez faire default () et remplir les valeurs vides:

 var data = res1 |join(res2) .as('res1', 'res2) .fill('null') |default() .field('res1.value', 0.0) .field('res2.value', 100.0) 

Il y a encore une nuance. Si dans l'exemple ci-dessus l'une des séries (res1 ou res2) est vide, la série finale (données) sera également vide. Il y a plusieurs tickets sur ce sujet sur le github ( 1633 , 1871 , 6967 ) - nous attendons des correctifs et souffrons un peu.

Utilisation de conditions dans les calculs (si dans lambda)


 |eval(lambda: if("value" > 0, true, false) 

Les cinq dernières minutes du pipeline sur la période


Par exemple, vous devez comparer les valeurs des cinq dernières minutes avec la semaine précédente. Vous pouvez prendre deux paquets de données avec deux batch'ami distincts ou extraire une partie des données d'une période plus longue:

  |where(lambda: duration((unixNano(now()) - unixNano("time"))/1000, 1u) < 5m) 

Une alternative pour les cinq dernières minutes pourrait être d'utiliser le nœud BarrierNode, qui coupe les données avant l'heure spécifiée:

 |barrier() .period(5m) 

Exemples d'utilisation de modèles Go'sh dans un message


Les modèles correspondent au format du package text.template , voici quelques tâches courantes.

if-else


Nous mettons les choses en ordre, nous ne déclenchons pas les gens avec le texte encore une fois:

 |alert() ... .message( '{{ if eq .Level "OK" }}It is ok now{{ else }}Chief, everything is broken{{end}}' ) 

Deux décimales dans le message


Amélioration de la lisibilité du message:

 |alert() ... .message( 'now value is {{ index .Fields "value" | printf "%0.2f" }}' ) 

Développement de variables dans le message


Nous affichons plus d'informations dans le message pour répondre à la question «Pourquoi crie-t-il?»

 var warnAlert = 10 |alert() ... .message( 'Today value less then '+string(warnAlert)+'%' ) 

Identifiant d'alerte unique


La bonne chose quand il y a plus d'un groupe dans les données, sinon une seule alerte sera générée:

 |alert() ... .id('{{ index .Tags "myname" }}/{{ index .Tags "myfield" }}') 

Gestionnaire personnalisé


La grande liste de gestionnaires contient exec, qui vous permet d'exécuter votre script avec les paramètres passés (stdin) - créativité et plus encore!

L'un de nos outils personnalisés est un petit script python pour envoyer des notifications à Slack.
Tout d'abord, nous voulions envoyer une photo d'une grafana protégée par autorisation dans le message. Après - écrivez OK dans le fil de l'alerte précédente du même groupe, et non sous forme de message distinct. Un peu plus tard - pour ajouter l'erreur la plus courante au cours des X dernières minutes au message.

Un sujet distinct est la communication avec d'autres services et toutes les actions lancées par une alerte (uniquement si votre surveillance fonctionne suffisamment bien).
Un exemple de description d'un gestionnaire, où slack_handler.py est notre script auto-écrit:

 topic: slack_graph id: slack_graph.alert match: level() != INFO AND changed() == TRUE kind: exec options: prog: /sbin/slack_handler.py args: ["-c", "CHANNELID", "--graph", "--search"] 

Comment débuter?


Option de sortie de journal


 |log() .level("error") .prefix("something") 

Watch (cli): kapacitor -url host-or-ip : 9092 logs lvl = error

Variante avec httpOut


Affiche les données dans le pipeline actuel:

 |httpOut('something') 

Regardez (obtenez): host-or-ip : 9092 / kapacitor / v1 / tasks / task_name / quelque chose

Schéma d'exécution


  • Chaque tâche renvoie un arbre d'exécution avec des nombres utiles au format graphviz .
  • Prenez le bloc de points .
  • Nous insérons dans le spectateur, profitez-en .


Où puis-je trouver un râteau


horodatage influxdb en écriture différée


Par exemple, nous configurons une alerte pour la somme des demandes par heure (groupBy (1h)) et voulons enregistrer l'incident dans influxdb (pour montrer magnifiquement le fait d'un problème sur le graphique dans grafana).

influxDBOut () écrira la valeur de temps de l'alerte à l'horodatage, respectivement, le point sur le graphique sera enregistré plus tôt / plus tard que l'alerte est arrivée.

Lorsque la précision est requise: nous contournons ce problème en appelant un gestionnaire personnalisé, qui écrira les données dans influxdb avec l'horodatage actuel.

docker, construire et déployer


Au démarrage, kapacitor peut charger des tâches, des modèles et des gestionnaires à partir du répertoire spécifié dans la configuration dans le bloc [load].

Pour créer correctement une tâche, les éléments suivants sont nécessaires:

  1. Nom de fichier - se développe en nom id / script
  2. Type - flux / lot
  3. dbrp - mot clé pour indiquer dans quelle base de données + politique le script fonctionne (dbrp "fournisseur". "autogène")

S'il n'y a pas de ligne avec dbrp dans certaines tâches par lots, l'ensemble du service refusera de démarrer et d'écrire honnêtement à ce sujet dans le journal.

Dans chronograf, au contraire, cette ligne ne devrait pas l'être, elle n'est pas acceptée via l'interface et lance une erreur.

Hack lors de la construction d'un conteneur: Dockerfile se ferme avec -1 s'il existe des lignes avec //.+dbrp, qui comprendra immédiatement la raison du fichier lors de la construction de la construction.

rejoindre un à plusieurs


Exemple de tâche: vous devez prendre le 95e centile du temps de fonctionnement du service par semaine, comparer chaque minute des 10 derniers avec cette valeur.

Vous ne pouvez pas joindre un à plusieurs, le dernier / moyen / médian par un groupe de points transforme le nœud en flux, l'erreur "ne peut pas ajouter de bords enfants non appariés: batch -> flux" sera de retour.

Le résultat du lot, en tant que variable dans une expression lambda, n'est pas non plus substitué.

Il existe une option pour enregistrer les numéros nécessaires du premier lot dans un fichier via udf et charger ce fichier via sideload.

Qu'avons-nous décidé?


Nous avons environ 100 fournisseurs d'hôtels, chacun d'entre eux peut avoir plusieurs connexions, appelons cela un canal. Il y a environ 300 de ces canaux; chacun des canaux peut tomber. De toutes les mesures enregistrées, nous surveillerons le taux d'erreur (demandes et erreurs).

Pourquoi pas grafana?


Les alertes d'erreur configurées dans grafan présentent plusieurs inconvénients. Certains critiques, certains peuvent fermer les yeux, selon la situation.

Grafana ne sait pas calculer entre dimensions + alerte, mais nous avons besoin d'un taux (requêtes-erreurs) / requêtes.

Les erreurs semblent vicieuses:



Et moins vicieux lorsqu'il est vu avec des demandes réussies:



D'accord, nous pouvons pré-calculer le taux du service avant grafana, et dans certains cas, il le fera. Mais pas le nôtre, car pour chaque canal, son ratio est considéré comme «normal», et les alertes fonctionnent selon des valeurs statiques (on regarde avec nos yeux, on change, si souvent alerte).

Ce sont des exemples de «normal» pour différents canaux:





Nous négligeons le paragraphe précédent et supposons que tous les fournisseurs ont une image "normale". Maintenant, tout va bien, et pouvons-nous nous en sortir avec des alertes dans Grafana?
Nous pouvons, mais ne le voulons vraiment pas, car nous devons choisir l'une des options:
a) faire de nombreux graphiques pour chaque canal séparément (et les accompagner douloureusement)
b) laisser un graphique avec tous les canaux (et se perdre dans les lignes colorées et les alertes réglées)



Comment as-tu fait ça?


Encore une fois, la documentation a un bon exemple de départ ( Calcul des taux sur des séries jointes ), vous pouvez jeter un œil ou prendre comme base des tâches similaires.

Qu'avez-vous fait en conséquence:

  • rejoindre deux épisodes en quelques heures, regroupés par chaîne;
  • remplir la série par groupes, s'il n'y avait pas de données;
  • comparer la médiane des 10 dernières minutes avec les données précédentes;
  • nous hurlons si nous trouvons quelque chose;
  • écrire les taux calculés et les alertes survenues dans influxdb;
  • envoyez un message utile à slack.

À mon avis, nous avons réussi aussi magnifiquement que possible tout ce que nous aimerions obtenir à la sortie (et même un peu plus avec des gestionnaires personnalisés).

Sur github.com, vous pouvez voir l' exemple de code et le diagramme minimal (graphviz) du script résultant.

Exemple du code résultant:
 dbrp "supplier"."autogen" var name = 'requests.rate' var grafana_dash = 'pczpmYZWU/mydashboard' var grafana_panel = '26' var period = 8h var todayPeriod = 10m var every = 1m var warnAlert = 15 var warnReset = 5 var reqQuery = 'SELECT sum("count") AS value FROM "supplier"."autogen"."requests"' var errQuery = 'SELECT sum("count") AS value FROM "supplier"."autogen"."errors"' var prevErr = batch |query(errQuery) .period(period) .every(every) .groupBy(1m, 'channel', 'supplier') var prevReq = batch |query(reqQuery) .period(period) .every(every) .groupBy(1m, 'channel', 'supplier') var rates = prevReq |join(prevErr) .as('req', 'err') .tolerance(1m) .fill('null') //   ,     |default() .field('err.value', 0.0) .field('req.value', 0.0) // if  lambda:  ,     |eval(lambda: if("err.value" > 0, 100.0 * (float("req.value") - float("err.value")) / float("req.value"), 100.0)) .as('rate') //      rates |influxDBOut() .quiet() .create() .database('kapacitor') .retentionPolicy('autogen') .measurement('rates') //     10 ,   var todayRate = rates |where(lambda: duration((unixNano(now()) - unixNano("time")) / 1000, 1u) < todayPeriod) |median('rate') .as('median') var prevRate = rates |median('rate') .as('median') var joined = todayRate |join(prevRate) .as('today', 'prev') |httpOut('join') var trigger = joined |alert() .warn(lambda: ("prev.median" - "today.median") > warnAlert) .warnReset(lambda: ("prev.median" - "today.median") < warnReset) .flapping(0.25, 0.5) .stateChangesOnly() //   message      .message( '{{ .Level }}: {{ index .Tags "channel" }} err/req ratio ({{ index .Tags "supplier" }}) {{ if eq .Level "OK" }}It is ok now{{ else }} '+string(todayPeriod)+' median is {{ index .Fields "today.median" | printf "%0.2f" }}%, by previous '+string(period)+' is {{ index .Fields "prev.median" | printf "%0.2f" }}%{{ end }} http://grafana.ostrovok.in/d/'+string(grafana_dash)+ '?var-supplier={{ index .Tags "supplier" }}&var-channel={{ index .Tags "channel" }}&panelId='+string(grafana_panel)+'&fullscreen&tz=UTC%2B03%3A00' ) .id('{{ index .Tags "name" }}/{{ index .Tags "channel" }}') .levelTag('level') .messageField('message') .durationField('duration') .topic('slack_graph') // "today.median"   "value",        (keep) trigger |eval(lambda: "today.median") .as('value') .keep() |influxDBOut() .quiet() .create() .database('kapacitor') .retentionPolicy('autogen') .measurement('alerts') .tag('alertName', name) 


Quelle conclusion?


Kapacitor est très bon pour surveiller les alertes avec un groupe de groupes, effectuer des calculs supplémentaires sur des mesures déjà enregistrées, effectuer des actions personnalisées et exécuter des scripts (udf).

Le seuil d'entrée n'est pas très élevé - essayez-le si grafana ou d'autres outils ne satisfont pas pleinement votre liste de souhaits.

Source: https://habr.com/ru/post/fr478702/


All Articles