Blog

Lokale GPU-Cluster-Überwachung für LLM-Hosting mit Prometheus & Grafana auf Kubernetes

Jetze Schuurmans

Aktualisiert Oktober 14, 2025
9 Minuten

Selbstgehostetes Prometheus und Grafana

Wenn Sie große Sprachmodelle (LLMs) auf GPU-Clustern einsetzen, ist ein detaillierter Einblick in die Leistung Ihres Systems entscheidend. Ein lokaler Überwachungsstack ist für dieses Szenario ideal, insbesondere wenn Sie mit sensiblen Anwendungen arbeiten, bei denen Sie nicht möchten, dass Protokollierungs- und Überwachungsdaten über das Internet an Server von Drittanbietern gesendet werden.

Eine beliebte Kombination von Open-Source-Tools für Observability ist Prometheus und Grafana. Prometheus eignet sich hervorragend zum Sammeln, Speichern und Abfragen von Metriken eines Systems. Grafana kann verwendet werden, um leistungsstarke Dashboards zur Visualisierung dieser Metriken zu erstellen. Dieser Blogbeitrag beschreibt, wie wir beide für einen lokalen Kubernetes-Cluster eingesetzt haben.

Konfiguration

Dieser Abschnitt beschreibt, wie Sie Prometheus und Grafana mit Helm und Kustomize einsetzen. Prometheus kann mit Community Helm Charts bereitgestellt werden. Grafana hingegen bietet offizielle Helm Charts. In diesem Blog wird erklärt, wie und warum Sie diese Charts ändern können, um Prometheus und Grafana einzusetzen.

Wir beginnen mit der Erstellung von zwei Ordnern, einen für jede Anwendung: prometheus/ und grafana/.

Prometheus

Im Ordner prometheus/ erstellen wir eine Datei zum Anpassen der Community Helm Charts, prometheus/kustomize.yml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: prometheus

helmCharts:
  - name: prometheus
    releaseName: prometheus
    version: "27.0"
    repo: https://prometheus-community.github.io/helm-charts/

Namespace

Um den Namensraum zu erstellen, wird unter dem Ordner prometheus/resources/ eine Datei namens namespace.yml erstellt:

apiVersion: v1
kind: Namespace
metadata:
  name: prometheus

Darauf wird in der prometheus/kustomize.yml verwiesen:

...
resources:
  - resources/namespace.yml

URL

Da das Prometheus Helm-Diagramm prometheus-server als Standarddienstnamen verwendet, können Sie intern über http://prometheus-server.prometheus.svc.cluster.local darauf zugreifen.

Grafana

Im Ordner grafana/ wird eine Kustomize-Datei für Grafana erstellt, grafana/kustomize.yml. Hier verweisen wir auf die offiziellen Helm Charts:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: grafana

helmCharts:
  - name: grafana
    releaseName: grafana
    version: "8.13.1"
    repo: https://grafana.github.io/helm-charts

Namespace

Um einen Namensraum zu erstellen, wird unter dem Ordner grafana/resources/ eine Datei namens namespace.yml erstellt:

apiVersion: v1
kind: Namespace
metadata:
  name: grafana

Auf diese Datei wird in der Datei grafana/kustomization.yml verwiesen:

...
resources:
  - resources/namespace.yml

Geheimnisse

Grafana erfordert einen Admin-Benutzer und ein Passwort, um konfiguriert zu werden. Natürlich wollen wir nicht, dass diese sensiblen Informationen in unserer Git-Historie auftauchen, also verwenden wir Sealed Secrets, um verschlüsselte Werte zu erstellen. Stellen Sie zunächst sicher, dass Sealed Secrets auf Ihrem Server und auf Ihrem lokalen Gerät installiert ist:

brew install kubeseal yq

Führen Sie dann den folgenden Befehl aus:

kubectl create secret generic admin-password --namespace grafana --dry-run=client -o json --from-literal=username=<your_username_value> --from-literal=password=<your_password_value> | kubeseal --controller-namespace sealed-secrets --controller-name sealed-secrets | yq -p json

Fügen Sie das Ergebnis in grafana/resources/secrets.yml ein:

kind: SealedSecret
apiVersion: bitnami.com/v1alpha1
metadata:
  name: admin-password
  namespace: grafana
  creationTimestamp: null
spec:
  template:
    metadata:
      name: admin-password
      namespace: grafana
      creationTimestamp: null
  encryptedData:
    password: ...
    username: ...

Beachten Sie, dass das Passwort und der Benutzername jetzt verschlüsselte Geheimnisse sind.

Dann verweisen Sie auf die Geheimnisse in grafana/kustomization.yml:

helmCharts:
    ...
    valuesInline:
      admin:
        existingSecret: admin-password
        userKey: username
        passwordKey: password

resources:
...
- resources/secrets.yml

Konfigurieren Sie die Datenquelle Prometheus

Um die Daten von Prometheus zu übernehmen, konfigurieren Sie Prometheus als Datenquelle in grafana/kustomization.yml:

...
helmCharts:
  ...
    valuesInline:
    ...
      datasources:
        datasources.yml:
          apiVersion: 1
          datasources:
            - name: Prometheus
              type: prometheus
              url: http://prometheus-server.prometheus.svc.cluster.local

Eindringen

Um innerhalb des Clusters auf Grafana zuzugreifen, aktivieren Sie den Ingress und konfigurieren den Host:

helmCharts:
  ...
    valuesInline:
    ...
      ingress:
        enabled: true
        hosts:
          - grafana.my-server.internal

Dauerhafte Speicherung

Um Daten (wie z.B. Dashboards, Benutzerdaten und -konfigurationen, Warnmeldungen und Snapshots) nach einem Neustart der Pods zu erhalten, konfigurieren Sie einen persistenten Speicher. Erstellen Sie zunächst ein Persistent Volume, indem Sie eine Datei grafana/resources/pv.yml erstellen:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: grafana-pv
  labels:
    model: grafana-pv
spec:
  storageClassName: manual
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: "/mnt/data/grafana"

Der Persistent Volume Claim wird in Grafana's Helm Chart grafana/kustomization.yml konfiguriert:

helmCharts:
  ...
    valuesInline:
    ...
      persistence:
        enabled: true
        storageClassName: manual
        volumeName: grafana-pv
        accessModes:
          - ReadWriteOnce
        size: 1Gi

Beide sind gemäß den minimalen Hardwareanforderungen von Grafana auf 1Gi eingestellt.

Vollständiges Beispiel

Zusammengefasst ergibt dies die folgenden Dateien:

prometheus/
├── kustomization.yml
└── resources
    └── namespace.yml
grafana/
├── kustomization.yml
└── resources
    ├── namespace.yml
    ├── pv.yml
    └── secrets.yml

Prometheus

prometheus/kustomization.yml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: prometheus

helmCharts:
  - name: prometheus
    releaseName: prometheus
    version: "27.0"
    repo: https://prometheus-community.github.io/helm-charts/

resources:
  - resources/namespace.yml

Ressourcen

prometheus/resources/namespace.yml:

apiVersion: v1
kind: Namespace
metadata:
  name: prometheus

Grafana

grafana/kustomization.yml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: grafana

helmCharts:
  - name: grafana
    releaseName: grafana
    version: "8.13.1"
    repo: https://grafana.github.io/helm-charts
    valuesInline:
      ingress:
        enabled: true
        hosts:
          - grafana.my-server.internal
      admin:
        existingSecret: admin-password
        userKey: username
        passwordKey: password
      datasources:
        datasources.yaml:
          apiVersion: 1
          datasources:
            - name: Prometheus
              type: prometheus
              url: http://prometheus-server.prometheus.svc.cluster.local
      persistence:
        enabled: true
        storageClassName: manual
        volumeName: grafana-pv
        accessModes:
          - ReadWriteOnce
        size: 1Gi

resources:
  - resources/namespace.yml
  - resources/secrets.yml
  - resources/pv.yml

Ressourcen

grafana/resources/namespace.yml:

apiVersion: v1
kind: Namespace
metadata:
  name: grafana

grafana/resources/pv.yml:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: grafana-pv
  labels:
    model: grafana-pv
spec:
  storageClassName: manual
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: "/mnt/data/grafana"

grafana/resources/secrets.yml:

kind: SealedSecret
apiVersion: bitnami.com/v1alpha1
metadata:
  name: admin-password
  namespace: grafana
  creationTimestamp: null
spec:
  template:
    metadata:
      name: admin-password
      namespace: grafana
      creationTimestamp: null
  encryptedData:
    password: ...
    username: ...

Zugang und Test

In diesem Abschnitt zeigen wir Ihnen, wie Sie den Einsatz von Prometheus und Grafana testen und nutzen können.

Endpunkte für Prometheus

Prometheus funktioniert, indem es Metriken von Endpunkten abruft, die Anwendungen bereitstellen. Woher weiß Prometheus, wo es scrapen soll?

Einige Anwendungen stellen standardmäßig Metadaten über ihre metrischen Endpunkte in ihren Helm Charts bereit. Nehmen Sie zum Beispiel den gpu-operator von NVIDIA. Dieser setzt den Data Center GPU Manager (DCGM) Exporter oder kurz dcgm-exporter ein. Dieser stellt GPU-Metriken mithilfe der NVIDIA Management Library (NVML) zur Verfügung. In seinem Helm Chart werden podAnnotations und additionalLabels für Prometheus festgelegt.

Für andere Anwendungen müssen Sie dies selbst konfigurieren. Wir verwenden vLLM, um große Sprachmodelle (LLMs) auf unserem GPU-Cluster einzusetzen. Damit Prometheus weiß, wo vLLM-Metriken abgerufen werden sollen, muss das Helm Chart von vLLM mit den folgenden Metadaten gepatcht werden:

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: vllm
  name: vllm-deployment-router
spec:
  template:
    metadata:
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/path: "/metrics"
        prometheus.io/port: "8000"

Eine Alternative wäre, auf der Seite von Prometheus zu konfigurieren, wo das Scrapen stattfinden soll. Für vLLM müsste dies zu Ihrer Prometheus-Konfiguration hinzugefügt werden.

Wir haben uns für das Hinzufügen von Metadaten auf der Anwendungsseite entschieden, da es so einfach ist, Scraping hinzuzufügen, wenn eine neue Anwendung bereitgestellt wird. Da die Konfigurationen zentral in Bezug auf die Anwendungen gespeichert werden.

Alternativ können Sie die testFramework auch erweitern, zum Beispiel mit Lasttests über k6.

Erweiterungen

Um diese Einrichtung zu professionalisieren, können Sie die Prometheus- und Grafana-Server um Folgendes erweitern:

Referenzen

Verfasst von

Jetze Schuurmans

Machine Learning Engineer

Jetze is a well-rounded Machine Learning Engineer, who is as comfortable solving Data Science use cases as he is productionizing them in the cloud. His expertise includes: AI4Science, MLOps, and GenAI. As a researcher, he has published papers on: Computer Vision and Natural Language Processing and Machine Learning in general.

Contact

Let’s discuss how we can support your journey.