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(mitpodman buildgebaut).
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
- Ein Kubernetes-Cluster (lokal: kind, minikube oder CodeReady Containers - Letzteres wurde auf der Referenz-Entwicklungsmaschine dieses Projekts konkret ausprobiert, siehe "Bekannte Einschraenkung" unten fuer das vollstaendige Versuchsprotokoll)
kubectlundhelm(siehe Chart-Uebersicht)- Ein Ingress-Controller im Cluster (z. B.
ingress-nginx) - Voraussetzung fuer denapi-gateway-Ingress
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.
- Erster
crc start: scheiterte mitFailed 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). crc delete -f+ erneutercrc 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. Eincrc deleteentfernt nur die Instanz, nicht den Cache - die neu erzeugte Instanz erbt exakt dieselben abgelaufenen Zertifikate.- Ein spaeterer, manueller Neustart (durch den Nutzer) gelang tatsaechlich - vermutlich griff
die interne Selbstheilung diesmal rechtzeitig. Der Cluster meldete sich als
Runningmit allen Cluster-OperatorenAvailable=True. - 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 statusmeldete danach "crc does not seem to be setup correctly", obwohl Hyper-V die VM selbst als sauberOffauswies - ein Zustands-Mismatch zwischen CRCs eigenem State-Tracking und der tatsaechlichen Hyper-V-Realitaet). - 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. - 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/gab503-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". - 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. - 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
- CRC auf 2.62.0 (oder neuer) aktualisieren:
crc versionzeigt die aktuelle Download-URL fuer den Windows-Installer an. - Danach:
crc delete -f(verwirft die alte, mit der alten Version erzeugte Instanz - wichtig, damit ein frisches Bundle mit gueltigen Zertifikaten gezogen wird) undcrc start. - 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 --shutdowndanach, falls Podman parallel gebraucht wird, um Images zu pushen). - Sobald der Cluster stabil
Runningist (oc get cozeigt durchgaengigAVAILABLE=True, DEGRADED=False, mehrfach hintereinander geprueft, nicht nur einmal): einlibrary-Namespace anlegen, die neun Images ueberoc registry login+podman pushin 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 perinfra/helm/library-servicedeployen (siehe "Deployen" und "Infrastruktur bereitstellen" oben) - Image-Repository-Angaben in den values-Dateien muessen dann auf die interne Registry-Route zeigen statt auflibrary/<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.