Hello! This article is also available in English!

NGINX-Performancemetriken mit Prometheus

Prometheus ist eine Kombination aus Monitoring-Werkzeug und Zeitreihen-Datenbank, die ich in den letzten Monaten sehr zu schätzen gelernt habe. Dieser Artikel zeigt, wie Prometheus genutzt werden kann, um verschiedene Webserver-Metriken (ohne Konfigurationseingriff) in Prometheus nutzbar zu machen.

Grundsätzlich werden Metriken nach dem Pull-Prinzip in Prometheus importiert. Dies bedeutet, dass ein zu überwachender Service einen HTTP-Endpunkt anbieten muss, der von Prometheus dann in regelmäßigen Intervallen (standardmäßig 15 Sekunden) abgefragt werden kann. Dieser Endpunkt (beispielsweise http://<service-name>/metrics) muss dann eine Antwort mit entsprechenden Zeitreihendaten ausliefern:

http_response_count_total{application="my-application"} 12423
http_response_time_seconds{application="my-application"} 1832745

Dies funktioniert ausgezeichnet in Microservice-Architekturen – hier kann jeder Service einen einzelnen /metrics-Endpunkt implementieren, der alle denkbaren Kennzahlen auswirft.

Das Problem

Nicht so gut funktioniert dieser Ansatz, wenn Prometheus zur Überwachung von Performance-Metriken einer (älteren) Web-Applikation genutzt werden soll, die in einem “klassischen” LEMP-Stack (Linux, NGINX, MySQL, PHP) betrieben wird. Dies liegt daran, dass eine PHP-Applikation üblicherweise nicht über solche Metriken wie Anzahl der verarbeiteten Requests oder die durchschnittliche Request-Dauer verfügt (oder sie selbst erheben und dann in einer Datenbank persistieren müsste).

Für die Integration von NGINX-Webservern mit Prometheus gibt es bereits mehrere Konnektoren im Internet – diese werten jedoch entweder nur die Informationen der NGINX-Statusseite aus, die über das Modul ngx_http_stub_status_module angeboten wird (nicht detailliert genug für meinen Anwendungsfall), oder erfordern eine Umkonfiguration des Webservers, um einen neuen Logging-Server anzusprechen (zu invasiv für meine gehegte und gepflegte Alt-Applikation).

Meine Lösung

Meine Lösung des Problems besteht nun in einem eigenen Exporter, der Performance-Metriken aus bestehenden NGINX-Accesslogs generieren kann. Dieser steht als Open Source auf Github zur Verfügung, und stellt einem Prometheus-Server auf Grundlage einer bestehenden access.log-Datei diverse Metriken zur Verfügung:

  • Anzahl der verarbeiteten Requests pro Request-Methode und Status
  • Summe der für die bisher verarbeiteten Requests notwendige Zeit, pro Methode und Status (zusammen mit der Anzahl kann hieraus die durchschnittliche Antwortzeit über den zeitlichen Verlauf ermittelt werden)
  • Summe der “Upstream Time”, also der Zeit, die NGINX damit zugebracht hat, auf PHP-FPM oder andere FastCGI-Module zu warten
  • Diverse Quantile der Antwortzeit und Upstream Time

Konfiguration

Der NGINX-Exporter wird über eine HCL-Konfigurationsdatei konfiguriert. In der Konfigurationsdatei können mehrere “Namespaces” konfiguriert werden – diese werden dann als separate Metriken exportiert. Nachfolgend ein kurzes Beispiel für eine solche Konfigurationsdatei:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
listen {
  port = 4040
  address = "0.0.0.0"
}

namespace "app1" {
  format = "$remote_addr - $remote_user [$time_local] \"$request\" $status $body_bytes_sent \"$http_referer\" \"$http_user_agent\" \"$http_x_forwarded_for\""
  source_files = ["/var/log/nginx/access.log"]
  labels {
    app = "my-application"
    environment = "production"
    foo = "bar"
  }
}

Beim Start des Exporters startet dieser einen eigenen HTTP-Server, der auf der konfigurierten IP-Adresse und Port lauscht (standardmäßig 0.0.0.0:4040). Über die URL http://<IP>:4040/metrics können dann von Prometheus die entsprechenden Performance-Metriken abgegriffen werden.

Der Namespace-Name (hier app1) wird später in den Namen der exportierten Metriken übernommen – also beispielsweise app1_http_response_time_seconds. Das Format (format) gibt das Format an, in dem NGINX die Access-Logs schreibt (für mehr Informationen dazu sei auf die Dokumentation verwiesen).

Start des Exporters

Der eigentliche Exporter ist ein statisch kompiliertes Go-Binary, welches nach dem Herunterladen sofort installiert werden kann:

wget https://github.com/martin-helmich/prometheus-nginxlog-exporter/releases/download/v1.0.0/prometheus-nginxlog-exporter
./prometheus-nginxlog-exporter -config-file /pfad/zur/config.hcl

Um sicherzustellen, dass der Exporter beim Systemstart automatisch startet, kann eine entsprechende systemd-Unit konfiguriert werden (ab Debian 8, Ubuntu 16.04 oder CentOS 7). Diese kann platziert werden unter /etc/systemd/system/prometheus-nginxlog-exporter.service:

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=NGINX metrics exporter for Prometheus
After=network-online.target

[Service]
ExecStart=/usr/local/bin/prometheus-nginxlog-exporter -config-file /etc/prometheus-nginxlog-exporter.hcl
Restart=always
ProtectSystem=full
CapabilityBoundingSet=

[Install]
WantedBy=multi-user.target

Beachtet, dass diese Unitfile davon ausgeht, dass das Binary unter /usr/local/bin/prometheus-nginxlog-exporter und die Konfigurationsdatei unter /etc/prometheus-nginxlog-exporter.hcl liegt. Diese Pfade können natürlich nach Bedarf angepasst werden.

Das Ergebnis

Ich betreibe den prometheus-nginxlog-exporter nun seit einiger Zeit schon produktiv. Insbesondere zusammen mit Grafana lassen sich wunderbare Auswertungen und Monitoring-Dashboards erstellen:

NGINX-Monitoring in Aktion

Die Diagramme im obigen Screenshot wurden aus den folgenden Prometheus-Queries generiert:

  • Durchschnittliche Antwortzeit: sum(rate(app_http_response_time_seconds_sum[5m])) by (instance) / sum(rate(app_http_response_time_seconds_count[5m])) by (instance)
  • Anfragen pro Sekunde: sum(rate(app_http_response_time_seconds_count[1m])) by (instance)
  • Antwortzeit (90%-Quantil): app_http_response_time_seconds{quantile="0.9",method="GET",status="200"}
  • HTTP-Traffic: sum(rate(app_http_response_size_bytes[5m])) by (instance)
  • Statuscodes pro Sekunde: sum(rate(app_http_response_count_total[1m])) by (status)

Kommentare