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.initialize
es 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.
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.
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
:
(
"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 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
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.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
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.
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.