Cómo conectamos Prometeo

De alguna manera tuve que lidiar con las métricas para nuestra API, como siempre (¿no hay tiempo?) Para agregar más tarde, es muy difícil y aún no se ha implementado, lo que significa que es hora de implementarlo. Después de algunas andanzas en la red, me pareció que el sistema de monitoreo más popular era Prometeo.


Usando Prometheus, podemos monitorear varios recursos de la computadora, tales como: memoria, procesador, disco, carga de red. También puede ser importante para nosotros calcular el número de llamadas a los métodos de nuestra API o medir el tiempo de ejecución, porque cuanto mayor es la carga en el sistema, más costoso es el tiempo de inactividad. Y aquí Prometeo viene en nuestra ayuda. Me parece que este artículo proporciona los puntos principales para comprender el trabajo de Prometheus y para agregar una colección de métricas a la API. Por lo tanto, comenzamos con lo más banal, con una pequeña descripción.


Prometheus es un sistema de código abierto y DBMS de series temporales escrito en Go y desarrollado por SoundCloud. Tiene documentación oficial y soporte para idiomas como: Go, Java o Scala, Python, Ruby. Hay soporte no oficial para otros idiomas, como: C #, C ++, C, Bash, Lua para Nginx, Lua para Tarantool y otros, toda la lista está en el sitio web oficial de Prometheus.


Todos los servicios de Prometheus están disponibles como imágenes de Docker en Docker Hub o Quay.io.


Prometheus se inicia mediante el docker run -p 9090:9090 prom/prometheus , que lo inicia con la configuración predeterminada y establece el puerto localhost:9090 Después de eso, la interfaz de usuario de Prometheus estará disponible en localhost:9090 .


Prometheus es un sistema de monitoreo que incluye varias herramientas para configurar el monitoreo de aplicaciones (puntos finales) utilizando el protocolo HTTP. Cuando se conecta a Prometheus, la API HTTP no admite "autenticación básica". Si desea usar la autenticación básica para conectarse a Prometheus, se recomienda que use Prometheus junto con un servidor proxy inverso y use la autenticación a nivel de proxy. Puede usar cualquier proxy inverso con Prometheus.


Los principales componentes de Prometeo:


  • un servidor que recopila métricas, las guarda en la base de datos y las limpia;
  • paquetes para recopilar métricas en la API;
  • Pushgateway: componente para recibir métricas de aplicaciones para las que no se puede utilizar una solicitud Pull;
  • Exportadores: herramientas para exportar métricas de aplicaciones y servicios de terceros, instalados en máquinas de destino;
  • AlertManager: administrador de notificaciones (alertas), las alertas se definen en el archivo de configuración y se establecen mediante un conjunto de reglas para las métricas.
    Si durante la operación se cumple con la regla, se activa una alerta y se envía a los destinatarios especificados por correo electrónico, Slack, etc.

Los objetos con los que Prometheus trabaja se llaman métricas recibidas de los objetivos, ya sea a través de Pushgateway o de Exportadores.


Al recopilar métricas, se utilizan varios métodos para transmitirlas:


  • Prometheus solicita métricas del objetivo a través de una solicitud de extracción, cuya configuración se especifica en el archivo de configuración en la sección scrape_config para cada trabajo.
    Cuando el sistema recopila datos, puede controlar la frecuencia de recopilación y crear varias configuraciones de recopilación de datos para seleccionar una frecuencia diferente para diferentes objetos;
  • Los exportadores le permiten recopilar métricas de varios objetos, por ejemplo: bases de datos (MongoDB, SQL, etc.), corredores de mensajes (RabbitMQ, EMQ, NSQ, etc.), equilibradores de carga HTTP, etc.
  • Pushgateway. Se puede usar si es necesario, cuando la aplicación no puede proporcionar la capacidad de proporcionar métricas directamente a Prometheus; o cuando se usan trabajos por lotes que no tienen la capacidad de usar la solicitud de extracción de Prometheus.

Por lo tanto, Prometheus almacenará todas las métricas recibidas en una base de datos con marcas de tiempo.


Configuracion


Prometheus se configura utilizando los indicadores de línea de comando y los archivos de configuración provistos en el formato YAML. Los indicadores de línea de comando le permiten configurar parámetros inmutables, como: rutas, volúmenes de datos almacenados en el disco y en la memoria, etc. El archivo de configuración le permite configurar todo lo relacionado con trabajos y configurar archivos cargados de reglas yaml. Todo está escrito en el archivo de configuración global, le permite establecer configuraciones generales para todos y resaltar configuraciones para diferentes secciones de configuración por separado. La configuración que las encuestas de Prometheus se configuran en el archivo de configuración en la sección scrape_configs.


Prometheus puede volver a cargar los archivos de configuración durante la operación, si la nueva configuración no es válida, no se aplicará. El reinicio del archivo de configuración se activa enviando el comando SIGHUP Prometheus o enviando la solicitud HTTP POST a /-/reload , siempre que se --web.enable-lifecycle el --web.enable-lifecycle . También volverá a cargar todos los archivos de reglas configurados.


¿Qué tipos de datos se utilizan?


Prometheus almacena un modelo de datos multidimensional personalizado y utiliza un lenguaje de consulta para datos multidimensionales llamado PromQL. Prometheus almacena datos en forma de series de tiempo; admite varias opciones de almacenamiento:


  • almacenamiento en disco local: cada 2 horas, los datos almacenados en la memoria se comprimen y almacenan en el disco. Por defecto, el directorio ./data se usa en el directorio de trabajo para guardar archivos comprimidos;
  • Repositorio remoto: Prometheus admite la integración con repositorios de terceros (por ejemplo: Kafka, PostgreSQL, Amazon S3, etc.) a través del adaptador Protocol Buffer.

La serie de tiempo almacenada está determinada por la métrica y los metadatos en forma de pares clave-valor, aunque, si es necesario, el nombre de la métrica no se puede usar y la métrica misma consistirá solo en metadatos. Una serie temporal se puede definir formalmente como <nombre de métrica> {<metadata>}. La clave es <nombre de la métrica> {<metadata>}: lo que estamos midiendo, y el valor es el valor real como un número con el tipo float64 (Prometheus solo admite este tipo). La descripción de la clave contiene metadatos (etiquetas), también descritos por pares clave-valor: <nombre de etiqueta> = "<valor de etiqueta>", <nombre de etiqueta> = "<valor de etiqueta>", ...


Al almacenar métricas, se utilizan los siguientes tipos de datos:


  • Contador : cuenta la cantidad durante un período de tiempo. Este tipo de métricas solo puede aumentar (no puede usar valores negativos) o restablecer el valor.
    Puede ser adecuado, por ejemplo, para contar la cantidad de solicitudes por minuto o la cantidad de errores por día, la cantidad de paquetes de red enviados / recibidos, etc.
  • Indicador : almacena valores que pueden disminuir o aumentar con el tiempo.
    Gauge no muestra el desarrollo de métricas durante un período de tiempo. Con Gauge, puede perder cambios métricos irregulares con el tiempo.
  • Histograma : guarda varias series de tiempo: la suma total de todos los valores observados; el número de eventos que se observaron;
    contadores acumulativos (cubos): se indican en la etiqueta como le="<upper inclusive bound>" .
    Los valores se recopilan en áreas con límites superiores personalizados (depósitos).
  • Resumen : guarda varias series de tiempo: la suma total de todos los valores observados; el número de eventos que se observaron;
    flujo φ-cuantiles (0 ≤ φ ≤ 1) de eventos observados - se indican en la etiqueta como quantile="<φ>" .

¿Cómo se guardan los datos?


Prometheus recomienda "dar" 2/3 de RAM a una aplicación en ejecución.
Para almacenar datos en la memoria, Prometheus usa archivos llamados fragmentos; cada métrica tiene su propio archivo. Todos los archivos de fragmentos son inmutables, excepto el último en el que se escriben los datos. Los nuevos datos se guardan en fragmentos y cada 2 horas la secuencia de fondo combina los datos y los escribe en el disco. Cada bloque de dos horas consta de un directorio que contiene uno o más archivos de fragmentos que contienen todas las muestras de series de tiempo para ese período de tiempo, así como un archivo de metadatos y un archivo de índice (que indexa los nombres de las métricas y etiquetas para series de tiempo en los archivos de fragmentos). Si dentro de una hora, Prometheus no escribe datos en el fragmento, entonces se guardará en el disco y se creará un nuevo fragmento para escribir datos. El período máximo de retención de datos en Prometheus es de ~ 21 días.


Porque el tamaño de la memoria es fijo, el rendimiento de escritura y lectura del sistema estará limitado por esta cantidad de memoria. La cantidad de memoria PTSDB está determinada por el período de tiempo mínimo, el período de recopilación y el número de métricas de tiempo.


Prometheus también tiene un mecanismo WAL para evitar la pérdida de datos.


El registro de escritura anticipada (WAL) serializa las operaciones memorizadas en un medio permanente en forma de archivos de registro. En caso de falla, los archivos WAL se pueden usar para restaurar la base de datos a su estado consistente mediante la restauración desde los registros.


Los archivos de registro se almacenan en un directorio wal en segmentos de 128 MB. Estos archivos contienen datos sin procesar que aún no se han comprimido, por lo que son significativamente más grandes que los archivos de fragmentos normales.


Prometheus almacenará al menos 3 archivos de registro, pero los servidores con mucho tráfico pueden ver más de tres archivos WAL, ya que necesita almacenar al menos dos horas de datos sin procesar.


El resultado del uso de WAL es una reducción significativa en el número de solicitudes de escritura en el disco, ya que solo un archivo de registro debe escribirse en el disco, y no todos los datos que se han modificado como resultado de la operación. El archivo de registro se escribe secuencialmente y, por lo tanto, el costo de sincronizar el registro es mucho menor que el costo de escribir fragmentos con datos.


Prometheus guarda puntos de interrupción periódicos, que por defecto se agregan cada 2 horas al comprimir los registros del período anterior y guardarlos en el disco.


Todos los puntos de interrupción se almacenan en el mismo directorio que checkpoint.ddd, donde ddd es un número monotónicamente creciente. Por lo tanto, cuando se recupera de una falla, puede restaurar los puntos de interrupción del catálogo de puntos de interrupción con una indicación del pedido (.ddd).
Al escribir registros de WAL, puede volver a cualquier punto de control para el que esté disponible el registro de datos.


¿Qué pasó en la práctica?


Al agregar al proyecto (.Net Framework), utilizamos el paquete Prometheus.Client.3.0.2 para recopilar métricas. Para recopilar métricas, los métodos y clases necesarios se han agregado al proyecto para almacenar métricas hasta que Prometheus las reciba.


Originalmente se definió una interfaz IMetricsService que contenía métodos de temporizador para medir cuánto tiempo funcionaron los métodos:


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

Agregamos la clase MetricsService, que implementa la interfaz IMetricsService y almacena temporalmente las métricas.


 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; } } 

Ahora podemos usar nuestra clase para almacenar las métricas que planeamos recopilar en los métodos Application_BeginRequest, Application_Error, Application_EndRequest. En la clase Global.cs, agregamos una colección de métricas a los métodos anteriores.


 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); } } 

Agregue un nuevo controlador, que será un punto de referencia para enviar las métricas de nuestra API a 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[] { }; } } 

El último paso será configurar la configuración de Prometheus para recopilar métricas en la sección scrape_configs, después de lo cual podemos ver las métricas recopiladas ya en la interfaz de usuario de Prometheus o Grafana.


Características clave que nos interesaron en Prometheus:


Modelo de datos multidimensionales: métricas y etiquetas.
Lenguaje flexible de consulta PromQL. En el mismo operador de consulta, podemos usar operaciones como multiplicación, suma, concatenación, etc. Se puede realizar con múltiples métricas.
Recopila datos basados ​​en HTTP utilizando el método de extracción.
Compatible con el método push a través de Pushgateway.
Es posible recopilar métricas de otras aplicaciones a través de exportadores.
Proporciona un mecanismo para evitar la pérdida de datos.
Admite varias representaciones gráficas de datos.

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


All Articles