
@jcutrer
Aujourd'hui, nous parlerons des journaux d'événements, des mesures quantitatives et de la surveillance de tout cela afin d'augmenter le taux de réponse de l'équipe aux incidents et de réduire les temps d'arrêt du système cible.
Erlang / OTP en tant que cadre et idéologie pour la construction de systèmes distribués nous donne des approches réglementées de développement, d'outils et de mise en œuvre de composants standard. Disons que nous avons utilisé le potentiel d'OTP et sommes allés du prototype à la production. Notre projet Erlang se sent bien sur les serveurs de combat, la base de code évolue constamment, de nouvelles exigences et fonctionnalités apparaissent, de nouvelles personnes viennent dans l'équipe, et tout semble aller bien. Mais parfois, quelque chose tourne mal et des problèmes techniques, multipliés par le facteur humain, peuvent conduire à un accident.
Puisqu'il est impossible de jeter des pailles pour absolument tous les cas possibles de pannes et de problèmes, ou que ce n'est pas économiquement faisable, il est nécessaire de réduire les temps d'arrêt du système en cas de pannes par les solutions de gestion et logicielles.
Dans les systèmes d'information, il y aura toujours une probabilité d'occurrence de défaillances de nature diverse:
- Pannes matérielles et pannes de courant
- Défaillances du réseau: erreurs de configuration, courbes du micrologiciel
- Erreurs logiques: des erreurs de codage des algorithmes aux problèmes architecturaux qui surviennent aux frontières des sous-systèmes et des systèmes.
- Problèmes de sécurité et attaques et hacks associés, y compris la fraude interne.
On distingue immédiatement la responsabilité: la surveillance des infrastructures, par exemple, organisée par zabbix, sera responsable du fonctionnement des équipements informatiques et des réseaux de transmission de données. Beaucoup a été écrit sur l'installation et la configuration d'une telle surveillance, nous ne la répéterons pas.
Du point de vue du développeur, le problème de l'accessibilité et de la qualité réside dans le plan de la détection précoce des erreurs et des problèmes de performance et de la réponse rapide à ceux-ci. Cela nécessite des approches et des moyens d'évaluation. Essayons donc de dériver des métriques quantitatives, en analysant qui, à différentes étapes du développement et du fonctionnement du projet, nous pouvons améliorer considérablement la qualité.
Systèmes d'assemblage
Permettez-moi de vous rappeler une fois de plus l'importance de l'approche d'ingénierie et des tests dans le développement de logiciels. Erlang / OTP propose deux cadres de test à la fois: eunit et test commun.
En tant que mesures pour l'évaluation initiale de l'état de la base de code et de sa dynamique, vous pouvez utiliser le nombre de tests réussis et problématiques, leur temps d'exécution et le pourcentage de couverture de code avec les tests. Les deux cadres permettent d'enregistrer les résultats des tests au format Junit.
Par exemple, pour rebar3 et ct, ajoutez les lignes suivantes à rebar.config:
{cover_enabled, true}. {cover_export_enabled, true}. {ct_opts,[ {ct_hooks, [{cth_surefire, [{path, "report.xml"}]}]} ]}.
Le nombre de tests réussis et infructueux vous permet de construire un graphique de tendance:

en regardant lequel, vous pouvez évaluer la dynamique de l'équipe et la régression des tests. Par exemple, dans Jenkins, ce graphique peut être obtenu à l'aide du plug-in Test Results Analyzer.
Si les tests deviennent rouges ou commencent à s'exécuter pendant une longue période, les métriques permettront de finaliser la version même au stade de l'assemblage et des tests automatiques.
Mesures d'application
En plus des mesures du système d'exploitation, la surveillance doit inclure des mesures d'application, telles que le nombre de vues par seconde, le nombre de paiements et d'autres indicateurs critiques.
Dans mes projets, j'utilise un modèle comme ${application}.${metrics_type}.${name}
pour nommer les métriques. Cette dénomination vous permet d'obtenir des listes de métriques du formulaire
messaging.systime_subs.messages.delivered = 1654 messaging.systime_subs.messages.proxied = 0 messaging.systime_subs.messages.published = 1655 messaging.systime_subs.messages.skipped = 3
Peut-être que plus il y a de métriques, plus il est facile de comprendre ce qui se passe dans un système complexe.
Métriques Erlang VM
Une attention particulière doit être accordée à la surveillance d'Erlang VM. L'idéologie de laisser tomber en panne est magnifique, et la bonne utilisation d'OTP aidera certainement à lever les parties tombées de l'application à l'intérieur d'Erlang VM. Mais n'oubliez pas Erlang VM lui-même, car il est difficile de le supprimer, mais c'est possible. Toutes les options sont basées sur l'épuisement des ressources. Nous listons les principaux:
Débordement de la table Atom.
Les atomes sont des identificateurs dont le but principal est d'améliorer la lisibilité du code. Les atomes créés une fois restent à jamais dans la mémoire de l'instance de machine virtuelle Erlang, car ils ne sont pas effacés par le garbage collector. Pourquoi cela se produit-il? Le garbage collector fonctionne séparément dans chaque processus avec les données de ce processus, tandis que les atomes peuvent être répartis sur les structures de données de nombreux processus.
Par défaut, 1 048 576 atomes peuvent être créés. Dans les articles sur la mort d'Erlang VM, vous pouvez généralement trouver quelque chose comme ça.
[list_to_atom(integer_to_list(I)) || I <- lists:seq(erlang:system_info(atom_count), erlang:system_info(atom_limit))]
comme illustration de cet effet. Il semblerait qu'un problème artificiel soit également inaccessible dans les systèmes réels, mais il y a des cas ... Par exemple, dans le gestionnaire d'API externe, lors de l'analyse des demandes, binary_to_atom/2
place de binary_to_existing_atom/2
ou list_to_atom/1
au lieu de list_to_existing_atom/1
.
Les paramètres suivants doivent être utilisés pour surveiller l'état des atomes:
erlang:memory(atom_used)
- quantité de mémoire utilisée pour les atomeserlang:system_info(atom_count)
- le nombre d'atomes créés dans le système. Avec erlang:system_info(atom_limit)
, l'utilisation des atomes peut être calculée.
Fuites de processus.
Je voudrais dire tout de suite que lorsque process_limit (+ P est atteint, l'argument erlang erlang vm ne tombe pas, mais il passe en état d'urgence, par exemple, il sera très probablement impossible de s'y connecter. En fin de compte, le manque de mémoire disponible lors de l'allocation aux processus ayant fait l'objet d'une fuite entraînera le blocage d'erlang vm.
erlang:system_info(process_count)
- le nombre de processus actifs en ce moment. Avec erlang:system_info(process_limit)
, l'utilisation du processus peut être calculée.erlang:memory(processes)
- mémoire allouée aux processuserlang:memory(processes_used)
- mémoire utilisée pour les processus.
Débordement du processus de boîte aux lettres.
Un exemple typique d'un tel problème est le processus expéditeur envoie des messages au processus destinataire sans attendre de confirmation, tandis que receive
dans le processus destinataire ignore tous ces messages en raison d'un modèle manquant ou incorrect. Par conséquent, les messages sont accumulés dans la boîte aux lettres. Bien qu'erlang dispose d'un mécanisme pour ralentir l'expéditeur au cas où le gestionnaire ne pourrait pas gérer le traitement, de toute façon, après épuisement de la mémoire disponible, vm se bloque.
Pour comprendre s'il y a des problèmes avec le débordement de la boîte aux lettres, etop vous aidera.
$ erl -name etop@host -hidden -s etop -s erlang halt -output text -node dest@host -setcookie some_cookie -tracing off -sort msg_q -interval 1 -lines 25

En tant que mesure de surveillance continue, vous pouvez prendre le nombre de processus problématiques. Pour les identifier, vous pouvez utiliser la fonction suivante:
top_msq_q()-> [{P, RN, L, IC, ST} || P <- processes(), { _, L } <- [ process_info(P, message_queue_len) ], L >= 1000, [{_, RN}, {_, IC}, {_, ST}] <- [process_info(P, [registered_name, initial_call, current_stacktrace]) ] ].
De plus, cette liste peut être journalisée, puis lors de la réception d'une notification de surveillance, l'analyse du problème est simplifiée.
Binaires qui fuient.
La mémoire pour les binaires volumineux (plus de 64 octets) est allouée dans le tas général. Le bloc alloué possède un compteur de référence indiquant le nombre de processus qui y ont accès. Après la réinitialisation du compteur, le nettoyage a lieu. Le système le plus simple, mais, comme on dit, il y a des nuances. En principe, il est possible qu'un processus génère tellement de déchets sur le tas que le système n'a pas assez de mémoire pour effectuer le nettoyage.
erlang:memory(binary)
agit comme une métrique de surveillance, montrant la mémoire allouée aux binaires.
Ainsi, les cas menant à la chute de vm sont triés, cependant, à côté d'eux, il est agréable de surveiller des paramètres non moins importants qui affectent directement ou indirectement le bon fonctionnement de vos applications:
- La mémoire utilisée par les
erlang:memory(ets)
ETS: erlang:memory(ets)
. - Mémoire pour les modules compilés:
erlang:memory(code)
.
Si vos solutions n'utilisent pas de compilation de code dynamique, cette option peut être exclue.
Je voudrais également mentionner erlydtl. Si vous compilez des modèles de manière dynamique, la compilation crée un faisceau qui est chargé dans la mémoire vm. Cela peut également provoquer des fuites de mémoire. - Mémoire système:
erlang:memory(system)
. Affiche la consommation de mémoire d'exécution erlang. - Mémoire totale consommée:
erlang:memory(total)
. Il s'agit de la quantité de mémoire consommée par les processus et l'exécution. - Informations sur les réductions:
erlang:statistics(reductions)
. - Nombre de processus et de ports prêts à être exécutés:
erlang:statistics(run_queue)
. - La disponibilité de l'
erlang:statistics(runtime)
vm: erlang:statistics(runtime)
vous permet de comprendre s'il y a eu un redémarrage sans analyse de journal. - Activité du réseau:
erlang:statistics(io)
.
Envoi de métriques à zabbix
Nous allons créer un fichier contenant les métriques d'application et les métriques erlang vm, que nous mettrons à jour toutes les N secondes. Pour chaque noeud erlang, le fichier de mesures doit contenir les mesures des applications qui s'exécutent dessus et les mesures de l'instance erlang vm. Le résultat devrait être quelque chose comme ceci:
messaging.systime_subs.messages.delivered = 1654 messaging.systime_subs.messages.proxied = 0 messaging.systime_subs.messages.published = 1655 messaging.systime_subs.messages.skipped = 3 …. erlang.io.input = 2205723664 erlang.io.output = 1665529234 erlang.memory.binary = 1911136 erlang.memory.ets = 1642416 erlang.memory.processes = 23596432 erlang.memory.processes_used = 23598864 erlang.memory.system = 50883752 erlang.memory.total = 74446048 erlang.processes.count = 402 erlang.processes.run_queue = 0 erlang.reductions = 148412771 ....
En utilisant zabbix_sender
nous enverrons ce fichier à zabbix, où une représentation graphique et la possibilité de créer des déclencheurs d'automatisation et de notification seront déjà disponibles.
Maintenant que nous avons des métriques dans le système de surveillance et des déclencheurs d'automatisation et des événements de notification créés sur leur base, nous avons une chance d'éviter les accidents en réagissant à l'avance à toutes les déviations dangereuses d'un état pleinement fonctionnel.
Collecte centralisée des journaux
Lorsqu'il y a 1-2 serveurs dans un projet, vous pouvez probablement vivre sans collection de journaux centrale, mais dès qu'un système distribué avec de nombreux serveurs, clusters, environnements apparaît, il devient nécessaire de résoudre le problème de la collecte et de la visualisation pratique des journaux.
Pour écrire des logs dans mes projets, j'utilise de la lager. Souvent, entre le prototype et la production, les projets passent par les étapes suivantes de collecte des journaux:
- Journalisation la plus simple avec sortie vers un fichier local ou même vers stdout (lager_file_backend)
- Collecte centralisée des journaux à l'aide, par exemple, de syslogd et envoi automatique de journaux au collecteur. Pour un tel schéma, lager_syslog convient.
Le principal inconvénient du schéma est que vous devez vous rendre sur le serveur de collecte de journaux, trouver le fichier avec les journaux nécessaires et filtrer les événements à la recherche de ceux nécessaires au débogage. - Collecte centralisée des journaux avec stockage dans le référentiel avec la possibilité de filtrer et de rechercher par enregistrements.
À propos des inconvénients, des avantages et des métriques quantitatives qui peuvent être appliquées à l'aide de ces dernières, et nous parlerons au prisme d'une implémentation spécifique - lager_clickhouse
, que j'utilise dans la plupart des projets développés. Quelques mots sur lager_clickhouse
. Il s'agit du backend lager pour enregistrer les événements dans clickhouse. Pour le moment, il s'agit d'un projet interne, mais il est prévu de l'ouvrir. Lors du développement de lager_clickhouse, j'ai dû contourner certaines fonctionnalités de clickhouse, par exemple, utiliser la mise en mémoire tampon des événements pour éviter de faire des demandes fréquentes dans clickhouse. L'effort dépensé a porté ses fruits avec un fonctionnement stable et de bonnes performances.
Le principal inconvénient de l'approche du stockage dans le référentiel est l'essence supplémentaire de clickhouse et la nécessité de développer du code pour y stocker des événements et une interface utilisateur pour analyser et rechercher des événements. De plus, pour certains projets, il peut être essentiel d'utiliser tcp pour envoyer des journaux.
Mais les avantages, me semble-t-il, l'emportent sur tous les inconvénients possibles.
Recherche d'événements simple et rapide:
- Filtrage par date sans avoir à rechercher un fichier / fichiers sur un serveur central contenant une série d'événements.
- Filtrage par environnement. Les journaux de différents sous-systèmes et souvent de différents clusters sont écrits dans un référentiel. À l'heure actuelle, la séparation se produit en fonction des étiquettes définies sur chaque nœud du cluster.
- Filtrer par nom d'hôte
- Filtrage par nom du module qui a envoyé l'événement
- Filtrage d'événements
- Recherche textuelle
Un exemple d'affichage de l'interface d'affichage des journaux est illustré dans la capture d'écran:

Capacité d'automatisation.
Avec l'introduction du stockage des journaux, il est devenu possible en temps réel de recevoir des informations sur le nombre d'erreurs, la survenue de pannes critiques et l'activité du système. En introduisant certaines limites, nous pouvons générer des événements d'urgence lorsque le système quitte l'état fonctionnel, dont les gestionnaires effectueront des actions d'automatisation pour éliminer cet état et enverront des notifications aux membres de l'équipe responsables de la fonctionnalité:
- Lorsqu'une erreur critique se produit.
- En cas d'erreurs de masse (la dérivée temporelle augmente plus vite qu'une certaine limite).
- Une métrique distincte est le taux de génération d'événements, c'est-à-dire le nombre de nouveaux événements qui apparaissent dans le journal des événements. Vous pouvez presque toujours connaître la quantité approximative de journaux générés par un projet par unité de temps. S'il est dépassé plusieurs fois, il est fort probable que quelque chose ne va pas.
La poursuite du développement du thème de l'automatisation de la gestion des événements d'urgence a été l'utilisation de scripts lua. Tout développeur ou administrateur peut écrire un script pour le traitement des journaux et des métriques. Les scripts apportent de la flexibilité et vous permettent de créer des scripts et des notifications d'automatisation personnalisés.
Résumé
Pour comprendre les processus se produisant dans le système et enquêter sur les incidents, il est essentiel d'avoir des indicateurs quantitatifs et des journaux d'événements, ainsi que des outils pratiques pour les analyser. Plus nous collectons d'informations sur le système, plus il est facile d'analyser son comportement et de corriger les problèmes, même au stade de leur apparition. Dans le cas où nos mesures ne fonctionneraient pas, nous avons toujours des calendriers et des journaux détaillés de l'incident à notre disposition.
Et comment opérez-vous des solutions sur Erlang / Elixir et quels cas intéressants avez-vous rencontrés en production?