secure k8 s

Kubernetes service registration

Summary: Die service_registration "kubernetes"-Stanza lässt OpenBao seinen eigenen Pod in Kubernetes mit Status-Labels markieren (aktiv, sealed, initialisiert, Version). Damit kann ein normaler Kubernetes Service per Selector exakt den aktuell aktiven Pod ansprechen — ohne dass der Client wissen muss, welcher der drei Raft-Knoten gerade Leader ist.

Sources: raw/docs/configuration/service-registration/index.md, raw/docs/configuration/service-registration/kubernetes.md, raw/docs/platform/k8s/helm/terraform.md, raw/docs/platform/k8s/helm/run.md.

Last updated: 2026-05-20


1 — Das Problem in einem Satz

In einer HA-Installation gibt es genau einen aktiven Knoten (Leader) und mehrere Standbys. Clients dürfen Schreib-Requests aber nur an den Leader schicken — ein Standby kann zwar weiterleiten (Request Forwarding), das kostet aber einen zusätzlichen Hop und funktioniert nicht, wenn der Pod gerade sealed oder nicht initialisiert ist.

Frage: Wie findet ein Client zuverlässig den aktiven Pod, wenn die drei OpenBao-Pods (openbao-0, openbao-1, openbao-2) sich jederzeit gegenseitig die Leader-Rolle abnehmen können?

Antwort: OpenBao schreibt seinen aktuellen Status in Pod-Labels, und ein normaler Kubernetes Service selektiert über diese Labels. Das nennt sich service registration (source: raw/docs/configuration/service-registration/kubernetes.md).

2 — Die Grundidee

Der Mechanismus besteht aus drei Bausteinen:

  1. OpenBao schreibt periodisch und ereignisbasiert Labels auf seinen eigenen Pod (z. B. openbao-active: "true").
  2. Kubernetes RBAC erlaubt dem ServiceAccount des Pods, sein eigenes Pod-Objekt zu patchen.
  3. Ein Service mit selector: openbao-active=true matcht dann immer genau den Leader-Pod und routet Traffic dorthin.

Das ist konzeptionell dasselbe wie ein Health-Check-getriebener Loadbalancer — nur dass die “Health”-Information nicht aus einer externen Probe kommt, sondern direkt vom Prozess selbst.

Wichtig: Service registration ist nur in HA-Mode verfügbar (source: raw/docs/configuration/service-registration/kubernetes.md). Auf einem Standalone-Single-Node ergibt sie auch keinen Sinn — dort ist der einzige Pod per Definition der aktive.

3 — Die minimale Konfiguration

Die kleinstmögliche funktionierende Stanza ist:

service_registration "kubernetes" {}

Mehr ist nicht nötig, wenn die beiden Pflichtinformationen — Namespace und Pod-Name — über Environment-Variablen kommen (source: raw/docs/configuration/service-registration/kubernetes.md):

  • BAO_K8S_NAMESPACE
  • BAO_K8S_POD_NAME

Genau so macht es das offizielle Helm-Chart: es injiziert beide Variablen über die Downward API, so dass jeder Pod automatisch weiß, wie er heißt. Konkretes Beispiel aus dem Chart (source: raw/docs/platform/k8s/helm/terraform.md):

storage "raft" {
  path = "/openbao/data"
}

service_registration "kubernetes" {}

seal "awskms" {
  region     = "us-west-2"
  kms_key_id = "alias/my-kms-key"
}

Falls man die Variablen nicht setzen kann oder will, gehen die Werte auch explizit in die Stanza:

service_registration "kubernetes" {
  namespace = "my-namespace"
  pod_name  = "my-pod-name"
}

Bei doppelter Angabe gewinnt die Environment-Variable (source: raw/docs/configuration/service-registration/index.md).

4 — Service registration vs. Storage-Backend

Die Stanza ist absichtlich getrennt vom storage-Stanza. Hintergrund (source: raw/docs/configuration/service-registration/index.md): Manche Storage-Backends (klassisch Consul) erledigen Service-Discovery selbst. Wenn man aber Raft als Storage nimmt — was im K8s-Standardpfad der Fall ist — übernimmt Raft nur die Persistenz; Service-Discovery muss man separat aktivieren.

Das typische Setup auf Kubernetes sieht deshalb so aus:

storage "raft" {
  path    = "/openbao/data"
  node_id = "raft_node_1"
}

service_registration "kubernetes" {
  namespace = "my-namespace"
  pod_name  = "my-pod-name"
}

storage regelt Wo liegen die Daten?, service_registration regelt Wer ist gerade der Leader?.

5 — RBAC: Pod-Labels schreiben dürfen

Damit OpenBao seinen eigenen Pod beschriften darf, braucht der ServiceAccount der OpenBao-Pods eine Role mit drei Verben auf pods: get, update, patch (source: raw/docs/configuration/service-registration/kubernetes.md).

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: mynamespace
  name: openbao-service-account
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "update", "patch"]

Diese Role wird per RoleBinding an den ServiceAccount der OpenBao-StatefulSet-Pods gebunden. Das offizielle Helm-Chart legt sie automatisch an — selbst aufgesetzt muss man daran denken.

Symptom bei fehlender Berechtigung: Die Pods starten zwar, aber alle bleiben ohne openbao-*-Labels. Im Pod-Log sehen die Fehlermeldungen typisch aus wie pods "openbao-0" is forbidden: User "system:serviceaccount:..." cannot patch resource "pods". Ein kubectl describe pod openbao-0 zeigt die fehlenden Labels.

6 — Welche Labels werden gesetzt?

Sobald die Konfiguration greift, tragen die Pods diese Labels (source: raw/docs/configuration/service-registration/kubernetes.md):

apiVersion: v1
kind: Pod
metadata:
  name: openbao
  labels:
    openbao-active: "false"
    openbao-initialized: "true"
    openbao-perf-standby: "false"
    openbao-sealed: "false"
    openbao-version: 2.0.1

Bedeutung der einzelnen Labels:

LabelWerteBedeutung
openbao-active"true" / "false"true = dieser Pod ist gerade Leader. false = Standby. Wird bei jedem Leader-Wechsel dynamisch aktualisiert.
openbao-initialized"true" / "false"true = OpenBao wurde initialisiert (bao operator init ist gelaufen). false = der Cluster ist noch frisch.
openbao-sealed"true" / "false"true = Pod ist [[seal-unseal
openbao-perf-standby"true" / "false"Performance-Standby (Read-Replica-Pfad). In OpenBao-OSS aktuell immer false; das Label existiert aus historischen Gründen.
openbao-versionz. B. "2.0.1"Die OpenBao-Version dieses Pods. Wechselt nur über einen Pod-Restart mit neuem Image.

Beim Shutdown wechselt das Sealed-Label kurz vor dem Beenden auf true und initialized auf false — der Pod entfernt sich also explizit aus aktiven Selectors, bevor er wegfällt (source: raw/docs/configuration/service-registration/kubernetes.md).

labels:
  openbao-active: "false"
  openbao-initialized: "false"
  openbao-sealed: "true"
  openbao-version: 2.0.1

7 — Das wichtigste Anwendungsmuster: Active-Service

Der Hauptgrund, warum man service registration einschaltet, ist genau dieser eine Service:

apiVersion: v1
kind: Service
metadata:
  name: openbao-active
  labels:
    app.kubernetes.io/instance: openbao
    app.kubernetes.io/name: openbao
spec:
  type: ClusterIP
  publishNotReadyAddresses: false
  selector:
    app.kubernetes.io/instance: openbao
    app.kubernetes.io/name: openbao
    component: server
    openbao-active: "true"
  ports:
  - name: http
    port: 8200
    protocol: TCP
    targetPort: 8200
  - name: internal
    port: 8201
    protocol: TCP
    targetPort: 8201

(angelehnt an: raw/docs/configuration/service-registration/kubernetes.md)

Was hier passiert:

  • Der selector enthält openbao-active: "true" — er matcht damit immer genau einen Pod im Cluster.
  • publishNotReadyAddresses: false sorgt dafür, dass Pods im “NotReady”-Zustand (z. B. weil sie gerade sealed sind oder die Readiness-Probe fehlschlägt) gar nicht erst Traffic bekommen.
  • Wechselt der Leader (etwa weil openbao-0 neu gestartet wird und openbao-1 Leader wird), aktualisiert OpenBao die Labels — Kubernetes routet automatisch um, ohne dass irgendein Client neu konfiguriert werden muss.

Clients sprechen also openbao-active:8200 an und reden dadurch immer mit dem Leader. Kein Request-Forwarding, kein verlorener Hop.

Analog kann man andere Services bauen, etwa einen reinen Standby-Service für Read-Heavy-Clients oder Monitoring (openbao-active: "false") — das ist die gleiche Mechanik mit anderem Selector-Wert.

8 — Zweites Anwendungsmuster: kontrollierte Upgrades

Mit den openbao-active- und openbao-version-Labels lassen sich Upgrades chirurgisch ausführen (source: raw/docs/configuration/service-registration/kubernetes.md, raw/docs/platform/k8s/helm/run.md). Das StatefulSet wird auf OnDelete-Update-Strategy gestellt — neue Pods entstehen erst, wenn man alte explizit löscht:

# 1. Neues Image im Chart hinterlegen
helm upgrade openbao --set='server.image.tag=1.14.0'

# 2. Welche Pods laufen noch auf der alten Version, sind aber nicht aktiv?
kubectl get pods -l openbao-active=false,openbao-version=2.0.1

# 3. Erst alle Standbys einzeln neu starten (Quorum bleibt erhalten)
kubectl delete pod -l openbao-active=false,openbao-version=2.0.1

# 4. Zum Schluss den aktiven Pod – er failt über, ein Standby übernimmt
kubectl delete pod -l openbao-active=true,openbao-version=2.0.1

Vorteil gegenüber einem naiven kubectl rollout restart:

  • Man hat zu jeder Zeit volle Kontrolle über das Quorum.
  • Die Leader-Übergabe passiert genau einmal ganz am Ende.
  • Man kann zwischen zwei Schritten verifizieren, dass der neue Standby gesund hochgekommen, unsealed und Raft-Member ist.

Siehe auch k8s-ha-setup → Upgrades und upgrading für das größere Bild.

9 — Zusammenspiel mit dem Helm-Chart

Das offizielle Helm-Chart erledigt alle drei Bausteine im HA-Mode automatisch:

  • ServiceAccount + Role + RoleBinding werden mit angelegt (Verben get, update, patch auf pods).
  • Downward API wird im StatefulSet konfiguriert, so dass BAO_K8S_NAMESPACE und BAO_K8S_POD_NAME in jedem Pod gesetzt sind.
  • Das generierte bao.hcl enthält service_registration "kubernetes" {} sobald HA aktiv ist (source: raw/docs/platform/k8s/helm/terraform.md).

Mit anderen Worten: Wer das Chart mit server.ha.enabled=true installiert, hat service registration ohne weiteres Zutun aktiv. Manuell hinzufügen muss man die Stanza nur, wenn man das StatefulSet selbst schreibt oder das Chart-server.standalone.config überschreibt — dort fehlt sie standardmäßig.

Praktischer Check nach dem helm install:

kubectl get pods -L openbao-active,openbao-sealed,openbao-version

Die -L-Flag (großgeschrieben) blendet die Werte als zusätzliche Tabellenspalten ein. Wenn nach helm install und bao operator init + unseal genau ein Pod openbao-active=true zeigt, funktioniert die Registrierung.

10 — Antipatterns und Gotchas

  • Standalone-Mode: Die Stanza tut nichts. OpenBao ignoriert sie, weil ohne HA der “active”-Begriff nicht existiert (source: raw/docs/configuration/service-registration/kubernetes.md).
  • Fehlende RBAC-Rechte: Pods starten ohne Fehler im Bootstrap, aber Labels bleiben leer. Selectors matchen dann nichts und der Active-Service hat keine Endpoints. Immer mit kubectl get pods -L openbao-active verifizieren.
  • Labels manuell überschreiben: Wer per kubectl label pod openbao-0 openbao-active=true eingreift, bekommt nur einen Moment lang das, was er will — OpenBao schreibt beim nächsten Statusupdate wieder zurück. Die Labels sind generated, nicht configured.
  • publishNotReadyAddresses: true am Active-Service: Hebelt den Sealed-Schutz aus, weil dann auch nicht-bereite Pods Traffic bekommen können. Default ist false und sollte so bleiben.
  • Auto-Unseal nicht aktiviert: Bei Pod-Restart bleibt der neue Pod sealed (openbao-sealed=true), das openbao-active-Label kann nicht auf true gehen, und der Active-Service hat keinen Endpoint mehr — bis jemand manuell unsealed. In K8s deshalb Auto-Unseal praktisch verpflichtend.
  • Cross-Cluster / Cross-Namespace-Lookup: Das Feature labelt nur den eigenen Pod. Wer aus einem anderen Namespace zugreifen will, nutzt ganz normal openbao-active.<namespace>.svc.cluster.local — die Logik bleibt cluster-weit gleich.
  • kubernetes-platform — Helm-Chart, Injector, VSO, CSI im Überblick
  • k8s-ha-setup — Schritt-für-Schritt-HA-Setup, in dem das Chart die Registrierung implizit konfiguriert
  • service-registration-reactivation — wie man die Stanza nach einem Workaround-Auskommentieren sauber wieder einschaltet (Reihenfolge: NetPol → RBAC → Downward API → Rollout)
  • high-availability — warum es überhaupt einen Leader gibt und was Request-Forwarding macht
  • raft — der Konsens-Algorithmus, der den Leader-Wechsel überhaupt erst koordiniert
  • seal-unseal — was openbao-sealed=true bedeutet und warum Auto-Unseal hier Pflicht ist
  • configuration — die service_registration-Stanza im Kontext der gesamten Server-Konfiguration
  • upgrading — Upgrade-Strategien, die diese Labels nutzen