Astuces pour traiter les métriques dans Kapacitor

Aujourd'hui, personne ne se pose de questions sur la nécessité de collecter des indicateurs de service. L'étape suivante consiste à configurer une alerte pour les indicateurs collectés, qui vous avertira de tout écart dans les données sur les canaux qui vous conviennent (mail, Slack, Telegram). Dans le service de réservation d'hôtel en ligne Ostrovok.ru Toutes les métriques de nos services sont intégrées à InfluxDB et affichées dans Grafana, où les alertes de base sont également configurées. Pour des tâches telles que « calculer un élément et le comparer », nous utilisons Kapacitor.

Astuces pour traiter les métriques dans Kapacitor
Kapacitor fait partie de la pile TICK et peut traiter les métriques d'InfluxDB. Il peut combiner plusieurs mesures, calculer des données utiles à partir des données reçues, renvoyer le résultat à InfluxDB et envoyer une alerte à Slack, Telegram ou par e-mail.

L'ensemble de la pile a un aspect cool et détaillé documentation, mais il existe toujours des choses utiles qui ne sont pas explicitement indiquées dans les manuels. Dans cet article, j'ai décidé de rassembler quelques conseils utiles et non évidents (la syntaxe de base de TICKscipt est décrite). ici) et montrez comment ils peuvent être appliqués en utilisant l’exemple de la résolution d’un de nos problèmes.

Allons-y!

float & int, erreurs de calcul

Un problème absolument standard, résolu par casting :

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 renseigné, des erreurs de calcul se produiront :

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

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

Par défaut, la jointure supprimera les points où il n'y a pas de données (internes).
Lorsque fill('null') une jointure externe sera effectuée, après quoi vous devrez effectuer un 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 une nuance ici. Si, dans l'exemple ci-dessus, l'une des séries (res1 ou res2) est vide, la série finale (données) le sera également. Plusieurs tickets à ce sujet sont disponibles sur GitHub (1633, 1871, 6967) – nous attendons des correctifs et souffrons un peu.

Utilisation des conditions dans les calculs (si dans lambda)

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

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

Par exemple, vous devez comparer les valeurs des cinq dernières minutes avec celles de la semaine précédente. Vous pouvez extraire deux lots de données distincts ou extraire 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 consiste à utiliser un nœud BarrierNode, qui coupe les données avant l'heure spécifiée :

|barrier()
        .period(5m)

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

Les modèles correspondent au format du package modèle de texte, voici quelques problèmes fréquemment rencontrés.

sinon

Mettons les choses en ordre et ne perturbons pas les gens avec du texte inutilement :

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

Deux chiffres après la virgule dans le message

Améliorer la lisibilité du message :

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

Déploiement des 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

Une chose nécessaire lorsqu'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" }}')

Gestionnaires personnalisés

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

L’une de nos coutumes est un petit script Python pour envoyer des notifications à Slack.
Au départ, nous voulions envoyer une image Grafana protégée par autorisation dans un message. Ensuite, nous avons indiqué « OK » dans le fil de discussion concernant l'alerte précédente du même groupe, et non dans un message séparé. Un peu plus tard, nous avons ajouté au message l'erreur la plus fréquente des X dernières minutes.

Un sujet distinct est la connexion avec d'autres services et toutes les actions initiées par l'alerte (uniquement si votre surveillance fonctionne suffisamment bien).
Un exemple de description de gestionnaire, où slack_handler.py est notre script personnalisé :

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éboguer ?

Option avec sortie à journaliser

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

Regarder (cli) : kapacitor -url hôte-ou-ip:9092 journaux lvl=erreur

Option avec httpOut

Affiche les données dans le pipeline actuel :

|httpOut('something')

Regarder (obtenir) : hôte-ou-ip:9092/kapacitor/v1/tasks/task_name/quelque chose

Schéma d'exécution

Où d'autre pouvez-vous vous faire ratisser ?

horodatage dans influxdb lors de l'écriture différée

Par exemple, nous avons configuré une alerte pour la somme des requêtes par heure (groupBy(1h)) et souhaitons enregistrer l'alerte qui s'est produite dans influxdb (pour bien montrer le fait du problème sur le graphique dans Grafana).

influxDBOut() écrira la valeur temporelle de l'alerte dans l'horodatage, de sorte que le point sur le graphique sera écrit plus tôt/plus tard que l'arrivée de l'alerte.

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 une tâche correctement, vous avez besoin des éléments suivants :

  1. Nom de fichier - se développe en identifiant/nom de script
  2. Type – flux/lot
  3. dbrp – mot-clé permettant de spécifier dans quelle base de données et politique le script fonctionne (dbrp "supplier"."autogen")

Si une tâche par lots n'a pas de ligne avec dbrp, l'ensemble du service refusera de démarrer et l'écrira honnêtement dans le journal.

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

Hack lors de la construction d'un conteneur : Dockerfile se termine avec -1 s'il y a des lignes avec //.+dbrp, ce qui vous permettra de comprendre immédiatement la raison de l'échec de la construction.

joindre un à plusieurs

Exemple de tâche : vous devez prendre le 95e percentile de la disponibilité du service pendant une semaine, comparer chaque minute des 10 dernières avec cette valeur.

Vous ne pouvez pas effectuer une jointure un-à-plusieurs, la dernière/moyenne/médiane sur un groupe de points transforme le nœud en un flux, l'erreur « impossible d'ajouter des arêtes enfants incompatibles : lot -> flux » sera renvoyée.

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

Il existe une option permettant d'enregistrer les numéros requis du premier lot dans un fichier via udf et de charger ce fichier via sideload.

Qu'est-ce qu'on résolvait avec ça ?

Nous avons environ 100 fournisseurs hôteliers, chacun pouvant avoir plusieurs connexions, ce que l'on appelle un canal. Il existe environ 300 canaux, chacun pouvant être déconnecté. Parmi toutes les mesures enregistrées, nous surveillerons le taux d'erreur (demandes et erreurs).

Pourquoi pas Grafana ?

Les alertes d'erreur configurées dans Grafana présentent plusieurs inconvénients. Certaines sont critiques, d'autres peuvent être ignorées, selon la situation.

Grafana ne peut pas faire de calculs interdimensionnels + d'alertes, mais nous avons besoin d'un taux (requêtes-erreurs)/requêtes.

Les erreurs semblent désagréables :

Astuces pour traiter les métriques dans Kapacitor

Et c'est moins mal si vous regardez les requêtes réussies :

Astuces pour traiter les métriques dans Kapacitor

D'accord, nous pouvons précalculer le taux dans le service avant Grafana, et dans certains cas, cela fonctionnera. Mais pas dans le nôtre, car chaque canal a son propre taux considéré comme « normal », et les alertes fonctionnent sur des valeurs statiques (nous les observons visuellement, et les modifions si les alertes sont fréquentes).

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

Astuces pour traiter les métriques dans Kapacitor

Astuces pour traiter les métriques dans Kapacitor

Ignorons le point précédent et supposons que tous les fournisseurs ont une situation « normale » similaire. Maintenant, tout va bien, et pouvons-nous nous en sortir avec les alertes dans Grafana ?
Nous pouvons, mais nous ne le voulons vraiment pas, car nous devons choisir l’une des options suivantes :
a) faire beaucoup de graphiques pour chaque canal séparément (et les maintenir péniblement)
b) laisser un graphique avec tous les canaux (et se perdre dans des lignes colorées et des alertes personnalisées)

Astuces pour traiter les métriques dans Kapacitor

Comment as-tu fait ?

Encore une fois, il y a un bon exemple de départ dans la documentation (Calcul des taux sur des séries jointes), vous pouvez y jeter un œil ou l'utiliser comme base dans des problèmes similaires.

Ce que nous avons fait à la fin :

  • rejoindre deux épisodes en quelques heures, en les regroupant par chaînes ;
  • nous complétons les séries 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 ;
  • crier si nous trouvons quelque chose ;
  • nous écrivons les taux calculés et les alertes survenues dans influxdb ;
  • envoyer un message utile à Slack.

À mon avis, nous avons réussi à obtenir tout ce que nous voulions obtenir au final de la plus belle des manières (et même un peu plus avec des handlers personnalisés).

Vous pouvez jeter un oeil sur github.com exemple de code и schéma minimal (graphviz) le script reçu.

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)

Et quelle est la conclusion?

Kapacitor est excellent pour surveiller et alerter un ensemble de groupes, effectuer des calculs supplémentaires basés sur des métriques 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 vos désirs.

Source: habr.com

Achetez un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Achetez un hébergement web fiable avec protection DDoS, serveurs VPS et VDS | ProHoster