Resource-Requests aufhören zu raten — VPA und Goldilocks im Recommend-Only-Modus

Auf vier ARM-Boards ist jedes Mebibyte gezählt — trotzdem habe ich CPU- und Memory-Requests jahrelang aus dem Bauch gesetzt. Der Vertical Pod Autoscaler beobachtet jetzt den echten Verbrauch und schreibt Empfehlungen, Goldilocks macht sie als Dashboard lesbar. Bewusst nur als Ratgeber: VPA fasst keinen einzigen Pod von selbst an.
Table of contents

Resource-Requests und -Limits habe ich in meinem Lab lange so gesetzt, wie man es eben macht: ein paar Werte aus dem Bauch, 100m/128Mi als Reflex, und dann nie wieder angefasst. Auf einer dicken Cloud ist das egal. Auf vier RK1-Boards mit endlichem ARM-Speicher ist es das nicht: Setze ich zu hoch, verschenke ich Kapazität, die kein anderer Pod mehr bekommt, weil der Scheduler den Request reserviert, nicht den Verbrauch. Setze ich zu niedrig, holt mir der OOM-Killer nachts einen Pod ab oder die CPU wird in den Boden gethrottlet. Beides hatte ich. Was mir fehlte, war kein Gefühl, sondern eine Zahl.

Der Plan: messen, empfehlen — nicht anfassen

Die Zahl liefert der Vertical Pod Autoscaler . Der VPA kann drei Dinge — und ich will bewusst nur das erste:

  1. Recommender — beobachtet den realen CPU-/Memory-Verbrauch (über den metrics-server) und schreibt pro Workload eine Empfehlung in ein VerticalPodAutoscaler-Objekt.
  2. Updater — evictet Pods, deren Requests zu weit von der Empfehlung abweichen, damit sie neu und „richtig" geschedult werden.
  3. Admission Controller — mutiert beim Erzeugen eines Pods dessen Requests live auf den empfohlenen Wert.

In einem Homelab will ich keinen Prozess, der mir nachts Pods evictet oder meine im Git deklarierten Requests hinter meinem Rücken überschreibt. Mein Flux -Repository ist die einzige Wahrheit — und das soll so bleiben. Also läuft der VPA strikt als Recommender, Updater und Admission Controller bleiben aus:

1values:
2  recommender:
3    enabled: true
4  updater:
5    enabled: false
6  admissionController:
7    enabled: false
Recommend-Only heißt: Der VPA schreibt nie ins Cluster, er liest nur und legt seine Empfehlung in ein CRD. Was damit passiert, entscheide ich — von Hand, im Git. Der Autoscaler ist hier ein Ratgeber, kein Akteur.

Goldilocks macht die Empfehlung lesbar

Ein VerticalPodAutoscaler-Objekt pro Deployment von Hand anzulegen und die Empfehlung per kubectl describe herauszuklauben, hält niemand durch. Das nimmt mir Goldilocks von Fairwinds ab: Es legt für jeden Workload automatisch ein VPA-Objekt an und stellt die Empfehlungen in einem Dashboard gegenüber — aktueller Request, empfohlener Wert, getrennt nach den QoS-Zielen Guaranteed und Burstable. Ich schalte es bewusst für alle Namespaces scharf, statt jeden einzeln per Label opt-in zu machen:

1values:
2  dashboard:
3    enabled: true
4  controller:
5    flags:
6      on-by-default: true   # Empfehlungen für ALLE Namespaces, ohne opt-in-Label
7  vpa:
8    enabled: false          # VPA kommt aus der eigenen fairwinds/vpa-HelmRelease

Die beiden Stücke sind absichtlich getrennt: Den VPA installiere ich über eine eigene fairwinds/vpa-HelmRelease (eben im Recommend-Only-Schnitt), und Goldilocks soll seinen eigenen VPA nicht mitbringen — sonst hätte ich zwei Installationen, die sich um dieselben CRDs streiten. Die Flux-Kustomization von Goldilocks hängt deshalb per dependsOn am VPA, damit die CRDs und der Recommender stehen, bevor Goldilocks die ersten VPA-Objekte erzeugt:

1dependsOn:
2  # Braucht die VPA-CRDs + Recommender, bevor es VPA-Objekte anlegt.
3  - name: vpa

So sieht der Fluss aus — und er endet bewusst bei mir, nicht beim Cluster:

flowchart LR MS["metrics-server<br/>(realer Verbrauch)"] --> REC["VPA Recommender"] REC --> VPA["VerticalPodAutoscaler<br/>(Empfehlung im CRD)"] VPA --> GD["Goldilocks<br/>Dashboard"] GD --> ME["ich lese ab"] ME --> GIT["requests/limits<br/>im Git anpassen"] GIT --> FLUX["Flux reconciled"] FLUX --> POD["Pod rollt mit<br/>passenden Requests"]

Der Updater würde den Bogen von VPA direkt zurück zum Pod schließen. Genau den lasse ich offen — die Schleife läuft über Git, nicht über eine Eviction.

Das Dashboard ist intern — und trotzdem hinter OIDC

Goldilocks bringt keine eigene Authentifizierung mit. Ein Dashboard, das jeden Resource-Verbrauch jedes Workloads im Cluster ausbreitet, will ich aber nicht offen ins interne Netz hängen. Also bekommt es dieselbe Behandlung wie ZeroClaw , umami oder der CI-Broker : eine HTTPRoute auf dem internen Envoy-Gateway , davor eine SecurityPolicy, die den OIDC-Flow gegen Pocket ID am Gateway terminiert:

1oidc:
2  provider:
3    issuer: "https://auth.${EXTERNAL_DOMAIN}"
4  clientID: "${ENVOY_GATEWAY_OIDC_CLIENT_ID}"
5  clientSecret:
6    name: goldilocks-oidc-secret
7  redirectURL: "https://goldilocks.${INTERNAL_DOMAIN}/oauth2/callback"

Envoy übernimmt den Login, Goldilocks sieht nur noch eine bereits authentifizierte Anfrage — die App muss von OIDC nichts wissen. Der Callback-Pfad muss als Redirect-URI am geteilten OIDC-Client in Pocket ID registriert sein, sonst dreht der Login leer. Das ist das immer gleiche Muster im Lab: Auth gehört ans Gateway, nicht in jede einzelne Anwendung.

Was bleibt

Right-Sizing ist keine einmalige Aktion, sondern eine wiederkehrende Pflicht — und genau die hatte vorher kein Werkzeug. Jetzt habe ich eine ehrliche Zahl statt eines Bauchgefühls, und ich behalte die Kontrolle: Der VPA sammelt, Goldilocks zeigt, ich entscheide, Git rollt. Kein Pod wird mir unter den Händen evictet, kein Request hinter meinem Rücken umgeschrieben. Für eine dicht gepackte Vier-Board-Maschine ist das die richtige Dosis Automatik — sie nimmt mir das Raten ab, aber nicht das letzte Wort.