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:
- OpenBao schreibt periodisch und ereignisbasiert Labels auf seinen eigenen Pod (z. B.
openbao-active: "true"). - Kubernetes RBAC erlaubt dem ServiceAccount des Pods, sein eigenes Pod-Objekt zu
patchen. - Ein
Servicemitselector: openbao-active=truematcht 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_NAMESPACEBAO_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:
| Label | Werte | Bedeutung |
|---|---|---|
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-version | z. 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
selectorenthältopenbao-active: "true"— er matcht damit immer genau einen Pod im Cluster. publishNotReadyAddresses: falsesorgt 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-0neu gestartet wird undopenbao-1Leader 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,patchaufpods). - Downward API wird im StatefulSet konfiguriert, so dass
BAO_K8S_NAMESPACEundBAO_K8S_POD_NAMEin jedem Pod gesetzt sind. - Das generierte
bao.hclenthältservice_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-activeverifizieren. - Labels manuell überschreiben: Wer per
kubectl label pod openbao-0 openbao-active=trueeingreift, 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: trueam Active-Service: Hebelt den Sealed-Schutz aus, weil dann auch nicht-bereite Pods Traffic bekommen können. Default istfalseund sollte so bleiben.- Auto-Unseal nicht aktiviert: Bei Pod-Restart bleibt der neue Pod sealed (
openbao-sealed=true), dasopenbao-active-Label kann nicht auftruegehen, 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.
Related pages
- 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=truebedeutet 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