Zum Inhalt springen

Metriken mit Prometheus

Auftrag

«Es ist Frühling. Der Webshop des Veloateliers wird jeden Samstag langsamer», schreibt Simone Wyss. «Die Praxis Rosenweg will wissen, ob die Plattform läuft, bevor Patientinnen anrufen. Und ich will sehen, ob unsere Knoten noch Platz haben.»

Lernziele

Nach diesem Kapitel kannst du:

  • Die vier goldenen Signale nennen und erklären, was Überwachung leisten soll
  • Das Modell von Prometheus mit Zeitreihen, Labels, Exportern und dem Abholen erklären
  • Den Monitoring-Stack über Argo CD aus deinem Plattform-Repo ausrollen
  • OpenNebula und den Webshop als Ziele einbinden
  • Einfache Abfragen mit PromQL schreiben und in Grafana darstellen

Überwachung soll zeigen, was kaputt ist und warum. Dass etwas nicht stimmt, merkt oft auch die Kundschaft, etwa an einem langsamen Shop. Den Grund findest du erst im System selbst, zum Beispiel volle Disks oder einen Knoten ohne freien Speicher. Das Buch «Site Reliability Engineering» von Google nennt vier goldene Signale, die man für jeden Dienst messen sollte.

SignalFrageBeim Webshop
LatenzWie lange dauert eine Antwort?Antwortzeit von /
VerkehrWie viel wird verlangt?Anfragen pro Sekunde
FehlerWie viel geht schief?Anteil der Antworten mit 5xx
SättigungWie voll ist das System?CPU der Pods, Speicher der Knoten

Prometheus speichert Zeitreihen. Eine Zeitreihe ist ein Name mit Labels und einer Folge von Werten mit Zeitstempel.

webshop_requests_total{code="200", path="/", pod="webshop-599c7b4f94-4htg9"} 1532

Prometheus holt die Werte selbst ab, in der Regel alle 15 bis 30 Sekunden. Jedes Ziel bietet unter /metrics eine Textseite an. Programme, die das nicht selbst können, bekommen einen Exporter, ein kleines Programm, das Werte sammelt und im Format von Prometheus ausgibt. Der Webshop aus Kapitel 4.4 hat /metrics schon eingebaut.

TypBedeutungBeispiel
Counterzählt nur aufwärts, bis zum Neustartwebshop_requests_total
Gaugekann steigen und sinkenopennebula_host_mem_usage_bytes
Histogramzählt Werte in Klassen, für Perzentilewebshop_request_duration_seconds_bucket

kube-prometheus-stack ist ein Helm-Chart der Community von Prometheus. Es bringt Prometheus, Alertmanager, Grafana, den Prometheus Operator, kube-state-metrics für den Zustand der Objekte im Cluster und node-exporter für die Nodes mit. Der Operator führt eigene Ressourcentypen ein. Ein ServiceMonitor sagt, bei welchen Services Prometheus Metriken abholt, eine ScrapeConfig nennt Ziele ausserhalb des Clusters, eine PrometheusRule enthält Regeln.

Der Stack kommt wie der Webshop aus Git. Die Ergänzungen für Teil 5 liegen im Kursrepo unter platform/observability/repo. Du kopierst sie über dein Plattform-Repo und ersetzt die Platzhalter durch deine Adressen.

Terminal-Fenster
source /etc/cc-lab.env
cd ~/platform
cp -r ~/cc/platform/observability/repo/. .
sed -i "s#http://FRONTEND:3000#http://$CC_FE_IP:3000#" apps/*.yaml
sed -i "s/FRONTEND_IP/$CC_FE_IP/; s/NODE1_IP/$CC_NODE1_IP/; s/NODE2_IP/$CC_NODE2_IP/" monitoring/config/opennebula.yaml
git status

git status zeigt neue Applications in apps, den Ordner monitoring und geänderte Dateien in webshop und templates/member. Bevor du pushst, legst du das Passwort für Grafana als Secret an, damit es nicht in Git landet.

make-secrets.sh
#!/bin/bash
# Creates the secrets of the monitoring stack that do not belong in Git, chapter 5.1.
#
# ~/cc/platform/observability/make-secrets.sh
#
# The Grafana password is stored in ~/cc-lab/grafana.pass.
set -euo pipefail
pass_file=~/cc-lab/grafana.pass
if [ ! -f "$pass_file" ]; then
install -m 0600 /dev/null "$pass_file"
openssl rand -hex 12 > "$pass_file"
fi
kubectl create namespace monitoring --dry-run=client -o yaml | kubectl apply -f -
kubectl -n monitoring create secret generic grafana-admin \
--from-literal=admin-user=admin \
--from-literal=admin-password="$(tr -d '\n' < "$pass_file")" \
--dry-run=client -o yaml | kubectl apply -f -
echo "Grafana user admin, password in $pass_file"
Terminal-Fenster
~/cc/platform/observability/make-secrets.sh

Die Application monitoring holt das Chart aus dem Repo der Community und die Werte aus deinem Gitea. Argo CD kann dafür mehrere Quellen in einer Application verbinden.

monitoring.yaml
# Prometheus, Alertmanager and Grafana from the chart kube-prometheus-stack, the values come from this repo
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: monitoring
namespace: argocd
spec:
project: default
sources:
- repoURL: https://prometheus-community.github.io/helm-charts
chart: kube-prometheus-stack
targetRevision: 92.17.0
helm:
releaseName: monitoring
valueFiles:
- $values/monitoring/kube-prometheus-stack.yaml
- repoURL: http://FRONTEND:3000/wolkenwerk/platform.git
targetRevision: main
ref: values
destination:
server: https://kubernetes.default.svc
namespace: monitoring
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
# The definitions of the operator are too large for the annotation of a client-side apply
- ServerSideApply=true
kube-prometheus-stack.yaml
# Values of kube-prometheus-stack for the lab, chapter 5.1
# k3s runs etcd, scheduler, controller manager and proxy inside one process, their own monitors find nothing
kubeEtcd:
enabled: false
kubeControllerManager:
enabled: false
kubeScheduler:
enabled: false
kubeProxy:
enabled: false
prometheus:
service:
type: NodePort
nodePort: 30090
prometheusSpec:
retention: 3d
resources:
requests:
cpu: 100m
memory: 512Mi
# Read monitors, rules and scrape configs of every namespace, not only those of this chart
serviceMonitorSelectorNilUsesHelmValues: false
podMonitorSelectorNilUsesHelmValues: false
ruleSelectorNilUsesHelmValues: false
scrapeConfigSelectorNilUsesHelmValues: false
storageSpec:
volumeClaimTemplate:
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 5Gi
alertmanager:
# Every alert goes to the receiver null, the advanced tasks add a receiver of their own
config:
route:
receiver: "null"
group_by: [alertname, namespace]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receivers:
- name: "null"
service:
type: NodePort
nodePort: 30093
alertmanagerSpec:
resources:
requests:
cpu: 10m
memory: 64Mi
grafana:
service:
type: NodePort
nodePort: 30300
# The password lives in a secret that make-secrets.sh creates, not in Git, part 6 replaces it with a SealedSecret
admin:
existingSecret: grafana-admin
userKey: admin-user
passwordKey: admin-password
additionalDataSources:
- name: Loki
type: loki
uid: loki
url: http://loki.monitoring.svc:3100
# Dashboards from config maps with the label grafana_dashboard in any namespace
sidecar:
dashboards:
enabled: true
searchNamespace: ALL

k3s führt Scheduler, Controller Manager und Proxy in einem einzigen Prozess aus. Die Monitore, die das Chart dafür mitbringt, fänden nichts und blieben rot. Darum sind sie abgeschaltet. Das README des Charts in der Version 92.17.0 beschreibt die Installation, die Werte und für Argo CD die Option ServerSideApply.

Für die IaaS-Schicht bringt OpenNebula eigene Exporter mit, im Paket opennebula-prometheus-kvm. Der OpenNebula-Exporter läuft auf dem Front-end und liefert auf Port 9925 Hosts, VMs und Datastores, wie OpenNebula sie sieht. Auf dem Front-end und auf beiden Knoten liefert node_exporter auf Port 9100 die Werte des Betriebssystems. Die Knoten haben zusätzlich den Libvirt-Exporter auf Port 9926, er zeigt die VMs, wie KVM sie sieht. Das Paket opennebula-prometheus installierst du nicht, es brächte ein zweites Prometheus mit. Für die Exporter folgt das Skript unten der Anleitung von OpenNebula 7.4, die zusätzlich dieses Paket installiert.

install-exporters.sh
#!/bin/bash
# Installs the Prometheus exporters of OpenNebula, the step of chapter 5.1. Run it on the front-end.
#
# ~/cc/platform/observability/install-exporters.sh
#
# Front-end: the OpenNebula exporter (port 9925) and node_exporter (9100).
# Nodes: node_exporter (9100) and the libvirt exporter (9926).
# The package opennebula-prometheus would bring a Prometheus of its own, the cluster already has one.
set -euo pipefail
# shellcheck source=/dev/null
source /etc/cc-lab.env
sudo apt-get install -y opennebula-prometheus-kvm
sudo systemctl enable --now opennebula-exporter opennebula-node-exporter
for node in "$CC_NODE1_HOST" "$CC_NODE2_HOST"; do
ssh "$node" "sudo apt-get install -y opennebula-prometheus-kvm && sudo systemctl enable --now opennebula-node-exporter opennebula-libvirt-exporter"
done
sleep 5
for target in "$CC_FE_IP:9925" "$CC_FE_IP:9100" "$CC_NODE1_IP:9100" "$CC_NODE1_IP:9926" "$CC_NODE2_IP:9100" "$CC_NODE2_IP:9926"; do
if curl -fs -o /dev/null "http://$target/metrics"; then echo "ok $target"; else echo "!! $target"; fi
done
Terminal-Fenster
~/cc/platform/observability/install-exporters.sh

Im Lab meldeten alle sechs Ziele ok. Der OpenNebula-Exporter kannte die Hosts wenige Sekunden bis eine halbe Minute nach dem Start. Prometheus erreicht die Exporter über drei ScrapeConfigs mit den Adressen aus deinen Parametern. Neue ScrapeConfigs und ServiceMonitors übernimmt Prometheus erst, wenn der Config-Reloader die geänderte Konfiguration sieht. Im Lab erschienen die Jobs der Ziele ausserhalb des Clusters zwei bis fünf Minuten nach dem Push, der Job webshop nach knapp vier Minuten.

In Prometheus zeigt «Status › Target health» jedes Ziel mit seinem Zustand.

Die Ziele in Prometheus unter Target health. Die drei ScrapeConfigs opennebula, opennebula-hosts und opennebula-libvirt holen die Werte vom Front-end und von den Knoten. Jedes der sechs Ziele steht auf UP, daneben stehen seine Labels und die Zeit seiner letzten Abfrage.
Was Prometheus abholt Links der Cluster k3s. Prometheus läuft im Namespace monitoring und holt die Metriken im Cluster vom Webshop im Namespace velo, von kube-state-metrics und von node-exporter auf k3s-server und k3s-agent. Ausserhalb des Clusters holt es auf dem Front-end den OpenNebula-Exporter auf Port 9925 und node_exporter auf Port 9100 ab, auf beiden Knoten node_exporter auf Port 9100 und den Libvirt-Exporter auf Port 9926. Grafana fragt Prometheus mit PromQL ab. Im Cluster findet Prometheus die Ziele über ServiceMonitors, ausserhalb über drei ScrapeConfigs. CLUSTER K3S ServiceMonitor FRONT-END ScrapeConfig KNOTEN 1 UND 2 ScrapeConfig PromQL Webshop Pods in velo, Port 8080 kube-state-metrics Zustand der Objekte node-exporter k3s-server, k3s-agent Grafana Dashboards Prometheus Namespace monitoring holt /metrics ab OpenNebula-Exporter Port 9925 node_exporter Port 9100 node_exporter Port 9100 Libvirt-Exporter Port 9926
opennebula.yaml
# The exporters of OpenNebula outside the cluster, the addresses come from the personal parameters
apiVersion: monitoring.coreos.com/v1alpha1
kind: ScrapeConfig
metadata:
name: opennebula
spec:
jobName: opennebula
staticConfigs:
- labels:
role: front-end
targets:
- FRONTEND_IP:9925
---
apiVersion: monitoring.coreos.com/v1alpha1
kind: ScrapeConfig
metadata:
name: opennebula-hosts
spec:
jobName: opennebula-hosts
staticConfigs:
- labels:
role: front-end
targets:
- FRONTEND_IP:9100
- labels:
role: node
targets:
- NODE1_IP:9100
- NODE2_IP:9100
---
apiVersion: monitoring.coreos.com/v1alpha1
kind: ScrapeConfig
metadata:
name: opennebula-libvirt
spec:
jobName: opennebula-libvirt
staticConfigs:
- labels:
role: node
targets:
- NODE1_IP:9926
- NODE2_IP:9926

Für den Webshop genügt ein ServiceMonitor neben seinem Deployment. Prometheus holt die Metriken dann bei jedem Pod hinter dem Service webshop ab.

servicemonitor.yaml
# Prometheus reads /metrics of every web shop pod every 15 seconds
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: webshop
spec:
selector:
matchLabels:
app: webshop
endpoints:
- port: http
path: /metrics
interval: 15s

Die NetworkPolicy der Mitglieder aus Kapitel 4.5 liess bisher nur den eigenen Namespace und kube-system durch. Ohne Änderung käme Prometheus nicht an /metrics. Die neue Fassung der Vorlage lässt auch den Namespace monitoring zu.

networkpolicy.yaml
# Pods of a member accept traffic only from their own namespace, from the ingress of k3s in kube-system
# and from Prometheus in monitoring
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: member
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- podSelector: {}
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring

Sobald du alles gepusht hast, rollt Argo CD den Stack aus. Im Lab war er 140 bis 160 Sekunden nach dem Push synchron und gesund.

Terminal-Fenster
git add .
git commit -m "Überwachung mit Prometheus, Grafana und den Exportern von OpenNebula"
git push
kubectl -n monitoring get pods

Prometheus und Grafana erreichst du wie Argo CD über einen Tunnel, Prometheus auf Port 30090 und Grafana auf Port 30300 des Servers. Den Tunnel öffnest du vom Client aus, auf smartlearn ist das die Kali-VM, mit eigenem Hypervisor dein PC.

Terminal-Fenster
ssh -L 9090:<adresse-des-servers>:30090 -L 3000:<adresse-des-servers>:30300 mmuster-fe

Im Browser des Clients zeigt Prometheus unter http://localhost:9090/targets jedes Ziel mit seinem Zustand. Im Lab waren alle up, auch die Jobs von OpenNebula (opennebula, opennebula-hosts, opennebula-libvirt) und der Job webshop. Unter «Query» probierst du Abfragen aus.

AbfrageErgebnis
up1 für jedes erreichbare Ziel, 0 sonst
opennebula_host_mem_usage_bytes / opennebula_host_mem_total_bytesAnteil des Speichers jedes Knotens, den VMs belegen
rate(webshop_requests_total[5m])Anfragen pro Sekunde, gemittelt über fünf Minuten
sum by (code) (rate(webshop_requests_total[5m]))dasselbe, nach Statuscode zusammengezählt
histogram_quantile(0.95, sum by (le) (rate(webshop_request_duration_seconds_bucket[5m])))Zeit, in der 95 % der Anfragen beantwortet sind

Ein Counter allein sagt wenig, weil er seit dem Start des Pods nur steigt. rate() macht daraus eine Änderung pro Sekunde und übersteht auch Neustarts, bei denen der Counter auf null fällt. sum by fasst Zeitreihen zusammen und behält nur die genannten Labels. Im Lab belegten die k3s-VMs laut der zweiten Abfrage auf jedem Knoten 83 % des Speichers, den OpenNebula vergeben kann.

Grafana zeigt Abfragen als Dashboards. Im Browser des Clients meldest du dich unter http://localhost:3000/ als admin an, mit dem Passwort aus ~/cc-lab/grafana.pass auf dem Front-end. Das Chart bringt Dashboards für Kubernetes mit, etwa «Kubernetes / Compute Resources / Namespace (Pods)». Dazu kommt ein Dashboard des Plattform-Teams aus Git, monitoring/config/dashboard-wolkenwerk.yaml. Es zeigt Hosts und VMs von OpenNebula, Verkehr, Fehler und Antwortzeit des Webshops, CPU pro Namespace und den Speicher der Mitglieder gegen ihre Quota. Grafana lädt jede ConfigMap mit dem Label grafana_dashboard von selbst.

Das Dashboard Wolkenwerk platform in Grafana während eines Lasttests. Oben zählen Kacheln die Hosts und VMs in OpenNebula, die Anfragen pro Sekunde und den Anteil der Fehler des Webshops. Darunter stehen der vergebene Arbeitsspeicher der Knoten und die CPU der k3s-VMs, wie libvirt sie sieht, ganz unten die Anfragen nach Statuscode und die Antwortzeit.

Für ein eigenes Panel kopierst du in monitoring/config/dashboard-wolkenwerk.yaml ein bestehendes Panel. Gib ihm einen eigenen title, deine Abfrage in expr und einen freien Platz in gridPos, etwa unter den bestehenden Panels mit "y": 28. Die Panels der Datei haben keine id, auch dein Panel braucht keine. Pushe danach die Änderung. Ein Panel, das du in Grafana entworfen hast, findest du als JSON im Menü des Panels unter «Inspect» und «Panel JSON». Die Felder eines Dashboards und seiner Panels beschreibt das JSON-Modell von Grafana 13.3.

Aufgabe Lab

Rolle den Stack aus, installiere die Exporter und zeige in Prometheus, dass alle Ziele up sind. Öffne das Dashboard «Wolkenwerk platform» und erzeuge mit curl Verkehr auf den Webshop.

Lösung

kubectl -n argocd get applications zeigt monitoring und monitoring-config als Synced und Healthy. Unter Targets stehen die Jobs opennebula mit einem Ziel, opennebula-hosts mit drei, opennebula-libvirt mit zwei und webshop mit einem Ziel pro Pod. Nach einigen curl http://<adresse-des-servers>/ zeigt das Dashboard Anfragen mit dem Code 200.

Aufgabe Eigene Abfrage

Die Gemeinde Hinterbüel will wissen, wie viele VMs gerade in OpenNebula laufen und wie viel CPU sie insgesamt beanspruchen. Schreibe die Abfragen.

Lösung

count(opennebula_vm_state == 3) zählt die VMs im Zustand aktiv. Welche Zahl für welchen Zustand steht, beschreibt der Exporter im Hilfetext der Metrik. sum(opennebula_host_cpu_usage_ratio) zählt die CPU zusammen, die OpenNebula auf allen Knoten an VMs vergeben hat. Wer statt der Zuteilung die echte Last will, nimmt sum(rate(opennebula_libvirt_cpu_seconds_total[5m])) von den Knoten.

Aufgabe Sättigung

Welche Abfrage hätte dir in Kapitel 4.2 gezeigt, dass der Cluster neben den VMs aus Teil 2 und 3 keinen Platz hat?

Lösung

opennebula_host_mem_total_bytes - opennebula_host_mem_usage_bytes zeigt pro Knoten den Speicher, den OpenNebula noch vergeben kann. Mit den Webservern, den VMs der Praxis und dem Dienst der Treuhand lag er unter den 6 GB, die eine VM des Clusters braucht.

Texte und Abbildungen stehen unter CC BY-SA 4.0 und der Code unter der MIT-Lizenz. Die Quellen liegen auf GitHub.