Comment nous avons connecté Prometheus

D'une manière ou d'une autre, j'ai dû gérer les métriques de notre API, comme toujours (pas de temps?!) Pour ajouter plus tard - c'est très difficile et n'a pas encore été implémenté - cela signifie qu'il est temps de l'implémenter. Après quelques errances sur le net, le système de surveillance le plus populaire, me semblait-il, était Prometheus.


En utilisant Prometheus, nous pouvons surveiller diverses ressources informatiques, telles que: la mémoire, le processeur, le disque, la charge réseau. Il peut également être important pour nous de calculer le nombre d'appels aux méthodes de notre API ou de mesurer le temps de leur exécution, car plus la charge sur le système est élevée, plus son temps d'arrêt est coûteux. Et ici, Prométhée vient à notre secours. Cet article fournit, il me semble, les principaux points pour comprendre le travail de Prometheus et pour ajouter une collection de métriques à l'API. Par conséquent, nous commençons par le plus banal, avec une petite description.


Prometheus est un système open source et un SGBD Time Series écrit en Go et développé par SoundCloud. Il a une documentation officielle et un support pour des langages tels que: Go, Java ou Scala, Python, Ruby. Il existe un support non officiel pour d'autres langages, tels que: C #, C ++, C, Bash, Lua pour Nginx, Lua pour Tarantool et autres, toute la liste est sur le site officiel de Prometheus.


Tous les services Prometheus sont disponibles sous forme d'images Docker sur le Docker Hub ou Quay.io.


Prometheus est lancé par la commande docker run -p 9090:9090 prom/prometheus , qui le démarre avec la configuration par défaut et définit le port localhost:9090 Après cela, l'interface utilisateur de Prometheus sera disponible sur localhost:9090 .


Prometheus est un système de surveillance qui comprend divers outils pour configurer la surveillance des applications (points de terminaison) à l'aide du protocole HTTP. Lors de la connexion à Prometheus, l'API HTTP ne prend pas en charge "l'authentification de base". Si vous souhaitez utiliser l'authentification de base pour vous connecter à Prometheus, nous vous recommandons d'utiliser Prometheus conjointement avec un serveur proxy inverse et d'utiliser l'authentification au niveau du proxy. Vous pouvez utiliser n'importe quel proxy inverse avec Prometheus.


Les principaux composants de Prométhée:


  • un serveur qui collecte des métriques, les enregistre dans la base de données et nettoie;
  • packages pour collecter des métriques dans l'API;
  • Pushgateway - composant pour recevoir des métriques des applications pour lesquelles une demande Pull ne peut pas être utilisée;
  • Exportateurs - outils pour exporter des métriques à partir d'applications et de services tiers, installés sur des machines cibles;
  • AlertManager - gestionnaire de notifications (alertes), les alertes sont définies dans le fichier de configuration et définies par un ensemble de règles de métriques.
    Si, pendant le fonctionnement, la règle est respectée, une alerte est déclenchée et envoyée aux destinataires spécifiés par e-mail, Slack ou autres.

Les objets avec lesquels Prometheus travaille sont appelés métriques reçues des cibles via Pushgateway ou via les exportateurs.


Lors de la collecte des métriques, plusieurs méthodes de transmission sont utilisées:


  • Prometheus demande des métriques à la cible via une requête Pull, dont les paramètres sont spécifiés dans le fichier de configuration dans la section scrape_config pour chaque travail.
    Lorsque le système collecte des données, vous pouvez contrôler la fréquence de collecte et créer plusieurs configurations de collecte de données pour sélectionner une fréquence différente pour différents objets;
  • Les exportateurs vous permettent de collecter des métriques à partir de divers objets, par exemple: bases de données (MongoDB, SQL, etc.), courtiers de messages (RabbitMQ, EMQ, NSQ, etc.), équilibreurs de charge HTTP, etc.;
  • Pushgateway. Il peut être utilisé si nécessaire, lorsque l'application ne peut pas fournir directement la mesure à Prometheus; ou lorsque vous utilisez des travaux par lots qui n'ont pas la possibilité d'utiliser la demande d'extraction Prometheus.

Ainsi, toutes les métriques reçues seront stockées par Prometheus dans une base de données avec des horodatages.


La configuration


Prometheus est configuré à l'aide des drapeaux de ligne de commande et des fichiers de configuration fournis au format YAML. Les drapeaux de ligne de commande vous permettent de configurer des paramètres immuables, tels que: chemins, volumes de données stockés sur disque et en mémoire, etc. Le fichier de configuration vous permet de configurer tout ce qui concerne les travaux et de configurer les fichiers yaml de règles chargés. Tout est écrit dans le fichier de configuration globale, il vous permet de définir les paramètres généraux pour tout le monde et de mettre en évidence les paramètres des différentes sections de configuration séparément. Les paramètres que Prometheus interroge sont configurés dans le fichier de configuration dans la section scrape_configs.


Prometheus peut recharger les fichiers de configuration pendant le fonctionnement, si la nouvelle configuration n'est pas valide, elle ne sera pas appliquée. Le redémarrage du fichier de configuration est déclenché en envoyant la commande SIGHUP Prometheus ou en envoyant une demande HTTP POST à /-/reload , à condition que l' --web.enable-lifecycle soit --web.enable-lifecycle . Il rechargera également tous les fichiers de règles configurés.


Quels types de données sont utilisés


Prometheus stocke un modèle de données multidimensionnel personnalisé et utilise un langage de requête pour les données multidimensionnelles appelé PromQL. Prometheus stocke les données sous forme de séries temporelles; il prend en charge plusieurs options de stockage:


  • stockage sur disque local: toutes les 2 heures, les données qui ont été mises en mémoire tampon sont compressées et stockées sur le disque. Par défaut, le répertoire ./data est utilisé dans le répertoire de travail pour enregistrer les fichiers compressés;
  • Référentiel distant: Prometheus prend en charge l'intégration avec des référentiels tiers (par exemple: Kafka, PostgreSQL, Amazon S3, etc.) via l'adaptateur Protocol Buffer.

La série temporelle stockée est déterminée par la métrique et les métadonnées sous la forme de paires clé-valeur, bien que, si nécessaire, le nom de la métrique ne puisse pas être utilisé et la métrique elle-même ne sera constituée que de métadonnées. Une série chronologique peut être formellement définie comme <nom de la mesure> {<métadonnées>}. La clé est <nom métrique> {<métadonnées>} - ce que nous mesurons, et la valeur est la valeur réelle sous forme de nombre avec le type float64 (Prometheus ne prend en charge que ce type). La description de la clé contient des métadonnées (étiquettes), également décrites par des paires clé-valeur: <nom de l'étiquette> = "<valeur de l'étiquette>", <nom de l'étiquette> = "<valeur de l'étiquette>", ...


Lors du stockage des métriques, les types de données suivants sont utilisés:


  • Contre - compte le montant sur une période de temps. Ce type de mesures ne peut qu'augmenter (vous ne pouvez pas utiliser de valeurs négatives) ou réinitialiser la valeur.
    Il peut convenir, par exemple, pour compter le nombre de requêtes par minute ou le nombre d'erreurs par jour, le nombre de paquets réseau envoyés / reçus, etc.
  • Jauge - stocke les valeurs qui peuvent diminuer ou augmenter avec le temps.
    Gauge ne montre pas le développement des métriques sur une période de temps. À l'aide de la jauge, vous pouvez perdre des modifications métriques irrégulières au fil du temps.
  • Histogramme - enregistre plusieurs séries chronologiques: la somme totale de toutes les valeurs observées; le nombre d'événements qui ont été observés;
    compteurs cumulatifs (compartiments) - sont indiqués dans l'étiquette comme le="<upper inclusive bound>" .
    Les valeurs sont collectées dans les zones avec des limites supérieures personnalisées (compartiments).
  • Résumé - enregistre plusieurs séries chronologiques: la somme totale de toutes les valeurs observées; le nombre d'événements qui ont été observés;
    flux φ-quantiles (0 ≤ φ ≤ 1) des événements observés - sont indiqués dans l'étiquette par quantile="<φ>" .

Comment les données sont-elles enregistrées?


Prometheus recommande de "donner" 2/3 de RAM à une application en cours d'exécution.
Pour stocker des données en mémoire, Prometheus utilise des fichiers appelés chunk; chaque métrique a son propre fichier. Tous les fichiers de morceaux sont immuables, sauf le dernier dans lequel les données sont écrites. Les nouvelles données sont enregistrées en bloc et toutes les 2 heures, le flux d'arrière-plan combine les données et les écrit sur le disque. Chaque bloc de deux heures se compose d'un répertoire contenant un ou plusieurs fichiers de blocs contenant tous les échantillons de séries chronologiques pour cette période, ainsi qu'un fichier de métadonnées et un fichier d'index (qui indexe les noms des métriques et des étiquettes pour les séries chronologiques dans les fichiers de blocs). Si, dans l'heure qui suit, Prometheus n'écrit pas de données sur chunck, il sera alors enregistré sur disque et un nouveau chunck sera créé pour écrire des données. La durée de conservation maximale des données dans Prometheus est d'environ 21 jours.


Parce que la taille de la mémoire est fixe, les performances d'écriture et de lecture du système seront limitées par cette quantité de mémoire. La quantité de mémoire PTSDB est déterminée par la période de temps minimale, la période de collecte et le nombre de métriques de temps.


Prometheus dispose également d'un mécanisme WAL pour éviter la perte de données.


L'écriture anticipée (WAL) sérialise les opérations mémorisées sur un support permanent sous forme de fichiers journaux. En cas d'échec, les fichiers WAL peuvent être utilisés pour restaurer la base de données à son état cohérent en restaurant à partir des journaux.


Les fichiers journaux sont stockés dans un répertoire wal en segments de 128 Mo. Ces fichiers contiennent des données brutes qui n'ont pas encore été compressées, ils sont donc beaucoup plus volumineux que les fichiers de fragments ordinaires.


Prometheus stockera au moins 3 fichiers journaux, mais les serveurs à fort trafic peuvent voir plus de trois fichiers WAL, car il doit stocker au moins deux heures de données brutes.


Le résultat de l'utilisation de WAL est une réduction significative du nombre de demandes d'écriture sur le disque, car seul un fichier journal doit être écrit sur le disque, et pas chaque élément de données qui a été modifié à la suite de l'opération. Le fichier journal est écrit séquentiellement et donc le coût de synchronisation du journal est beaucoup moins élevé que le coût d'écriture de fragments avec des données.


Prometheus enregistre des points d'arrêt périodiques, qui par défaut sont ajoutés toutes les 2 heures en compressant les journaux de la période écoulée et en les enregistrant sur le disque.


Tous les points d'arrêt sont stockés dans le même répertoire que checkpoint.ddd, où ddd est un nombre croissant monotone. Par conséquent, lors de la récupération après une défaillance, il peut restaurer les points d'arrêt à partir du catalogue de points d'arrêt avec une indication de la commande (.ddd).
En écrivant les journaux WAL, vous pouvez revenir à n'importe quel point de contrôle pour lequel le journal de données est disponible.


Que s'est-il passé en pratique?


Lors de l'ajout au projet (.Net Framework), nous avons utilisé le package Prometheus.Client.3.0.2 pour collecter les métriques. Pour collecter les métriques, les méthodes et classes nécessaires ont été ajoutées au projet pour stocker les métriques jusqu'à leur réception par Prometheus.


Une interface IMetricsService a été initialement définie qui contenait des méthodes de temporisation pour mesurer la durée de fonctionnement des méthodes:


 public interface IMetricsService { Stopwatch StartTimer(); void StopTimer(Stopwatch timer, string controllerName, string actionName, string methodName = "POST"); } 

Nous ajoutons la classe MetricsService, qui implémente l'interface IMetricsService et stocke temporairement les métriques.


 public class MetricsService : IMetricsService { private static Histogram _histogram; static MetricsService() { _histogram = CreateHistogram(); } public Stopwatch StartTimer() { try { var timer = new Stopwatch(); timer.Start(); return timer; } catch (Exception exception) { Logger.Error(exception); } return null; } public void StopTimer(Stopwatch timer, string controllerName, string actionName, string methodName = "POST") { try { if (timer == null) { throw new ArgumentException($"{nameof(timer)} can't be null."); } timer.Stop(); _histogram .WithLabels(controllerName, actionName, methodName) .Observe(timer.ElapsedMilliseconds, DateTimeOffset.UtcNow); } catch (Exception exception) { Logger.Error(exception); } } public static List<string> GetAllLabels() { var metricsList = new List<string>(); try { foreach (var keyValuePair in _histogram.Labelled) { var controllerName = keyValuePair.Key.Labels[0].Value; var actionName = keyValuePair.Key.Labels[1].Value; var methodName = keyValuePair.Key.Labels[2].Value; var requestDurationSum = keyValuePair.Value.Value.Sum; var requestCount = keyValuePair.Value.Value.Count; metricsList.Add($"http_request_duration_widget_sum{{controller={controllerName},action={actionName},method={methodName}}} {requestDurationSum}"); metricsList.Add($"http_request_duration_widget_count{{controller={controllerName},action={actionName},method={methodName}}} {requestCount}"); } _histogram = CreateHistogram(); } catch (Exception exception) { Logger.Error(exception); } return metricsList; } private static Histogram CreateHistogram() { var newMetrics = Metrics .WithCustomRegistry(new CollectorRegistry()) .CreateHistogram(name: "http_request_duration_web_api", help: "Histogram metrics of Web.Api", includeTimestamp: true, labelNames: new[] { "controller", "action", "method" }); var oldValue = _histogram; for (var i = 0; i < 10; i++) { var oldValue = Interlocked.Exchange<Histogram>(ref oldValue, newMetrics); if (oldValue != null) { return oldValue; } } return null; } } 

Nous pouvons maintenant utiliser notre classe pour enregistrer les métriques que nous prévoyons de collecter dans les méthodes Application_BeginRequest, Application_Error, Application_EndRequest. Dans la classe Global.cs, nous ajoutons une collection de métriques aux méthodes ci-dessus.


 private IMetricsService _metricsService; protected virtual void Application_BeginRequest(object sender, EventArgs e) { var context = new HttpContextWrapper(HttpContext.Current); var metricServiceTimer = _metricsService.StartTimer(); context.Items.Add("metricsService", _metricsService); context.Items.Add("metricServiceTimer", metricServiceTimer); } protected virtual void Application_EndRequest(object sender, EventArgs e) { WriteMetrics(new HttpContextWrapper(HttpContext.Current)); } protected void Application_Error(object sender, EventArgs e) { WriteMetrics(new HttpContextWrapper(HttpContext.Current)); } private void WriteMetrics(HttpContextBase context) { try { _metricsService = context.Items["metricsService"] as IMetricsService; if (_metricsService != null) { var timer = context.Items["metricServiceTimer"] as Stopwatch; string controllerName = null; string actionName = null; var rd = RouteTable.Routes.GetRouteData(context); if (rd != null) { controllerName = rd.GetRequiredString("controller"); actionName = rd.GetRequiredString("action"); } _metricsService.StopTimer(timer, controllerName, actionName, context.Request.HttpMethod); } } catch (Exception exception) { Logger.Error("Can't write metrics.", exception); } } 

Ajoutez un nouveau contrôleur, qui sera un point de référence pour l'envoi des métriques de notre API à Prometheus:


 public class MetricsController : Controller { [HttpGet] public string[] GetAllMetrics() { try { var metrics = MetricsService.GetAllLabels(); return metrics.ToArray(); } catch (Exception exception) { Logger.Error(exception); } return new string[] { }; } } 

La dernière étape consistera à configurer la configuration Prometheus pour collecter les métriques dans la section scrape_configs, après quoi nous pouvons voir les métriques collectées déjà dans l'interface utilisateur Prometheus ou Grafana.


Principales fonctionnalités qui nous intéressaient chez Prometheus:


Modèle de données multidimensionnel: métriques et étiquettes.
Langage de requête PromQL flexible. Dans le même opérateur de requête, nous pouvons utiliser des opérations telles que la multiplication, l'addition, la concaténation, etc.; peut être effectuée avec plusieurs métriques.
Recueille des données HTTP à l'aide de la méthode Pull.
Compatible avec la méthode push via Pushgateway.
Il est possible de collecter des métriques à partir d'autres applications via les exportateurs.
Fournit un mécanisme pour éviter la perte de données.
Prend en charge diverses représentations graphiques des données.

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


All Articles