Capítulo 4. Exposición

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

En el Capítulo 3 nos centramos principalmente en añadir instrumentación a tu código. Pero toda la instrumentación del mundo no sirve de mucho si las métricas producidas no acaban en tu sistema de monitoreo. El proceso de poner métricas a disposición de Prometheus se conoce como exposición.

La exposición a Prometheus se realiza a través de HTTP. Normalmente expones las métricas bajo la ruta /metrics, y la petición la gestiona por ti una biblioteca cliente. Prometheus admite dos formatos de texto legibles por humanos: el formato de texto de Prometheus y OpenMetrics. Tienes la opción de elaborar el formato de exposición a mano, en cuyo caso será más fácil con el formato de texto de Prometheus, que es menos estricto. Puedes optar por hacerlo así si no existe una biblioteca adecuada para tu idioma, pero es recomendable que utilices una biblioteca, ya que se encargará de que todos los pequeños detalles, como el escape, sean correctos. La mayoría de las bibliotecas también ofrecen la posibilidad de producir métricas utilizando tanto el formato de texto de OpenMetrics como el de Prometheus.

La exposición suele hacerse en tu función principal o en otra función de nivel superior y sólo es necesario configurarla una vez por aplicación.

Las métricas suelen registrarse en el registro por defecto cuando las defines. Si una de las bibliotecas de las que dependes tiene instrumentación Prometheus, las métricas estarán en el registro por defecto y obtendrás el beneficio de esa instrumentación adicional sin tener que hacer nada. Algunos usuarios prefieren pasar explícitamente un registro desde la función principal, por lo que tendrías que confiar en que todas las bibliotecas entre la función principal de tu aplicación y la instrumentación de Prometheus conozcan la instrumentación. Esto supone que todas las bibliotecas de la cadena de dependencia se preocupan por la instrumentación y están de acuerdo en la elección de lasbibliotecas de instrumentación.

Este diseño permite instrumentar las métricas de Prometheus sin exposición alguna.1 En ese caso, aparte de seguir pagando el (ínfimo) coste de recursos de la instrumentación, no hay ningún impacto en tu aplicación. Si eres tú quien escribe una biblioteca, puedes añadir la instrumentación para tus usuarios que utilicen Prometheus sin que ello suponga un esfuerzo adicional para tus usuarios que no monitorizan. Para soportar mejor este caso de uso, las partes de instrumentación de las bibliotecas cliente intentan minimizar sus dependencias.

Echemos un vistazo a la exposición de algunas de las bibliotecas cliente más populares. Vamos a suponer aquí que sabes cómo instalar las bibliotecas cliente y cualquier otra dependencia necesaria.

Python

Ya has visto start_http_server en el Capítulo 3. Inicia un subproceso en segundo plano con un servidor HTTP que sólo sirve métricas de Prometheus, como se indica a continuación:

from prometheus_client import start_http_server

if __name__ == '__main__':
    start_http_server(8000)
    // Your code goes here.

start_http_server es muy cómodo para ponerlo en marcha rápidamente. Pero es probable que ya tengas un servidor HTTP en tu aplicación desde el que te gustaría que se sirvieran tus métricas.

En Python hay varias formas de hacerlo, según el framework que utilices.

WSGI

Web Server Gateway Interface (WSGI) es un estándar de Python para aplicaciones web. El cliente Python proporciona una aplicación WSGI que puedes utilizar con tu código WSGI existente. En el Ejemplo 4-1, metrics_app es delegada por my_app si se solicita la ruta /metrics; en caso contrario, realiza su lógica habitual. Al encadenar aplicaciones WSGI, puedes añadir middleware como la autenticación, que las bibliotecas cliente no ofrecen de fábrica.

Ejemplo 4-1. Exposición utilizando WSGI en Python
from prometheus_client import make_wsgi_app
from wsgiref.simple_server import make_server

metrics_app = make_wsgi_app()

def my_app(environ, start_fn):
    if environ['PATH_INFO'] == '/metrics':
        return metrics_app(environ, start_fn)
    start_fn('200 OK', [])
    return [b'Hello World']

if __name__ == '__main__':
    httpd = make_server('', 8000, my_app)
    httpd.serve_forever()

Retorcido

Twisted es un motor de red basado en eventos de Python. Es compatible con WSGI, por lo que puedes conectar make_wsgi_app, como se muestra en el Ejemplo 4-2.

Ejemplo 4-2. Exposición utilizando Twisted en Python
from prometheus_client import make_wsgi_app
from twisted.web.server import Site
from twisted.web.wsgi import WSGIResource
from twisted.web.resource import Resource
from twisted.internet import reactor

metrics_resource = WSGIResource(
        reactor, reactor.getThreadPool(), make_wsgi_app())

class HelloWorld(Resource):
      isLeaf = False
      def render_GET(self, request):
          return b"Hello World"

root = HelloWorld()
root.putChild(b'metrics', metrics_resource)

reactor.listenTCP(8000, Site(root))
reactor.run()

Multiproceso con Gunicorn

Prometheus asume que las aplicaciones que está monitorizando son de larga duración y multihilo. Pero esto puede fallar un poco con tiempos de ejecución como CPython.2 CPython está efectivamente limitado a un núcleo de procesador debido al Bloqueo Global del Intérprete (GIL). Para evitarlo, algunos usuarios reparten la carga de trabajo entre varios procesos utilizando una herramienta como Gunicorn.

Si utilizaras la biblioteca cliente Python de la forma habitual, cada trabajador rastrearía sus propias métricas. Cada vez que Prometheus fuera a raspar la aplicación, obtendría aleatoriamente las métricas de sólo uno de los trabajadores, lo que sería sólo una fracción de la información y también tendría problemas como que los contadores parecieran ir hacia atrás. Los trabajadores también pueden durar relativamente poco.

La solución a este problema que ofrece el cliente Python es hacer que cada trabajador realice un seguimiento de sus propias métricas. En el momento de la exposición, todas las métricas de todos los trabajadores se combinan de forma que proporcionan la semántica que obtendrías de una aplicación multihilo. El enfoque utilizado tiene algunas limitaciones: las métricas deprocess_ y los colectores personalizados no se expondrán, y no se puede utilizar el Pushgateway.3

Utilizando Gunicorn, tienes que hacer saber a la biblioteca cliente cuándo sale un proceso trabajador.4 Esto se hace en un archivo de configuración como el del Ejemplo 4-3.

Ejemplo 4-3. Gunicorn config.py para gestionar la salida de los procesos de trabajo
from prometheus_client import multiprocess

def child_exit(server, worker):
    multiprocess.mark_process_dead(worker.pid)

También necesitarás una aplicación para servir las métricas. Gunicorn utiliza WSGI, por lo que puedes utilizarmake_wsgi_app. Debes crear un registro personalizado que contenga sólo unMultiProcessCollector para la exposición, de modo que no incluya tanto las métricas multiproceso como las métricas del registro local por defecto(Ejemplo 4-4).

Ejemplo 4-4. Aplicación Gunicorn en app.py
from prometheus_client import multiprocess, make_wsgi_app, CollectorRegistry
from prometheus_client import Counter, Gauge

REQUESTS = Counter("http_requests_total", "HTTP requests")
IN_PROGRESS = Gauge("http_requests_inprogress", "Inprogress HTTP requests",
        multiprocess_mode='livesum')

@IN_PROGRESS.track_inprogress()
def app(environ, start_fn):
    REQUESTS.inc()
    if environ['PATH_INFO'] == '/metrics':
        registry = CollectorRegistry()
        multiprocess.MultiProcessCollector(registry)
        metrics_app = make_wsgi_app(registry)
        return metrics_app(environ, start_fn)
    start_fn('200 OK', [])
    return [b'Hello World']

Como puedes ver en el Ejemplo 4-4, los contadores funcionan normalmente, al igual que los resúmenes y los histogramas. Para los indicadores hay una configuración opcional adicional utilizando multiprocess_mode. Puedes configurar el indicador en función de cómo pretendas utilizarlo, como se indica a continuación:

all

El predeterminado, que devuelve una serie temporal de cada proceso, esté vivo o muerto. Esto te permite agregar las series como desees en PromQL. Se distinguirán por una etiqueta pid.

liveall

Devuelve una serie temporal de cada proceso vivo.

livesum

Devuelve una única serie temporal que es la suma del valor de cada proceso vivo. Lo utilizarías para cosas como las solicitudes en curso o el uso de recursos en todos los procesos. Un proceso puede haber abortado con un valor distinto de cero, por lo que se excluyen los procesos muertos.

max

Devuelve una única serie temporal que es el máximo del valor de cada proceso vivo o muerto. Esto es útil si quieres rastrear la última vez que ocurrió algo, como que se procesara una petición, que podría haber estado en un proceso que ahora está muerto.

min

Devuelve una única serie temporal que es el mínimo del valor de cada proceso vivo o muerto.

Hay una pequeña configuración antes de que puedas ejecutar Gunicorn, como se muestra enel Ejemplo 4-5. Debes establecer una variable de entorno llamadaprometheus_multiproc_dir. Ésta apunta a un directorio vacío que la biblioteca cliente utiliza para el seguimiento de las métricas. Antes de iniciar la aplicación, siempre debes limpiar este directorio para gestionar cualquier cambio potencial en tu instrumentación.

Ejemplo 4-5. Preparar el entorno antes de iniciar Gunicorn con dos trabajadores
hostname $ export prometheus_multiproc_dir=$PWD/multiproc
hostname $ rm -rf $prometheus_multiproc_dir
hostname $ mkdir -p $prometheus_multiproc_dir
hostname $ gunicorn -w 2 -c config.py app:app
[2018-01-07 19:05:30 +0000] [9634] [INFO] Starting gunicorn 19.7.1
[2018-01-07 19:05:30 +0000] [9634] [INFO] Listening at: http://127.0.0.1:8000 (9634)
[2018-01-07 19:05:30 +0000] [9634] [INFO] Using worker: sync
[2018-01-07 19:05:30 +0000] [9639] [INFO] Booting worker with pid: 9639
[2018-01-07 19:05:30 +0000] [9640] [INFO] Booting worker with pid: 9640

Cuando mires la ruta /metrics, verás las dos métricas definidas, peropython_info y la métrica process_ no estarán allí.

Consejo

Cada proceso crea varios archivos que deben leerse en el momento de la exposición enprometheus_multiproc_dir. Si tus trabajadores paran y arrancan mucho, esto puede hacer que la exposición sea lenta cuando tengas miles de archivos.

No es seguro borrar archivos individuales, ya que eso podría hacer que los contadores retrocedieran incorrectamente, pero puedes intentar reducir el churn (por ejemplo, aumentando o eliminando un límite en el número de peticiones que los trabajadores gestionan antes de salir5), o reiniciando regularmente la aplicación y borrando los archivos.

Estos pasos son para Gunicorn. El mismo enfoque también funciona con otras configuraciones multiproceso de Python, como el uso del módulo multiprocessing.

Ve a

En Go, http.Handler es la interfaz estándar para proporcionar manejadores HTTP, ypromhttp.Handler proporciona esa interfaz para la biblioteca cliente Go. Para demostrar cómo funciona, coloca el código del Ejemplo 4-6 en un archivo llamado ejemplo.go.

Ejemplo 4-6. Un programa Go sencillo que demuestra la instrumentación y la exposición
package main

import (
  "log"
  "net/http"

  "github.com/prometheus/client_golang/prometheus"
  "github.com/prometheus/client_golang/prometheus/promauto"
  "github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
  requests = promauto.NewCounter(
    prometheus.CounterOpts{
      Name: "hello_worlds_total",
      Help: "Hello Worlds requested.",
    })
)

func handler(w http.ResponseWriter, r *http.Request) {
  requests.Inc()
  w.Write([]byte("Hello World"))
}

func main() {
  http.HandleFunc("/", handler)
  http.Handle("/metrics", promhttp.Handler())
  log.Fatal(http.ListenAndServe(":8000", nil))
}

Puedes obtener dependencias y ejecutar este código de la forma habitual:

hostname $ go get -d -u github.com/prometheus/client_golang/prometheus
hostname $ go run example.go

Este ejemplo utiliza promauto, que registrará automáticamente tu métrica en el registro por defecto. Si no deseas hacerlo, puedes utilizarprometheus.NewCounter en su lugar y luego utilizar MustRegister en una función init:

func init() {
  prometheus.MustRegister(requests)
}

Esto es un poco más frágil, ya que es fácil que crees y utilices la métrica pero olvides la llamada a MustRegister.

Java

La biblioteca cliente Java también se conoce como simpleclient. Sustituyó al cliente original, que se desarrolló antes de que se establecieran muchas de las prácticas y directrices actuales sobre cómo escribir una biblioteca cliente. El cliente Java debe utilizarse para cualquier instrumentación de lenguajes que se ejecuten en una máquina virtual Java (JVM).

Servidor HTTPS

De forma similar a start_http_server en Python, la clase HTTPServer en el cliente Java te ofrece una forma sencilla de ponerte en marcha(Ejemplo 4-7).

Ejemplo 4-7. Un programa Java sencillo que demuestra la instrumentación y la exposición
import io.prometheus.client.Counter;
import io.prometheus.client.hotspot.DefaultExports;
import io.prometheus.client.exporter.HTTPServer;

public class Example {
  private static final Counter myCounter = Counter.build()
      .name("my_counter_total")
      .help("An example counter.").register();

  public static void main(String[] args) throws Exception {
    DefaultExports.initialize();
    HTTPServer server = new HTTPServer(8000);
    while (true) {
      myCounter.inc();
      Thread.sleep(1000);
    }
  }
}

Por lo general, debes tener las métricas Java como campos estáticos de clase, para que sólo se registren una vez.

La llamada a DefaultExports.initialize es necesaria para que funcionen las distintas métricas process yjvm. Por lo general, debes llamarlo una vez en todas tus aplicaciones Java, por ejemplo en la función principal. Sin embargo, DefaultExports.initializees idempotente y seguro para los hilos, por lo que las llamadas adicionales son inofensivas.

Para ejecutar el código del Ejemplo 4-7, necesitarás las dependencias de simpleclient. Si utilizas Maven, elEjemplo 4-8 es el aspecto que debe tener dependencies en tu pom.xml.

Ejemplo 4-8. Dependencias depom.xml para el Ejemplo 4-7
  <dependencies>
    <dependency>
      <groupId>io.prometheus</groupId>
      <artifactId>simpleclient</artifactId>
      <version>0.16.0</version>
    </dependency>
    <dependency>
      <groupId>io.prometheus</groupId>
      <artifactId>simpleclient_hotspot</artifactId>
      <version>0.16.0</version>
    </dependency>
    <dependency>
      <groupId>io.prometheus</groupId>
      <artifactId>simpleclient_httpserver</artifactId>
      <version>0.16.0</version>
    </dependency>
  </dependencies>

Servlet

Muchos marcos Java y JVM admiten el uso de subclases de HttpServlet en sus servidores HTTP y middleware. Jetty es uno de esos servidores, y puedes ver cómo utilizar el MetricsServlet del cliente Java en el Ejemplo 4-9.

Ejemplo 4-9. Un programa Java que demuestra la exposición utilizando MetricsServlet y Jetty
import io.prometheus.client.Counter;
import io.prometheus.client.exporter.MetricsServlet;
import io.prometheus.client.hotspot.DefaultExports;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import java.io.IOException;


public class Example {
  static class ExampleServlet extends HttpServlet {
    private static final Counter requests = Counter.build()
        .name("hello_worlds_total")
        .help("Hello Worlds requested.").register();

    @Override
    protected void doGet(final HttpServletRequest req,
        final HttpServletResponse resp)
        throws ServletException, IOException {
      requests.inc();
      resp.getWriter().println("Hello World");
    }
  }

  public static void main(String[] args) throws Exception {
      DefaultExports.initialize();

      Server server = new Server(8000);
      ServletContextHandler context = new ServletContextHandler();
      context.setContextPath("/");
      server.setHandler(context);
      context.addServlet(new ServletHolder(new ExampleServlet()), "/");
      context.addServlet(new ServletHolder(new MetricsServlet()), "/metrics");

      server.start();
      server.join();
  }
}

También tendrás que especificar el cliente Java como dependencia. Si utilizas Maven, será como en el Ejemplo 4-10.

Ejemplo 4-10. Dependenciaspom.xml del Ejemplo 4-9
  <dependencies>
    <dependency>
      <groupId>io.prometheus</groupId>
      <artifactId>simpleclient</artifactId>
      <version>0.16.0</version>
    </dependency>
    <dependency>
      <groupId>io.prometheus</groupId>
      <artifactId>simpleclient_hotspot</artifactId>
      <version>0.16.0</version>
    </dependency>
    <dependency>
      <groupId>io.prometheus</groupId>
      <artifactId>simpleclient_servlet</artifactId>
      <version>0.16.0</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-servlet</artifactId>
      <version>11.0.11</version>
    </dependency>
  </dependencies>

Pushgateway

Los trabajos por lotes suelen ejecutarse según un programa regular, por ejemplo cada hora o cada día. Se inician, realizan algún trabajo y luego salen. Como no se ejecutan continuamente, Prometheus no puede rasparlos exactamente.6 Aquí es donde entra en juegoPushgateway.

El Pushgateway7 es una caché de métricas para trabajos por lotes a nivel de servicio. Su arquitectura se muestra en la Figura 4-1. Sólo recuerda el último envío que le hagas para cada trabajo por lotes. Lo utilizas haciendo que tus trabajos por lotes envíen sus métricas justo antes de salir. Prometheus extrae estas métricas de tu Pushgateway y tú puedes alertarlas y representarlas gráficamente. Normalmente, ejecutas un Pushgateway junto a un Prometheus.

Pushgateway architecture diagram
Figura 4-1. La arquitectura Pushgateway

Un trabajo por lotes a nivel de servicio es aquel en el que realmente no hay una etiqueta instance que aplicarle. Es decir, se aplica a la totalidad de uno de tus servicios, en lugar de estar ligado de forma innata a una máquina o instancia de proceso.8 Si no te importa especialmente dónde se ejecuta un trabajo por lotes, pero sí que se ejecute (aunque esté configurado para ejecutarse mediante cron en una máquina), se trata de un trabajo por lotes a nivel de servicio. Los ejemplos incluyen un trabajo por lotes por centro de datos para comprobar si hay máquinas defectuosas, o uno que realice la recogida de basura en todo un servicio.

Nota

El Pushgateway no es una forma de convertir Prometheus de pull a push. Si, por ejemplo, hay varios push entre un scrape de Prometheus y el siguiente, el Pushgateway sólo devolverá el último push de ese trabajo por lotes. Esto se trata con más detalle en "Redes y autenticación".

Puedes descargar Pushgateway desde la página de descargas de Prometheus. Es un exportador que se ejecuta por defecto en el puerto 9091, y Prometheus debe estar configurado para hacer scrape. Sin embargo, también debes proporcionar la configuración honor_labels: true en la configuración de scrape, como se muestra en el Ejemplo 4-11. Esto se debe a que las métricas que envíes a Pushgateway no deben tener una etiqueta instance, y no quieres que la propia etiqueta de destino instance de Pushgateway aparezca en las métricas cuando Prometheus las rastree.9 honor_labels se trata en"Choques de etiquetas y honor_labels".

Ejemplo 4-11. prometheus.yml scrape config para un Pushgateway local
scrape_configs:
 - job_name: pushgateway
   honor_labels: true
   static_configs:
    - targets:
      - localhost:9091

Puedes utilizar librerías cliente para hacer push a la Pushgateway. El Ejemplo 4-12 muestra la estructura que utilizarías para un trabajo por lotes en Python. Se crea un registro personalizado para que sólo se envíen las métricas específicas que elijas. La duración del trabajo por lotes siempre se empuja,10 y la hora a la que finalizó sólo se envía si el trabajo tiene éxito.

Hay tres formas diferentes de escribir en Pushgateway. En Python son las funciones push_to_gateway, pushadd_to_gateway y delete_from_gateway:

push

Se eliminan todas las métricas existentes para este trabajo y se añaden las métricas empujadas. Esto utiliza el método HTTP PUT encubierto.

pushadd

Las métricas empujadas anulan las métricas existentes con los mismos nombres de métrica para este trabajo.Cualquier métrica que existiera previamente con nombres de métrica diferentes permanece sin cambios. Esto utiliza el método HTTP POST bajo cubierta.

delete

Se eliminan las métricas de este trabajo. Esto utiliza el método HTTP DELETE encubierto.

Como en el Ejemplo 4-12 se utiliza pushadd_to_gateway, el valor de my_job_duration_seconds siempre se sustituirá. Sin embargo,my_job_last_success_seconds# sólo se reemplazará si no hay excepciones; se añade al registro y luego se empuja.

Ejemplo 4-12. Instrumentar un trabajo por lotes y enviar sus métricas a un Pushgateway
from prometheus_client import CollectorRegistry, Gauge, pushadd_to_gateway

registry = CollectorRegistry()
duration = Gauge('my_job_duration_seconds',
        'Duration of my batch job in seconds', registry=registry)
try:
    with duration.time():
        # Your code here. 
        pass

    # This only runs if there wasn't an exception. 
    g = Gauge('my_job_last_success_seconds',
            'Last time my batch job successfully finished', registry=registry)
    g.set_to_current_time()
finally:
    pushadd_to_gateway('localhost:9091', job='batch', registry=registry)

Puedes ver los datos empujados en la página de estado, como muestra la Figura 4-2. Pushgateway ha añadido una métrica adicional push_time_seconds porque Prometheus siempre utilizará la hora a la que realiza el raspado como marca de tiempo de las métricas de Pushgateway. push_time_seconds te ofrece una forma de saber la hora real a la que se empujaron los datos por última vez. Se ha introducido otra métrica, push_failure_time_seconds, que representa la última vez que falló una actualización de este grupo en el Pushgateway.

Pushgateway Status page.
Figura 4-2. La página de estado de Pushgateway mostrando las métricas de un push

Habrás observado en la Figura 4-2 que el empuje se denomina grupo. Puedes proporcionar etiquetas además de la etiqueta job al empujar, y todas estas etiquetas se conocen como clave de agrupación. En Python esto se puede proporcionar con el argumento de la palabra clave grouping_key. Utilizarías esto si un trabajo por lotes estuviera fragmentado o dividido de alguna manera. Por ejemplo, si tienes 30 fragmentos de base de datos y cada uno tiene su propio trabajo por lotes, podrías distinguirlos con una etiqueta shard.

Consejo

Una vez enviados, los grupos permanecen para siempre en Pushgateway. Debes evitar utilizar claves de agrupación que varíen de una ejecución de trabajo por lotes a la siguiente, ya que esto dificultará el trabajo con las métricas y causará problemas de rendimiento. Cuando retires un trabajo por lotes, no olvides borrar sus métricas de la Pushgateway.

Puentes

Las bibliotecas cliente de Prometheus no se limitan a dar salida a las métricas en el formato de Prometheus. Existe una separación de intereses entre la instrumentación y la exposición, de modo que puedes procesar las métricas del modo que desees.

Por ejemplo, los clientes Go, Python y Java incluyen cada uno un puente Graphite. Un puente toma la salida de métricas del registro de la biblioteca del cliente y la envía a algo distinto de Prometheus. Así, el puente Graphite convertirá las métricas en un formato que Graphite pueda entender11 y las escribirá en Graphite, como se muestra enel Ejemplo 4-13.

Ejemplo 4-13. Utilizar Python GraphiteBridge para enviar a Graphite cada 10 segundos
import time
from prometheus_client.bridge.graphite import GraphiteBridge

gb = GraphiteBridge(['graphite.your.org', 2003])
gb.start(10)
while True:
    time.sleep(1)

Esto funciona porque el registro tiene un método que te permite obtener una instantánea de todas las métricas actuales. Se trata de CollectorRegistry.collect en Python,CollectorRegistry.metricFamilySamples en Java y Registry.Gather en Go. Éste es el método que utiliza la exposición HTTP, y tú también puedes utilizarlo. Por ejemplo, podrías utilizar este método para introducir datos en otra biblioteca de instrumentación que no sea Prometheus.12

Consejo

Si alguna vez quieres engancharte a la instrumentación directa, deberías utilizar en su lugar la salida de métricas de un registro. Querer saber cada vez que se incrementa un contador no tiene sentido en términos de un sistema de monitoreo basado en métricas. Sin embargo, el recuento de incrementos ya te lo proporcionaCollectorRegistry.collect y funciona para colectores personalizados.

Parsers

Además del registro de una biblioteca cliente que te permite acceder a la salida de métricas, los clientes Go13 y Python también disponen de un analizador sintáctico para los formatos de exposición de Prometheus y OpenMetrics. El ejemplo 4-14 sólo imprime las muestras, pero podrías introducir las métricas de Prometheus en otros sistemas de monitoreo o en tus herramientas locales.

Ejemplo 4-14. Análisis del formato de texto de Prometheus con el cliente Python
from prometheus_client.parser import text_string_to_metric_families

for family in text_string_to_metric_families(u"counter_total 1.0\n"):
  for sample in family.samples:
    print("Name: {0} Labels: {1} Value: {2}".format(*sample))

DataDog, InfluxDB, Sensu y Metricbeat14 son algunos de los sistemas de monitoreo que tienen componentes que pueden analizar el formato de texto.Utilizando uno de estos sistemas de monitoreo, podrías aprovechar el ecosistema Prometheus sin tener que ejecutar nunca el servidor Prometheus. Creemos que esto es bueno, ya que actualmente hay mucha duplicación de esfuerzos entre los distintos sistemas de monitoreo. Cada uno de ellos tiene que escribir un código similar para dar soporte a la miríada de salidas métricas personalizadas que proporciona el software más utilizado.

Formato de exposición del texto

El formato de exposición de texto de Prometheus es relativamente fácil de producir y analizar.Aunque casi siempre deberías confiar en una biblioteca cliente para que lo gestione por ti, hay casos, como con el recopilador de archivos de texto Node Exporter (tratado en"Recopilador de archivos de texto"), en los que puede que tengas que producirlo tú mismo.

Te mostramos la versión 0.0.4 del formato de texto, que tiene el tipo de contenido cabecera:

Content-Type: text/plain; version=0.0.4; charset=utf-8

En los casos más sencillos, el formato de texto es sólo el nombre de la métrica seguido de un número en coma flotante de 64 bits. Cada línea se termina con un carácter de salto de línea (\n):

my_counter_total 14
a_small_gauge 8.3e-96

Tipos métricos

Una salida en formato de texto de Prometheus más completa sería incluiría las HELP y TYPE de las métricas, como se muestra en el Ejemplo 4-15. HELP es una descripción de lo que es la métrica, y generalmente no debería cambiar de un scrape a otro. TYPE es una decounter, gauge, summary, histogram, o untyped. untyped se utiliza cuando no se conoce el tipo de la métrica, y es el valor por defecto si no se especifica ningún tipo. No es válido que tengas una métrica duplicada, así que asegúrate de que todas las series temporales que pertenecen a una métrica están agrupadas.

Ejemplo 4-15. Formato de exposición de un indicador, contador, resumen e histograma
# HELP example_gauge An example gauge
# TYPE example_gauge gauge
example_gauge -0.7
# HELP my_counter_total An example counter
# TYPE my_counter_total counter
my_counter_total 14
# HELP my_summary An example summary
# TYPE my_summary summary
my_summary_sum 0.6
my_summary_count 19
# HELP latency_seconds An example histogram
# TYPE latency_seconds histogram
latency_seconds_bucket{le="0.1"} 7 1
latency_seconds_bucket{le="0.2"} 18
latency_seconds_bucket{le="0.4"} 24
latency_seconds_bucket{le="0.8"} 28
latency_seconds_bucket{le="+Inf"} 29
latency_seconds_sum 0.6
latency_seconds_count 29 2
1

Para los histogramas, las etiquetas le tienen valores en coma flotante y deben ordenarse. Debes tener en cuenta que los cubos del histograma son acumulativos, ya quele significa menor o igual que.

2

El _count debe coincidir con el cubo +Inf, y el cubo +Inf debe estar siempre presente. Los cubos no deben cambiar de un scrape a otro, ya que esto causaría problemas a la funciónhistogram_quantile de PromQL.

Etiquetas

El histograma del ejemplo anterior también muestra cómo se representan las etiquetas. Las etiquetas múltiples se separan mediante comas, y está bien que haya una coma final antes de la llave de cierre.

El orden de las etiquetas no importa, pero es una buena idea que el orden sea coherente de un scrape a otro. Esto facilitará la escritura de tus pruebas unitarias, y un ordenamiento coherente garantiza el mejor rendimiento de ingestión en Prometheus.

Aquí tienes un ejemplo de un resumen en formato de texto:

# HELP my_summary An example summary
# TYPE my_summary summary
my_summary_sum{foo="bar",baz="quu"} 1.8
my_summary_count{foo="bar",baz="quu"} 453
my_summary_sum{foo="blaa",baz=""} 0
my_summary_count{foo="blaa",baz="quu"} 0

Es posible tener una métrica sin series temporales, si no se ha inicializado ningún hijo, como se explica en "Hijo":

# HELP a_counter_total An example counter
# TYPE a_counter_total counter

Escapar de

El formato de exposición del texto está codificado en UTF-8, y el UTF-8 completo15 está permitido tanto en HELP como en los valores de las etiquetas. Por tanto, debes utilizar barras invertidas para escapar de los caracteres que causarían problemas utilizando barras invertidas. En HELP se utilizan saltos de línea y barras invertidas. Para los valores de etiqueta, son saltos de línea, barras invertidas y comillas dobles.16 El formato ignora los espacios en blanco adicionales.

Aquí tienes un ejemplo de escape en el formato de exposición de texto:

# HELP escaping A newline \\n and backslash \\ escaped
# TYPE escaping gauge
escaping{foo="newline \\n backslash \\ double quote \" "} 1

Marcas de tiempo

Es posible especificar una marca de tiempo en una serie temporal. Es un valor entero en milisegundos desde la época Unix,17 y va después del valor. Por lo general, deben evitarse las marcas de tiempo en el formato de exposición, ya que sólo son aplicables en determinados casos de uso limitados (como la federación) y tienen limitaciones. Las marcas de tiempo para los raspados suelen ser aplicadas automáticamente por Prometheus. No está definido qué ocurre si especificas varias líneas con el mismo nombre y etiquetas pero diferentes marcas de tiempo.

Este medidor tiene una marca de tiempo:

# HELP foo I'm trapped in a client library
# TYPE foo gauge
foo 1 15100992000000
Advertencia

Las marcas de tiempo se expresan en milisegundos desde la época en el formato de texto de Prometheus, mientras que en OpenMetrics se expresan en segundos desde la época.

comprobar métricas

Prometheus 2.0 utiliza un analizador sintáctico personalizado en aras de la eficacia. Por tanto, el hecho de que un punto final /metrics pueda ser escaneado no significa que las métricas cumplan el formato.

Promtool es una utilidad incluida con Prometheus que, entre otras cosas, puede verificar que tu salida métrica es válida y realizar comprobaciones de pelusa:

curl http://localhost:8000/metrics | promtool check metrics

Los errores más comunes incluyen olvidar el salto de línea en la última línea, utilizar retorno de carro y salto de línea en lugar de sólo salto de línea,18 y nombres de métrica o etiqueta no válidos. Como breve recordatorio, los nombres de métricas y etiquetas no pueden contener guiones y no pueden empezar por un número.

Ahora ya conoces el formato de texto. Puedes encontrar la especificación completa en la documentación oficial de Prometheus.

OpenMetrics

El formato OpenMetrics es similar al formato de exposición de texto de Prometheus, pero contiene varios cambios incompatibles con el formato de texto de Prometheus. Aunque parezcan similares, para un conjunto determinado de métricas, la salida que generen será generalmente diferente.

Te mostraremos la versión 1.0.0 del formato OpenMetrics, que tiene la cabecera de tipo de contenido:

Content-Type: application/openmetrics-text; version=1.0.0; charset=utf-8

En los casos más sencillos, el formato de texto es sólo el nombre de la métrica seguido de un número en coma flotante de 64 bits. Cada línea se termina con un carácter de salto de línea (\n). El archivo termina con # EOF:

my_counter_total 14
a_small_gauge 8.3e-96
# EOF

Tipos métricos

Los tipos de métrica admitidos por el formato de exposición de texto Prometheus también se admiten en OpenMetrics. Además de contadores, indicadores, resúmenes e histogramas, se han añadido tipos específicos: StateSet, GaugeHistograms e Info.

Los StateSets representan una serie de valores booleanos relacionados, también llamados conjunto de bits. Un valor de 1 significa true y 0 significa false.

Los GaugeHistogramas miden distribuciones de corriente. La diferencia con los histogramas es que los valores de los cubos y la suma pueden subir y bajar.

Las métricas de información se utilizan para exponer información textual que no cambia durante la vida del proceso. La versión de una aplicación, el commit del control de revisiones y la versión de un compilador son buenos candidatos. El valor de estas métricas es siempre 1.

Además de HELP y TYPE, las familias de métricas en OpenMetrics tienen un metadato opcional UNIT que especifica la unidad de una métrica.

En el Ejemplo 4-16 se muestran todos los tipos.

Ejemplo 4-16. Formato de exposición para distintos tipos de métricas
# HELP example_gauge An example gauge
# TYPE example_gauge gauge
example_gauge -0.7
# HELP my_counter An example counter
# TYPE my_counter counter
my_counter_total 14
my_counter_created 1.640991600123e+09
# HELP my_summary An example summary
# TYPE my_summary summary
my_summary_sum 0.6
my_summary_count 19
# HELP latency_seconds An example histogram
# TYPE latency_seconds histogram
# UNIT latency_seconds seconds
latency_seconds_bucket{le="0.1"} 7
latency_seconds_bucket{le="0.2"} 18
latency_seconds_bucket{le="0.4"} 24
latency_seconds_bucket{le="0.8"} 28
latency_seconds_bucket{le="+Inf"} 29
latency_seconds_sum 0.6
latency_seconds_count 29
# TYPE my_build_info info
my_build_info{branch="HEAD",version="0.16.0rc1"} 1.0
# TYPE my_stateset stateset
# HELP my_stateset An example stateset
my_stateset{feature="a"} 1
my_stateset{feature="b"} 0
# TYPE my_gaugehistogram gaugehistogram
# HELP my_gaugehistogram An example gaugehistogram
my_gaugehistogram_bucket{le="1.0"} 0
my_gaugehistogram_bucket{le="+Inf"} 3
my_gaugehistogram_gcount 3
my_gaugehistogram_gsum 2
# EOF

En OpenMetrics, como se muestra en el Ejemplo 4-16, los GaugeHistograms utilizan sufijos distintos _gcount y _gsum para los recuentos y las sumas, diferenciándolos de los _count y _sum de los Histograms.

Etiquetas

El Histograma y el GaugeHistogram del ejemplo anterior también mostraban cómo se representan las etiquetas.Las etiquetas múltiples se separan mediante comas, pero a diferencia del formato de cable de Prometheus, enOpenMetrics no se permiten las comas antes de la llave de cierre.

Marcas de tiempo

Es posible especificar una marca de tiempo en una serie temporal. Es un valor flotante en segundos desde la época Unix,19 y va después del valor, como se muestra en este ejemplo:

# HELP foo I'm trapped in a client library
# TYPE foo gauge
foo 1 1.5100992e9
Advertencia

Las marcas de tiempo se expresan en segundos desde la época en OpenMetrics, mientras que en el formato de texto de Prometheus se expresan en milisegundos desde la época.

Ahora ya conoces el formato OpenMetrics. Puedes encontrar la especificación completa en el repositorio GitHub de OpenMetrics.

Ya hemos mencionado las etiquetas unas cuantas veces. En el capítulo siguiente aprenderás en detalle qué son.

1 Sin exposición significa que las métricas no son raspadas por un servidor de Prometheus.

2 CPython es el nombre oficial de la implementación estándar de Python. No la confundas con Cython, que sirve para escribir extensiones de C en Python.

3 El Pushgateway no es adecuado para este caso de uso, por lo que no supone un problema en la práctica.

4 child_exit se añadió en la versión 19.7 de Gunicorn, publicada en marzo de 2017.

5 La bandera --max-requests de Gunicorn es un ejemplo de dicho límite.

6 Aunque para los trabajos por lotes que tardan más de unos minutos en ejecutarse, también puede tener sentido rasparlos normalmente a través de HTTP para ayudar a depurar los problemas de rendimiento.

7 Puede que lo veas referenciado como pgw en contextos informales.

8 Para los trabajos por lotes, como las copias de seguridad de bases de datos que están ligadas al ciclo de vida de una máquina, el recopilador de archivos de texto Node Exporter es una mejor opción. Esto se trata en "Recopilador de archivos de texto".

9 El Pushgateway exporta explícitamente etiquetas instance vacías para las métricas sin etiqueta instance. Combinado con honor_labels: true, esto hace que Prometheus no aplique una etiqueta instance a estas métricas. Normalmente, las etiquetas vacías y las que faltan son lo mismo en Prometheus, pero ésta es la excepción.

10 Al igual que los resúmenes y los histogramas, los indicadores tienen un decorador de función temporal y un gestor de contexto. Está pensado sólo para su uso en trabajos por lotes.

11 Las etiquetas se aplanan en el nombre de la métrica. El soporte de etiquetas para Graphite se ha añadido recientemente, en la versión 1.1.0.

12 Esto funciona en ambos sentidos. Otras bibliotecas de instrumentación con una función equivalente pueden hacer que sus métricas se introduzcan en una biblioteca cliente de Prometheus. Esto se trata en "Colectores personalizados".

13 El analizador sintáctico del cliente Go es la implementación de referencia.

14 Parte de la pila Elasticsearch.

15 El byte nulo es un carácter UTF-8 válido.

16 Sí, hay dos conjuntos diferentes de reglas de escape dentro del formato de texto. En OpenMetrics, esto se ha unificado en una sola regla, ya que las comillas dobles también deben escaparse en HELP.

17 Medianoche del 1 de enero de 1970 UTC.

18 \r\n es el final de línea en Windows, mientras que en Unix se utiliza \n. Prometheus tiene herencia Unix, por lo que utiliza \n.

19 Medianoche del 1 de enero de 1970 UTC.

Get Prometeo: Up & Running, 2ª Edición now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.