Kubernetes-Guide

Zielgruppe: DevOps. Phase 7. Setzt einen laufenden Kubernetes-Cluster voraus (siehe "Cluster besorgen" unten) und die neun Container-Images aus library-platform/Dockerfile (mit podman build gebaut).

Scope dieser Phase (bewusst abgegrenzt)

Das Helm-Chart deployt nur die Anwendungsschicht: die sechs Fachservices, api-gateway und config-server - acht Services, NICHT neun. discovery-server fehlt bewusst (siehe ADR-0007): im Kubernetes-Profil uebernimmt Kubernetes selbst die Service-Discovery per DNS, eine Eureka-Registry waere dort ein sinnloser zusaetzlicher Single-Point-of-Failure.

Infrastruktur (Postgres, Kafka, RabbitMQ, Redis, Keycloak, Prometheus, Grafana, Zipkin) wird NICHT durch dieses Chart bereitgestellt. In einem echten Cluster kaemen dafuer etablierte Helm-Charts (Bitnami, das offizielle Prometheus-Stack-Chart, ein Managed-Postgres-Angebot des Cloud-Anbieters, ...) oder verwaltete Cloud-Dienste zum Einsatz - das selbst nachzubauen waere hier reine Infrastruktur-Fleissarbeit ohne Bezug zur eigentlichen Anwendung, die dieses Lernrepo vermitteln will. Fuer eine lokale End-to-End-Vertiefung: die Infrastruktur laesst sich trotzdem IM selben Cluster erreichbar machen, siehe "Infrastruktur bereitstellen" unten.

Für einen produktionsnahen, dauerhaften Cluster (CRC/OKD oder ein echter Cloud-Cluster) bleibt dieser Guide der richtige Ort - siehe "Bekannte Einschränkung" unten für das CRC-Versuchsprotokoll. Für die Jenkins-Test-Stufe reicht dagegen ein einfacher, wegwerfbarer lokaler Cluster: kind-cluster.md beschreibt genau diesen (kleineren, bewusst getrennten) Weg, der dort inzwischen tatsächlich funktioniert - beide Dokus bewusst nicht vermischt, weil sie unterschiedliche Ziele verfolgen (Lern-/Test-Cluster hier vs. produktionsnaher Cluster-Versuch).

Voraussetzungen

Chart-Uebersicht

infra/helm/
├── library-service/          <- EIN generisches Chart fuer JEDEN der acht Services
│   ├── Chart.yaml
│   ├── values.yaml           <- Defaults (bewusst "kaputt", siehe Kommentar dort)
│   └── templates/
│       ├── deployment.yaml
│       ├── service.yaml
│       ├── hpa.yaml          <- nur gerendert, wenn autoscaling.enabled=true
│       ├── ingress.yaml      <- nur gerendert, wenn ingress.enabled=true (nur api-gateway)
│       ├── serviceaccount.yaml
│       └── _helpers.tpl
└── values/
    ├── catalog-service-values.yaml
    ├── member-service-values.yaml
    ├── lending-service-values.yaml
    ├── reservation-service-values.yaml
    ├── fine-service-values.yaml
    ├── notification-service-values.yaml
    ├── api-gateway-values.yaml
    └── config-server-values.yaml

Warum EIN generisches Chart statt acht eigener Charts? Die acht Services unterscheiden sich ausschliesslich in Werten (Image, Port, Umgebungsvariablen, Ressourcen-Limits, ob Ingress/HPA aktiv ist) - nicht in der STRUKTUR ihrer Kubernetes-Ressourcen (jeder ist ein zustandsloses Deployment + Service, optional HPA/Ingress). Ein Chart pro Service wuerde denselben Deployment-/Service-Template-Code achtmal duplizieren - genau das Muster, das dieses Projekt an anderer Stelle (Config Server, common-*-Module) konsequent vermeidet.

Deployen

kubectl create namespace library

# Vorher: Secrets fuer DB-Zugangsdaten anlegen (siehe Kommentar in jeder values-Datei,
# "secretEnvFrom") - hier mit dem lokalen Compose-Passwort als Platzhalter:
for svc in catalog-service member-service lending-service reservation-service fine-service notification-service; do
  kubectl create secret generic ${svc}-db-credentials -n library \
    --from-literal=SPRING_DATASOURCE_USERNAME=library \
    --from-literal=SPRING_DATASOURCE_PASSWORD=library
done

# Jeder Service ist ein EIGENES Helm-Release (nicht ein einziges Umbrella-Release fuer alle acht) -
# das erlaubt, einzelne Services unabhaengig voneinander upzugraden/zurueckzurollen, genau wie es
# bei unabhaengig versionierten Microservices auch sein sollte.
for svc in catalog-service member-service lending-service reservation-service fine-service notification-service api-gateway config-server; do
  helm install $svc infra/helm/library-service -f infra/helm/values/${svc}-values.yaml -n library
done

kubectl get pods,svc -n library

Ohne Cluster (nur die Templates pruefen, siehe auch infra/ci/README.md):

helm lint infra/helm/library-service -f infra/helm/values/catalog-service-values.yaml
helm template catalog-service infra/helm/library-service -f infra/helm/values/catalog-service-values.yaml -n library

Infrastruktur bereitstellen (fuer eine echte End-to-End-Vertiefung)

Damit die obigen helm install-Befehle tatsaechlich funktionierende Pods ergeben (nicht nur "CrashLoopBackOff", weil z. B. kein Postgres erreichbar ist), braucht der Cluster zusaetzlich Postgres/Kafka/RabbitMQ/Redis/Keycloak unter GENAU den Servicenamen, die die values-Dateien referenzieren (postgres, kafka, rabbitmq, redis, keycloak - bewusst identisch mit den Compose-Servicenamen, siehe ADR-0007-Kommentar in library-service/templates/service.yaml). Fuer eine lokale Vertiefung eignet sich z. B. das Bitnami-Postgres-Chart:

helm install postgres oci://registry-1.docker.io/bitnamicharts/postgresql -n library \
  --set auth.username=library --set auth.password=library --set auth.database=library

(entsprechend fuer Kafka/RabbitMQ/Redis/Keycloak) - das vollstaendig vorzukonfigurieren ist bewusst eine Vertiefungsaufgabe, kein Teil dieses Kern-Repos (siehe "Scope dieser Phase" oben).

Bekannte Einschraenkung dieser Entwicklungsumgebung

Alles oben Beschriebene wurde bis "helm lint/helm template erfolgreich" verifiziert (inklusive eines echten podman build + podman run fuer das generische library-platform/Dockerfile, siehe den Stolperstein in den Coding Guidelines), aber NICHT gegen einen echten, stabil laufenden Kubernetes-Cluster - trotz eines konkreten, mehrstuendigen Versuchs mit CodeReady Containers (CRC, Red Hats Single-VM-OpenShift/OKD fuer lokale Entwicklung). Der Vollstaendigkeit halber und weil das Scheitern selbst lehrreich ist (siehe die Stolpersteine in diesem Projekt allgemein - ein dokumentierter Fehlschlag ist mehr wert als eine verschwiegene Luecke): der genaue Ablauf.

Versuchsprotokoll: CRC (OKD) auf der Referenzmaschine

Ausgangslage: CRC 2.61.0 war bereits installiert (crc version warnte direkt beim ersten Aufruf, dass 2.62.0 verfuegbar ist), aber die VM war noch nie erfolgreich hochgefahren.

  1. Erster crc start: scheiterte mit Failed to renew TLS certificates: ... No Pending CSR with signerName kubernetes.io/kubelet-serving. Der im gecachten OKD-Bundle (crc_okd_hyperv_4.21.0-okd-scos.8_amd64.crcbundle) eingebettete Kubelet-Client-Zertifikat war zum Zeitpunkt des ersten Starts bereits abgelaufen - CRCs eingebauter Auto-Renewal-Mechanismus kam nicht rechtzeitig zum Zug (Henne-Ei-Problem: die erneuerte Zertifikatsanfrage braucht einen funktionierenden API-Server, der aber selbst noch auf abgelaufenen Zertifikaten laeuft).
  2. crc delete -f + erneuter crc start: derselbe Fehler, identisch reproduzierbar - ein klares Indiz, dass das Problem im GECACHTEN BUNDLE selbst steckt (im Bundle eingebrannte Zertifikate mit festem Ablaufdatum), nicht in einer bestimmten VM-Instanz. Ein crc delete entfernt nur die Instanz, nicht den Cache - die neu erzeugte Instanz erbt exakt dieselben abgelaufenen Zertifikate.
  3. Ein spaeterer, manueller Neustart (durch den Nutzer) gelang tatsaechlich - vermutlich griff die interne Selbstheilung diesmal rechtzeitig. Der Cluster meldete sich als Running mit allen Cluster-Operatoren Available=True.
  4. Ressourcen-Konflikt: die Standard-Konfiguration (6 CPU / 14 GB RAM fuer die CRC-VM) gleichzeitig mit der laufenden Compose-Infrastruktur (Postgres, Kafka, Keycloak, ...) ueberforderte die Maschine (32 GB RAM insgesamt) - nur noch 3,7 GB frei, die CRC-VM stuerzte unter dem Druck ab (crc status meldete danach "crc does not seem to be setup correctly", obwohl Hyper-V die VM selbst als sauber Off auswies - ein Zustands-Mismatch zwischen CRCs eigenem State-Tracking und der tatsaechlichen Hyper-V-Realitaet).
  5. Entscheidung: Compose-Infrastruktur stoppen, podman machine stop + wsl --shutdown (die WSL2-VM haelt belegten Arbeitsspeicher auch nach dem Stoppen aller Container - erst ein vollstaendiges VM-Shutdown gibt ihn frei), CRC-RAM auf das von CRC selbst erzwungene Minimum (10752 MiB) reduziert, CPU probeweise auf 4 reduziert.
  6. Mit 4 CPU: die VM startete (diesmal OHNE erneuten Zertifikatsfehler - bestaetigt, dass Punkt 3 bereits eine funktionierende, nicht mehr abgelaufene Zertifikatsbasis erzeugt hatte), aber der API-Server blieb dauerhaft Unreachable/gab 503-Fehler zurueck, obwohl die VM aktiv CPU verbrauchte (27 % → 37 % ueber mehrere Minuten) - dafuer spricht eher "zu wenig CPU fuer stabile Konvergenz" als "noch am Hochfahren".
  7. Mit wieder 6 CPU (sauberer crc stop + crc start): derselbe Fehlschlag - dieses Mal sogar mit sinkender statt steigender CPU-Auslastung (37 % → 7 %, die VM wurde zunehmend INAKTIV statt aktiver), waehrend der API-Port (443) Verbindungen weiterhin aktiv zurueckwies (wsarecv: An existing connection was forcibly closed by the remote host). Das widerlegt die reine Ressourcen-Theorie aus Schritt 6 - mit ausreichend CPU haette der Cluster eigentlich konvergieren muessen.
  8. Tieferer Diagnoseversuch: crc ssh (der uebliche Weg, um direkt auf der VM den kube-apiserver-Prozess/-Pod zu inspizieren) existiert in CRC 2.61.0 schlicht NICHT als Subcommand (unknown command "ssh" for "crc") - erst in neueren CRC-Versionen verfuegbar. Das SSH-Setup manuell nachzubauen (Hyper-V-vsock-basiert, kein einfacher TCP-Port) haette den Rahmen einer lokalen Lernumgebungs-Diagnose gesprengt.

Fazit: Die Kombination aus (a) einem abgelaufenen Bundle-Zertifikat beim allerersten Start, (b) dem fehlenden crc ssh-Diagnosewerkzeug und (c) der Tatsache, dass CRC selbst bei JEDEM Aufruf auf eine neuere Version (2.62.0) hinwies, zeigt in dieselbe Richtung: die installierte CRC-Version (2.61.0) ist fuer diese Maschine/Windows-Konfiguration zu alt, um zuverlaessig zu laufen. Ein Upgrade auf 2.62.0 ist der naheliegende naechste Schritt - wurde in dieser Sitzung bewusst NICHT durchgefuehrt (Installation eines neuen Windows-Installers mit Hyper-V-Eingriffen ist eine Entscheidung, die der Nutzer selbst treffen sollte, nicht die KI unaufgefordert).

Was das NICHT bedeutet: weder das Helm-Chart noch die Anwendung selbst sind hier das Problem - sie wurden nie erreicht, weil CRCs eigene Cluster-Bootstrap-Phase nie stabil zu Ende kam. Die Verifikation ueber helm lint/helm template plus den erfolgreichen podman build/podman run-Test bleibt der aktuelle Kenntnisstand fuer die Korrektheit der Kubernetes-Artefakte selbst.

Falls die Frage "was waere fuer einen ECHTEN Produktivbetrieb noetig" im Raum steht (nicht nur "wie bekomme ich CRC lokal stabil"): siehe kapazitaetsplanung-skalierung.md - dort auch die Lizenzfrage OpenShift-Subscription vs. kostenloses OKD (Abschnitt 8.5) im Detail, direkt anschliessend an den hier dokumentierten CRC/OKD-Versuch.

Fuer alle, die dies fortsetzen moechten

  1. CRC auf 2.62.0 (oder neuer) aktualisieren: crc version zeigt die aktuelle Download-URL fuer den Windows-Installer an.
  2. Danach: crc delete -f (verwirft die alte, mit der alten Version erzeugte Instanz - wichtig, damit ein frisches Bundle mit gueltigen Zertifikaten gezogen wird) und crc start.
  3. Vor dem Start pruefen, wie viel RAM tatsaechlich frei ist (Get-CimInstance Win32_OperatingSystem, PowerShell) - die Podman-Machine/WSL2 sollte NICHT gleichzeitig mit vollem Infrastruktur-Stack laufen (podman machine stop + wsl --shutdown danach, falls Podman parallel gebraucht wird, um Images zu pushen).
  4. Sobald der Cluster stabil Running ist (oc get co zeigt durchgaengig AVAILABLE=True, DEGRADED=False, mehrfach hintereinander geprueft, nicht nur einmal): ein library-Namespace anlegen, die neun Images ueber oc registry login + podman push in OpenShifts internes Image-Registry schieben (Route vorher freischalten: oc patch configs.imageregistry.operator.openshift.io/cluster --type merge -p '{"spec":{"defaultRoute":true}}'), dann Postgres/Redis/Kafka/Keycloak per Bitnami-Chart und die App-Schicht per infra/helm/library-service deployen (siehe "Deployen" und "Infrastruktur bereitstellen" oben) - Image-Repository-Angaben in den values-Dateien muessen dann auf die interne Registry-Route zeigen statt auf library/<service>:local.

Von Compose zu Kubernetes: was sich aendert (und was nicht)

Compose (Podman) Kubernetes
Service-Discovery Eureka (discovery-server) Kubernetes-DNS (kein discovery-server)
Aktiviert durch kein Profil (Default) SPRING_PROFILES_ACTIVE=k8s (setzt das Chart automatisch, siehe deployment.yaml)
Skalierung manuell, podman compose up --scale catalog-service=3 HorizontalPodAutoscaler (siehe hpa.yaml, autoscaling.enabled)
Health-Checks depends_on: condition: service_healthy (nur bei Postgres/RabbitMQ definiert) Liveness-/Readiness-Probes gegen /actuator/health/* (siehe probes in den values-Dateien)
Externer Zugriff Host-Port-Mapping (ports: je Service) Ingress-Ressource (nur api-gateway, siehe ingress.enabled)
Konfiguration environment:-Block je Service in docker-compose.yml env:-Liste je service-values.yaml (identisches Prinzip, andere Syntax)
Zugangsdaten Klartext in docker-compose.yml (nur fuer lokale Entwicklung vertretbar!) Kubernetes-Secret, referenziert per secretEnvFrom

Bewusst IDENTISCH geblieben: die Hostnamen selbst (postgres, kafka, catalog-service, ...) - siehe die Erklaerung dazu in library-service/templates/service.yaml.

⌂ Cockpit