Kapitel 4. Exposition
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
In Kapitel 3 haben wir uns hauptsächlich darauf konzentriert, deinen Code zu instrumentieren. Aber alle Instrumentierung der Welt nützt nichts, wenn die erzeugten Metriken nicht in deinem Überwachungssystem landen. Der Prozess, bei dem Metriken für Prometheus verfügbar gemacht werden, wird als Exposition bezeichnet.
Die Übergabe an Prometheus erfolgt über HTTP. Normalerweise stellst du Metriken unter dem Pfad /metrics zur Verfügung und die Anfrage wird von einer Client-Bibliothek für dich bearbeitet. Prometheus unterstützt zwei für Menschen lesbare Textformate: das Prometheus-Textformat und OpenMetrics. Du hast die Möglichkeit, das Expositionsformat von Hand zu erstellen. In diesem Fall ist es einfacher, das Prometheus-Textformat zu verwenden, das weniger streng ist. Das ist einfacher, wenn du das Prometheus-Textformat verwendest, das weniger streng ist. Du kannst das auch tun, wenn es keine geeignete Bibliothek für deine Sprache gibt, aber es ist empfehlenswert, eine Bibliothek zu verwenden, da sie alle kleinen Details wie das Escaping richtig hinbekommt. Die meisten Bibliotheken bieten auch die Möglichkeit, Metriken sowohl im OpenMetrics- als auch im Prometheus-Textformat zu erstellen.
Die Exposition erfolgt in der Regel entweder in deiner Hauptfunktion oder einer anderen Top-Level-Funktion und muss nur einmal pro Anwendung konfiguriert werden.
Metriken werden normalerweise in der Standardregistrierung registriert, wenn du sie definierst. Wenn eine der Bibliotheken, von denen du abhängst, über Prometheus-Instrumente verfügt, werden die Metriken in der Standardregistrierung eingetragen und du profitierst von dieser zusätzlichen Instrumentierung, ohne etwas tun zu müssen. Manche Benutzer ziehen es vor, die Registry explizit von der Hauptfunktion aus weiterzugeben. Du müsstest dich also darauf verlassen, dass jede Bibliothek zwischen der Hauptfunktion deiner Anwendung und der Prometheus-Instrumentierung von der Instrumentierung weiß. Das setzt voraus, dass sich jede Bibliothek in der Abhängigkeitskette um die Instrumentierung kümmert und sich über die Wahl derInstrumentierungsbibliotheken einig ist.
Dieses Design ermöglicht die Instrumentierung für Prometheus-Metriken ohne jegliche Exposition.1 Abgesehen von den (geringen) Ressourcenkosten für die Instrumentierung gibt es in diesem Fall keine Auswirkungen auf deine Anwendung. Wenn du eine Bibliothek schreibst, kannst du die Instrumentierung für deine Benutzer mit Prometheus hinzufügen, ohne dass deine Benutzer, die nicht überwachen, zusätzlichen Aufwand haben. Um diesen Anwendungsfall besser zu unterstützen, versuchen die Instrumentierungsteile der Client-Bibliotheken, ihre Abhängigkeiten zu minimieren.
Werfen wir einen Blick auf die Darstellung in einigen der gängigen Client-Bibliotheken. Wir gehen hier davon aus, dass du weißt, wie du die Client-Bibliotheken und alle anderen erforderlichen Abhängigkeiten installierst.
Python
Du hast start_http_server
bereits in Kapitel 3 kennengelernt. Er startet einen Hintergrund-Thread mit einem HTTP-Server, der nur Prometheus-Metriken liefert, wie folgt:
from
prometheus_client
import
start_http_server
if
__name__
==
'__main__'
:
start_http_server
(
8000
)
//
Your
code
goes
here
.
start_http_server
ist sehr praktisch, um schnell loslegen zu können. Wahrscheinlich hast du aber bereits einen HTTP-Server in deiner Anwendung, von dem du deine Metriken abrufen möchtest.
In Python gibt es verschiedene Möglichkeiten, dies zu tun, je nachdem, welche Frameworks du verwendest.
WSGI
Web Server Gateway Interface (WSGI) ist ein Python-Standard für Webanwendungen. Der Python-Client bietet eine WSGI-App, die du mit deinem bestehenden WSGI-Code verwenden kannst. In Beispiel 4-1 wird metrics_app
von my_app
delegiert, wenn der Pfad /metrics angefordert wird; andernfalls führt sie ihre übliche Logik aus. Durch die Verkettung von WSGI-Anwendungen kannst du Middleware wie z. B. Authentifizierung hinzufügen, die Client-Bibliotheken nicht von Haus aus bieten.
Beispiel 4-1. Exposition mit WSGI in 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
()
Verdreht
Twisted ist eine ereignisgesteuerte Python-Netzwerk-Engine. Sie unterstützt WSGI, sodass du make_wsgi_app
einbinden kannst, wie in Beispiel 4-2 gezeigt.
Beispiel 4-2. Exposition mit Twisted in 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
()
Multiprozess mit Gunicorn
Prometheus geht davon aus, dass die Anwendungen, die es überwacht, langlebig und multithreaded sind. Aber das kann bei Laufzeiten wie CPython ein wenig in die Hose gehen.2 CPython ist aufgrund des Global Interpreter Lock (GIL) effektiv auf einen Prozessorkern beschränkt. Um dies zu umgehen, verteilen manche Nutzer die Arbeitslast mit einem Tool wie Gunicorn auf mehrere Prozesse.
Wenn du die Python-Client-Bibliothek auf die übliche Weise verwenden würdest, würde jeder Worker seine eigenen Metriken verfolgen. Jedes Mal, wenn Prometheus die Anwendung scrapen würde, würde es zufällig die Metriken von nur einem der Worker abrufen, was nur einen Bruchteil der Informationen ausmachen würde und außerdem zu Problemen führen würde, z. B. dass die Zähler scheinbar rückwärts laufen. Worker können auch relativ kurzlebig sein.
Die Lösung für dieses Problem, die der Python-Client bietet, ist, dass jeder Worker seine eigenen Metriken verfolgt. Zum Zeitpunkt der Veröffentlichung werden alle Metriken aller Worker so kombiniert, dass die Semantik einer Multithreading-Anwendung entsteht. Es gibt einige Einschränkungen bei diesem Ansatz: Die Metriken vonprocess_
und die benutzerdefinierten Collectors werden nicht angezeigt, und das Pushgateway kann nicht verwendet werden.3
Wenn du Gunicorn verwendest, musst du der Client-Bibliothek mitteilen, wenn ein Worker-Prozess beendet wird.4 Das machst du in einer Konfigurationsdatei wie in Beispiel 4-3.
Beispiel 4-3. Gunicorn config.py zum Umgang mit dem Beenden von Arbeitsprozessen
from
prometheus_client
import
multiprocess
def
child_exit
(
server
,
worker
):
multiprocess
.
mark_process_dead
(
worker
.
pid
)
Du brauchst auch eine Anwendung, die die Metriken liefert. Gunicorn verwendet WSGI, du kannst alsomake_wsgi_app
verwenden. Du musst eine benutzerdefinierte Registry erstellen, die nur eineMultiProcessCollector
für die Darstellung enthält, damit sie nicht sowohl die Multiprozess-Metriken als auch die Metriken aus der lokalen Standard-Registry enthält(Beispiel 4-4).
Beispiel 4-4. Gunicorn-Anwendung in 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'
]
Wie du in Beispiel 4-4 sehen kannst, funktionieren Zähler normal, ebenso wie Zusammenfassungen und Histogramme. Für Messgeräte gibt es eine zusätzliche optionale Konfiguration mit multiprocess_mode
. Du kannst das Messgerät wie folgt konfigurieren, je nachdem, wie du es verwenden möchtest:
all
-
Die Standardeinstellung, die von jedem Prozess eine Zeitreihe zurückgibt, unabhängig davon, ob er lebendig oder tot ist. So kannst du die Reihen in PromQL nach Belieben aggregieren. Sie werden durch ein
pid
Label unterschieden. liveall
-
Gibt eine Zeitreihe von jedem lebenden Prozess zurück.
livesum
-
Gibt eine einzelne Zeitreihe zurück, die die Summe der Werte jedes aktiven Prozesses ist. Du würdest dies für Dinge wie laufende Anfragen oder Ressourcennutzung über alle Prozesse hinweg verwenden. Ein Prozess könnte mit einem Wert ungleich Null abgebrochen worden sein, daher werden tote Prozesse ausgeschlossen.
max
-
Gibt eine einzelne Zeitreihe zurück, die das Maximum des Wertes jedes lebenden oder toten Prozesses ist. Das ist nützlich, wenn du das letzte Mal verfolgen willst, wann etwas passiert ist, z. B. die Bearbeitung einer Anfrage, die in einem Prozess gewesen sein könnte, der jetzt tot ist.
min
-
Gibt eine einzelne Zeitreihe zurück, die das Minimum des Wertes von jedem lebenden oder toten Prozess ist.
Bevor du Gunicorn ausführen kannst, musst du einige Einstellungen vornehmen, wie inBeispiel 4-5 gezeigt. Du musst eine Umgebungsvariable namensprometheus_multiproc_dir
setzen. Sie verweist auf ein leeres Verzeichnis, das die Client-Bibliothek für das Tracking von Metriken verwendet. Bevor du die Anwendung startest, solltest du dieses Verzeichnis immer löschen, um eventuelle Änderungen an deiner Instrumentierung zu berücksichtigen.
Beispiel 4-5. Vorbereiten der Umgebung vor dem Start von Gunicorn mit zwei Arbeitern
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
Wenn du dir den Pfad /metrics ansiehst, siehst du die beiden definierten Metriken, aberpython_info
und die process_
Metriken sind nicht dabei.
Tipp
Jeder Prozess erstellt mehrere Dateien, die zur Expositionszeit inprometheus_multiproc_dir
gelesen werden müssen. Wenn deine Worker häufig stoppen und starten, kann dies die Exposition bei tausenden von Dateien verlangsamen.
Es ist nicht sicher, einzelne Dateien zu löschen, da dies dazu führen könnte, dass die Zähler fälschlicherweise rückwärts laufen, aber du kannst entweder versuchen, den Churn zu reduzieren (z. B. indem du die Anzahl der Anfragen, die Worker vor dem Beenden bearbeiten, erhöhst oder entfernst5), oder indem du die Anwendung regelmäßig neu startest und die Dateien löschst.
Diese Schritte gelten für Gunicorn. Der gleiche Ansatz funktioniert auch mit anderen Python-Multiprozess-Konfigurationen, zum Beispiel mit dem Modul multiprocessing
.
Geh
In Go ist http.Handler
die Standardschnittstelle für die Bereitstellung von HTTP-Handlern, undpromhttp.Handler
bietet diese Schnittstelle für die Go-Client-Bibliothek. Um zu zeigen, wie das funktioniert, füge den Code in Beispiel 4-6 in eine Datei namens example.go ein.
Beispiel 4-6. Ein einfaches Go-Programm zur Demonstration von Instrumentierung und Exposition
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
))
}
Du kannst die Abhängigkeiten abrufen und diesen Code auf die übliche Weise ausführen:
hostname $ go get -d -u github.com/prometheus/client_golang/prometheus hostname $ go run example.go
In diesem Beispiel wird promauto
verwendet, das deine Metrik automatisch in der Standardregistrierung registriert. Wenn du das nicht möchtest, kannst du stattdessenprometheus.NewCounter
verwenden und dann MustRegister
in einer init
Funktion einsetzen:
func init() { prometheus.MustRegister(requests) }
Das ist etwas anfälliger, denn du kannst die Metrik leicht erstellen und verwenden, aber den Aufruf von MustRegister
vergessen.
Java
Die Java-Client-Bibliothek ist auch als Simpleclient bekannt. Sie hat den ursprünglichen Client ersetzt, der entwickelt wurde, bevor viele der aktuellen Praktiken und Richtlinien für das Schreiben einer Client-Bibliothek festgelegt wurden. Der Java-Client sollte für die Instrumentierung von Sprachen verwendet werden, die auf einer Java Virtual Machine (JVM) laufen.
HTTPServer
Ähnlich wie start_http_server
in Python bietet dir die Klasse HTTPServer
im Java-Client einen einfachen Weg, um loszulegen(Beispiel 4-7).
Beispiel 4-7. Ein einfaches Java-Programm zur Demonstration von Instrumentierung und Exposition
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
);
}
}
}
Du solltest Java-Metriken in der Regel als statische Klassenfelder haben, damit sie nur einmal registriert werden.
Der Aufruf von DefaultExports.initialize
wird benötigt, damit die verschiedenen Metriken process
undjvm
funktionieren. In der Regel solltest du ihn in allen deinen Java-Anwendungen einmal aufrufen, z. B. in der Hauptfunktion. DefaultExports.initialize
ist jedoch idempotent und thread-sicher, sodass zusätzliche Aufrufe unbedenklich sind.
Um den Code in Beispiel 4-7 ausführen zu können, brauchst du die Simpleclient-Abhängigkeiten. Wenn du Maven verwendest, sollte die dependencies
in deiner pom.xmlwie inBeispiel 4-8 aussehen.
Beispiel 4-8. pom.xml-Abhängigkeiten für Beispiel 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
Viele Java- und JVM-Frameworks unterstützen die Verwendung von Unterklassen von HttpServlet in ihren HTTP-Servern und Middleware. Jetty ist ein solcher Server. In Beispiel 4-9 siehst du, wie du die MetricsServlet
des Java-Clients verwenden kannst.
Beispiel 4-9. Ein Java-Programm zur Demonstration der Darstellung mit MetricsServlet
und 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
();
}
}
Du musst auch den Java-Client als Abhängigkeit angeben. Wenn du Maven verwendest, sieht das wie in Beispiel 4-10 aus.
Beispiel 4-10. pom.xml-Abhängigkeiten für Beispiel 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
Batch-Aufträge werden in der Regel nach einem regelmäßigen Zeitplan ausgeführt, z. B. stündlich oder täglich. Sie werden gestartet, erledigen einige Aufgaben und werden dann beendet. Da sie nicht ununterbrochen laufen, kann Prometheus sie nicht wirklich auslesen.6 An dieser Stelle kommt dasPushgateway ins Spiel.
Das Pushgateway7 ist ein Metrik-Cache für Batch-Aufträge auf Service-Ebene. Seine Architektur ist in Abbildung 4-1 dargestellt. Es erinnert sich nur an den letzten Push, den du ihm für jeden Batch-Job gibst. Du verwendest es, indem du deine Batch-Jobs ihre Metriken pushen lässt, kurz bevor sie beendet werden. Prometheus holt diese Metriken von deinem Pushgateway ab und du kannst sie dann als Alert anzeigen und grafisch darstellen. Normalerweise läuft ein Pushgateway neben einem Prometheus.
Ein Batch-Job auf Service-Ebene ist ein Job, der nicht wirklich mit einem instance
Label versehen werden kann. Das heißt, er gilt für alle deine Dienste und ist nicht von vornherein an eine Maschine oder eine Prozessinstanz gebunden.8 Wenn es dir egal ist, wo ein Batch-Job ausgeführt wird, du aber Wert darauf legst, dass er ausgeführt wird (auch wenn er derzeit per Cron auf einem Rechner läuft), handelt es sich um einen Batch-Job auf Dienstebene. Beispiele dafür sind ein Batch-Job pro Rechenzentrum, der nach fehlerhaften Rechnern sucht, oder ein Batch-Job, der die Speicherbereinigung für einen ganzen Dienst durchführt.
Hinweis
Das Pushgateway ist keine Möglichkeit, Prometheus von Pull auf Push umzustellen. Wenn zum Beispiel mehrere Pushs zwischen einem Prometheus-Scrape und dem nächsten liegen, gibt das Pushgateway nur den letzten Push für diesen Batch-Job zurück. Dies wird in "Netzwerke und Authentifizierung" näher erläutert .
Du kannst Pushgateway von der Prometheus Download-Seite herunterladen. Es ist ein Exporter, der standardmäßig auf Port 9091 läuft, und Prometheus sollte so eingestellt sein, dass es scrapen kann. Du solltest jedoch auch die Einstellung honor_labels: true
in der Scrape-Konfiguration angeben, wie in Beispiel 4-11 gezeigt. Der Grund dafür ist, dass die Metriken, die du an das Pushgateway sendest, kein instance
Label haben sollten und du nicht willst, dass das instance
Ziel-Label des Pushgateways auf den Metriken landet, wenn Prometheus sie scrapt.9 honor_labels
wird in"Label-Kollisionen und honor_labels" besprochen.
Beispiel 4-11. prometheus.yml scrape Konfiguration für ein lokales Pushgateway
scrape_configs
:
-
job_name
:
pushgateway
honor_labels
:
true
static_configs
:
-
targets
:
-
localhost:9091
Du kannst Client-Bibliotheken verwenden, um an das Pushgateway zu pushen. Beispiel 4-12 zeigt die Struktur, die du für einen Python-Batch-Job verwenden würdest. Es wird eine benutzerdefinierte Registry erstellt, damit nur die von dir ausgewählten Metriken gepusht werden. Die Dauer des Batch-Jobs wird immer gepusht,10 und die Zeit, zu der er beendet wurde, wird nur übertragen, wenn der Auftrag erfolgreich war.
Es gibt drei verschiedene Möglichkeiten, wie du an das Pushgateway schreiben kannst. In Python sind das die Funktionen push_to_gateway
, pushadd_to_gateway
und delete_from_gateway
:
push
-
Alle vorhandenen Metriken für diesen Auftrag werden entfernt und die gepushten Metriken hinzugefügt. Dies geschieht mit der HTTP-Methode PUT unter der Haube.
pushadd
-
Die gepushten Metriken überschreiben bestehende Metriken mit denselben Metriknamen für diesen Auftrag.Alle Metriken, die zuvor mit anderen Metriknamen existierten, bleiben unverändert. Dabei wird die HTTP-Methode POST unter dem Deckmantel verwendet.
delete
-
Die Metriken für diesen Auftrag werden entfernt. Dabei wird die HTTP-Methode DELETE verwendet.
Da in Beispiel 4-12 pushadd_to_gateway
verwendet wird, wird der Wert von my_job_duration_seconds
immer ersetzt. Der Wert vonmy_job_last_success_seconds
# wird jedoch nur ersetzt, wenn es keine Ausnahmen gibt; er wird der Registrierung hinzugefügt und dann verschoben.
Beispiel 4-12. Instrumentierung eines Batch-Jobs und Übermittlung seiner Metriken an ein 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
)
Du kannst gepushte Daten auf der Statusseite sehen, wie Abbildung 4-2 zeigt. Eine zusätzliche Metrik push_time_seconds
wurde vom Pushgateway hinzugefügt, weil Prometheus immer die Zeit, zu der es scrapt, als Zeitstempel der Pushgateway-Metriken verwendet. push_time_seconds
gibt dir die Möglichkeit, den tatsächlichen Zeitpunkt zu erfahren, zu dem die Daten zuletzt gepusht wurden. Mit push_failure_time_seconds
wurde eine weitere Metrik eingeführt, die den Zeitpunkt angibt, an dem eine Aktualisierung dieser Gruppe im Pushgateway zuletzt fehlgeschlagen ist.
Du hast vielleicht in Abbildung 4-2 gesehen, dass der Push als Gruppe bezeichnet wird. Neben dem Label job
kannst du beim Pushen weitere Labels angeben, die alle als Gruppierungsschlüssel bezeichnet werden. In Python kann dies mit dem Schlüsselwortargument grouping_key
angegeben werden. Du würdest dies verwenden, wenn ein Batch-Auftrag gesplittet oder irgendwie aufgeteilt wurde. Wenn du z. B. 30 Datenbank-Shards hast und jeder davon einen eigenen Batch-Auftrag, kannst du sie mit dem Label shard
unterscheiden.
Tipp
Einmal gepusht, bleiben die Gruppen für immer im Pushgateway. Du solltest es vermeiden, Gruppierungsschlüssel zu verwenden, die sich von einem Batch-Job-Lauf zum nächsten ändern, da dies die Arbeit mit den Metriken erschwert und zu Leistungsproblemen führt. Wenn du einen Batch-Job außer Betrieb nimmst, vergiss nicht, seine Metriken aus dem Pushgateway zu löschen.
Brücken
Die Prometheus-Client-Bibliotheken sind nicht auf die Ausgabe von Metriken im Prometheus-Format beschränkt. Es gibt eine Trennung zwischen Instrumentierung und Darstellung, sodass du die Metriken auf jede beliebige Weise verarbeiten kannst.
Die Go-, Python- und Java-Clients enthalten zum Beispiel jeweils eine Graphite-Bridge. Eine Bridge nimmt die Metrik-Ausgabe aus der Registry der Client-Bibliothek und gibt sie an etwas anderes als Prometheus aus. Die Graphite-Bridge wandelt die Metriken also in eine Form um, die Graphite verstehen kann11 und gibt sie an Graphite aus, wie inBeispiel 4-13 gezeigt.
Beispiel 4-13. Verwendung der Python GraphiteBridge
, um alle 10 Sekunden an Graphite zu pushen
import
time
from
prometheus_client.bridge.graphite
import
GraphiteBridge
gb
=
GraphiteBridge
([
'graphite.your.org'
,
2003
])
gb
.
start
(
10
)
while
True
:
time
.
sleep
(
1
)
Das funktioniert, weil die Registry eine Methode hat, mit der du einen Schnappschuss aller aktuellen Metriken erhalten kannst. Das ist CollectorRegistry.collect
in Python,CollectorRegistry.metricFamilySamples
in Java und Registry.Gather
in Go. Diese Methode wird in der HTTP-Exposition verwendet und du kannst sie auch nutzen. Du könntest diese Methode zum Beispiel verwenden, um Daten in eine andere, nicht zu Prometheus gehörende Instrumentierungsbibliothek einzuspeisen.12
Tipp
Wenn du dich jemals in die direkte Instrumentierung einklinken willst, solltest du stattdessen die von einer Registry ausgegebenen Metriken verwenden. Jedes Mal zu wissen, wenn ein Zähler erhöht wird, ist für ein metrikbasiertes Überwachungssystem nicht sinnvoll. Die Anzahl der Inkremente wird dir jedoch bereits vonCollectorRegistry.collect
zur Verfügung gestellt und funktioniert für benutzerdefinierte Collectors.
Parser
Zusätzlich zur Registry der Client-Bibliothek, die den Zugriff auf die Metrik-Ausgabe ermöglicht, bieten die Go13 und Python-Clients verfügen auch über einen Parser für die Prometheus- und OpenMetrics-Ausgabeformate. Beispiel 4-14 gibt nur die Beispiele aus, aber du kannst die Prometheus-Metriken auch in andere Überwachungssysteme oder in dein lokales Tooling einspeisen.
Beispiel 4-14. Parsen des Prometheus-Textformats mit dem Python-Client
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 und Metricbeat14 sind einige der Überwachungssysteme, die über Komponenten verfügen, die das Textformat parsen können.Wenn du eines dieser Überwachungssysteme verwendest, kannst du die Vorteile des Prometheus-Ökosystems nutzen, ohne den Prometheus-Server zu starten. Wir glauben, dass dies eine gute Sache ist, da es derzeit viel Doppelarbeit zwischen den verschiedenen Überwachungssystemen gibt. Jedes von ihnen muss ähnlichen Code schreiben, um die unzähligen benutzerdefinierten Metrikausgaben zu unterstützen, die von der am häufigsten verwendeten Software bereitgestellt werden.
Format der Textexposition
Das Prometheus-Textexpositionsformat ist relativ einfach zu erzeugen und zu parsen.Obwohl du dich fast immer auf eine Client-Bibliothek verlassen solltest, die das Format für dich handhabt, gibt es Fälle, wie den Node Exporter Textfile Collector (siehe"Textfile Collector"), in denen du es selbst erzeugen musst.
Wir zeigen dir die Version 0.0.4 des Textformats, das den Inhaltstyp Header hat:
Content-Type: text/plain; version=0.0.4; charset=utf-8
In den einfachsten Fällen besteht das Textformat nur aus dem Namen der Metrik, gefolgt von einer 64-Bit-Gleitkommazahl. Jede Zeile wird mit einem Zeilenvorschubzeichen (\n
) abgeschlossen:
my_counter_total 14 a_small_gauge 8.3e-96
Metrische Typen
Eine vollständigere Prometheus-Ausgabe im Textformat würde die HELP
und TYPE
der Metrik enthalten, wie in Beispiel 4-15 gezeigt. HELP
ist eine Beschreibung der Metrik und sollte sich im Allgemeinen nicht von Scrape zu Scrape ändern. TYPE
ist eine der Optionencounter
, gauge
, summary
, histogram
oder untyped
. untyped
wird verwendet, wenn du den Typ der Metrik nicht kennst, und ist die Standardeinstellung, wenn kein Typ angegeben wird. Es ist ungültig, wenn du eine doppelte Metrik hast. Achte also darauf, dass alle Zeitreihen, die zu einer Metrik gehören, in einer Gruppe zusammengefasst werden.
Beispiel 4-15. Darstellungsformat für ein Messgerät, einen Zähler, eine Zusammenfassung und ein Histogramm
# 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
Bei Histogrammen haben die
le
Beschriftungen Fließkommawerte und müssen sortiert werden. Du solltest beachten, wie die Histogramm-Eimer kumuliert werden, dennle
steht für kleiner oder gleich.Der
_count
muss mit dem+Inf
Bucket übereinstimmen, und der+Inf
Bucket muss immer vorhanden sein. Die Buckets sollten sich nicht von Scrape zu Scrape ändern, da dies zu Problemen mit derhistogram_quantile
Funktion von PromQL führt.
Etiketten
Das Histogramm im vorangegangenen Beispiel zeigt auch, wie Beschriftungen dargestellt werden. Mehrere Beschriftungen werden durch Kommas getrennt, und es ist in Ordnung, wenn vor der schließenden Klammer ein nachgestelltes Komma steht.
Die Reihenfolge der Labels spielt keine Rolle, aber es ist eine gute Idee, die Reihenfolge von Scrape zu Scrape einheitlich zu gestalten. Das macht das Schreiben deiner Unit-Tests einfacher, und eine konsistente Reihenfolge sorgt für die beste Ingestion-Leistung in Prometheus.
Hier ist ein Beispiel für eine Zusammenfassung im Textformat:
# 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 ist möglich, eine Metrik ohne Zeitreihe zu haben, wenn keine Kinder initialisiert wurden, wie in "Kind" beschrieben :
# HELP a_counter_total An example counter # TYPE a_counter_total counter
Die Flucht
Das Format der Textexposition ist in UTF-8 kodiert, und das vollständige UTF-815 ist sowohl in HELP
als auch in den Label-Werten erlaubt. Daher musst du Backslashes verwenden, um Zeichen zu vermeiden, die bei der Verwendung von Backslashes Probleme verursachen würden. Für HELP
sind das Zeilenvorschübe und Backslashes. Bei Labelwerten sind dies Zeilenvorschübe, Backslashes und doppelte Anführungszeichen.16 Das Format ignoriert zusätzliche Leerzeichen.
Hier ist ein Beispiel, das das Escaping im Format der Textexposition zeigt:
# HELP escaping A newline \\n and backslash \\ escaped # TYPE escaping gauge escaping{foo="newline \\n backslash \\ double quote \" "} 1
Zeitstempel
Es ist möglich, einen Zeitstempel für eine Zeitreihe anzugeben. Das ist ein ganzzahliger Wert in Millisekunden seit der Unix-Epoche,17 und steht hinter dem Wert. Zeitstempel im Expositionsformat sollten in der Regel vermieden werden, da sie nur in bestimmten Anwendungsfällen (z. B. bei der Föderation) anwendbar sind und mit Einschränkungen verbunden sind. Zeitstempel für Scraps werden in der Regel automatisch von Prometheus angewendet. Es ist nicht definiert, was passiert, wenn du mehrere Zeilen mit demselben Namen und denselben Bezeichnungen, aber unterschiedlichen Zeitstempeln angibst.
Dieses Messgerät hat einen Zeitstempel:
# HELP foo I'm trapped in a client library # TYPE foo gauge foo 1 15100992000000
Metriken prüfen
Prometheus 2.0 verwendet aus Effizienzgründen einen eigenen Parser. Nur weil ein /metrics-Endpunkt gecrappt werden kann, heißt das nicht, dass die Metriken mit dem Format konform sind.
Promtool ist ein Dienstprogramm, das in Prometheus enthalten ist und unter anderem überprüfen kann, ob deine Metrik-Ausgabe gültig ist und Lint-Checks durchführt:
curl http://localhost:8000/metrics | promtool check metrics
Häufige Fehler sind das Vergessen des Zeilenvorschubs in der letzten Zeile, die Verwendung von Wagenrücklauf und Zeilenvorschub statt nur Zeilenvorschub,18 und ungültige Metrik- oder Labelnamen. Zur Erinnerung: Metrik- und Labelnamen dürfen keine Bindestriche enthalten und nicht mit einer Zahl beginnen.
Du kennst dich jetzt mit dem Textformat aus. Die vollständige Spezifikation findest du in der offiziellen Prometheus-Dokumentation.
OpenMetrics
Das OpenMetrics-Format ähnelt dem Prometheus-Textformat, enthält aber einige inkompatible Änderungen gegenüber dem Prometheus-Textformat. Auch wenn sie ähnlich aussehen, ist die Ausgabe für eine bestimmte Menge von Metriken in der Regel unterschiedlich.
Wir zeigen dir die Version 1.0.0 des OpenMetrics-Formats, das den Content-Type-Header hat:
Content-Type: application/openmetrics-text; version=1.0.0; charset=utf-8
In den einfachsten Fällen besteht das Textformat nur aus dem Namen der Metrik, gefolgt von einer 64-Bit-Gleitkommazahl. Jede Zeile wird mit einem Zeilenvorschubzeichen (\n
) abgeschlossen. Die Datei wird mit # EOF
abgeschlossen:
my_counter_total 14 a_small_gauge 8.3e-96 # EOF
Metrische Typen
Die vom Prometheus-Textformat unterstützten Metrik-Typen werden auch in OpenMetrics unterstützt. Zusätzlich zu den Zählern, Messgeräten, Zusammenfassungen und Histogrammen wurden spezielle Typen hinzugefügt: StateSet, GaugeHistograms und Info.
StateSets stellen eine Reihe von zusammenhängenden booleschen Werten dar, die auch als Bitset bezeichnet werden. Ein Wert von 1
bedeutet true
und 0
bedeutet false
.
GaugeHistograms messen aktuelle Verteilungen. Der Unterschied zu Histogrammen ist, dass die Werte und die Summe der Eimer nach oben und unten gehen können.
Info-Metriken werden verwendet, um Textinformationen offenzulegen, die sich während der Prozessdauer nicht ändern. Die Version einer Anwendung, ein Commit der Versionskontrolle und die Version eines Compilers sind gute Kandidaten. Der Wert dieser Metriken ist immer 1.
Zusätzlich zu HELP
und TYPE
haben die Metrik-Familien in OpenMetrics ein optionales UNIT
Metadatum, das die Einheit einer Metrik angibt.
Alle Typen werden in Beispiel 4-16 gezeigt.
Beispiel 4-16. Darstellungsformat für verschiedene Arten von Metriken
# 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
In OpenMetrics, wie in Beispiel 4-16 gezeigt, verwenden GaugeHistograms unterschiedliche _gcount
und _gsum
Suffixe für Zählungen und Summen, wodurch sie sich von den Histograms _count
und _sum
unterscheiden.
Zeitstempel
Es ist möglich, einen Zeitstempel für eine Zeitreihe anzugeben. Das ist ein Fließkommawert in Sekunden seit der Unix-Epoche,19 und steht, wie in diesem Beispiel gezeigt, hinter dem Wert:
# HELP foo I'm trapped in a client library # TYPE foo gauge foo 1 1.5100992e9
Warnung
Zeitstempel werden in OpenMetrics in Sekunden seit der Epoche angegeben, während sie im Prometheus-Textformat in Millisekunden seit der Epoche angegeben werden.
Du kennst jetzt das OpenMetrics-Format. Die vollständige Spezifikation findest du im OpenMetrics GitHub Repository.
Wir haben jetzt schon ein paar Mal über Labels gesprochen. Im folgenden Kapitel erfährst du, was sie im Detail sind.
1 Keine Exposition bedeutet, dass die Metriken nicht von einem Prometheus-Server abgefragt werden.
2 CPython ist der offizielle Name für die Standard-Python-Implementierung. Verwechsle sie nicht mit Cython, mit dem du C-Erweiterungen in Python schreiben kannst.
3 Das Pushgateway ist für diesen Anwendungsfall nicht geeignet, daher ist dies in der Praxis kein Problem.
4 child_exit
wurde in Gunicorn Version 19.7, die im März 2017 veröffentlicht wurde, hinzugefügt.
5 Die Flagge von Gunicorn --max-requests
ist ein Beispiel für eine solche Grenze.
6 Bei Batch-Aufträgen, deren Ausführung mehr als ein paar Minuten dauert, kann es jedoch auch sinnvoll sein, sie normal über HTTP zu scrapen, um Leistungsprobleme zu beheben.
7 In informellen Kontexten kann es auch als pgw bezeichnet werden.
8 Für Batch-Aufträge wie Datenbanksicherungen, die an den Lebenszyklus eines Rechners gebunden sind, ist der Node Exporter Textfile Collector die bessere Wahl. Dies wird im Abschnitt "Textdatei-Collector" erläutert .
9 Das Pushgateway exportiert ausdrücklich leere instance
Labels für Metriken ohne instance
Label. In Kombination mit honor_labels: true
führt dies dazu, dass Prometheus diesen Metriken kein instance
Label zuweist. Normalerweise sind leere Labels und fehlende Labels in Prometheus das Gleiche, aber das ist die Ausnahme.
10 Genau wie Zusammenfassungen und Histogramme haben Gauges einen Zeitfunktionsdekorator und einen Kontextmanager. Er ist nur für die Verwendung in Batch-Aufträgen gedacht.
11 Die Bezeichnungen werden in den Namen der Metrik eingefügt. Die Unterstützung von Tags (d. h. Bezeichnungen) für Graphite wurde erst kürzlich in Version 1.1.0 hinzugefügt.
12 Das funktioniert in beide Richtungen. Andere Instrumentierungsbibliotheken mit einer entsprechenden Funktion können ihre Metriken in eine Prometheus-Client-Bibliothek einspeisen. Dies wird unter "Benutzerdefinierte Collectors" beschrieben .
13 Der Parser des Go-Clients ist die Referenzimplementierung.
14 Teil des Elasticsearch-Stacks.
15 Das Null-Byte ist ein gültiges UTF-8-Zeichen.
16 Ja, es gibt zwei verschiedene Regeln für das Escape-Format im Textformat. In OpenMetrics wurde dies zu einer einzigen Regel vereinheitlicht, da doppelte Anführungszeichen auch in HELP
escaped werden müssen.
17 Mitternacht 1. Januar 1970 UTC.
18 Unter Windows ist \r\n
die Zeilenendung, während unter Unix \n
verwendet wird. Prometheus hat ein Unix-Erbe, daher verwendet es \n
.
19 Mitternacht 1. Januar 1970 UTC.
Get Prometheus: Up & Running, 2. Auflage 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.