Read in English · Leer en español · Lire en français
Abschnitt 10.2 von 10.8 · Fortgeschritten · 25 Min. Lesezeit
Container und Orchestrierung mit Kubernetes
Videolektion
5 Min. · auf Englisch gesprochen, mit UntertitelnNur anhören (Audio)
Transkript lesen
Einen Container zu starten, ist ein einziger Befehl. Vierzig Services mit jeweils mehreren Kopien auf Dutzenden Maschinen zu betreiben, ist es nicht. So macht es Kubernetes, und so erkennen Sie, wann es mehr ist, als Sie brauchen.
Denken Sie an einen großen Hafen. Waren kommen in genormten Schiffscontainern an, sodass jeder Kran, jedes Schiff und jeder Lkw sie handhaben kann, ohne zu wissen, was darin ist. Der Hafenmeister bewegt die Kisten nicht selbst. Er führt eine Tafel, auf der steht, was wo sein soll. Seine Leute gleichen den Kai ständig mit der Tafel ab und beheben jede Abweichung. Container sind die genormten Kisten, und Kubernetes ist das Büro des Hafenmeisters.
Ein Container-Image ist ein Stapel schreibgeschützter Layer plus eine kleine Konfiguration und ein Manifest, das sie auflistet. Jeder Layer wird durch einen Digest identifiziert, einen Hash seines Inhalts, sodass identische Layer nur einmal gespeichert und heruntergeladen werden. Ein Tag wie Version 2.4.1 ist ein verschiebbares Etikett: Wer Push-Rechte hat, kann es morgen auf ein anderes Image zeigen lassen. Ein Digest ändert seine Bedeutung nie, deshalb sollte die Produktion Images ausführen, die per Digest fixiert sind. Und dank offener Standards kann ein mit jedem Werkzeug gebautes Image in jeder konformen Registry gespeichert und von jeder konformen Runtime ausgeführt werden.
Ein Kubernetes-Cluster hat zwei Hälften: eine Control Plane, die Entscheidungen trifft, und Worker Nodes, die die Arbeitslasten ausführen. Sie, eine Pipeline oder ein GitOps-Agent deklarieren den gewünschten Zustand beim API-Server, der Eingangstür. Er prüft, wer Sie sind, und validiert jede Anfrage. Nur der API-Server spricht mit dem Speicher, der den Zustand des Clusters hält. Der Scheduler beobachtet neue Pods und wählt für jeden einen Node. Der Controller Manager führt Dutzende Controller aus, jeder zuständig für eine Art von Objekt, etwa dafür, dass die richtige Zahl von Pods läuft. Auf jedem Worker Node beobachtet ein Agent den API-Server auf ihm zugewiesene Pods, bittet die Container-Runtime, sie zu starten, prüft ihre Gesundheit und meldet zurück. Beachten Sie die Richtung. Die Control Plane meldet sich nie auf einem Node an. Die Nodes beobachten den Zustand, in dem sie sein sollen, und nähern sich ihm an, sodass ein abgeschnittener Node einfach aufholt.
Die zentrale Idee ist deklarativ. Sie sagen dem Cluster nicht, er solle drei Container starten. Sie speichern den gewünschten Zustand: Drei Kopien sollen existieren. Controller führen dann ohne Ende eine Schleife aus: den tatsächlichen Zustand beobachten, mit dem gewünschten vergleichen und handeln, um die Differenz zu beseitigen. Fällt im Abendansturm ein Node aus, sieht der Controller zwei Pods, wo drei gewünscht sind, und legt einen an; der Scheduler platziert ihn auf einem gesunden Node. Weil Controller auf die aktuelle Differenz reagieren, richtet ein verpasstes Ereignis oder ein Absturz keinen bleibenden Schaden an.
Zwei Einstellungen zählen am meisten. Ein Request ist das, was der Scheduler reserviert: Ein Pod, der eine halbe CPU anfordert, kommt nur auf einen Node mit so viel unreservierter Kapazität. Ein Limit ist eine Obergrenze. Ein Container, der sein CPU-Limit überschreitet, wird verlangsamt; einer, der sein Speicherlimit überschreitet, wird beendet und neu gestartet. Probes informieren Kubernetes über die Gesundheit. Solange eine Readiness Probe fehlschlägt, erhält der Pod keinen Verkehr, läuft aber weiter. Schlägt Liveness wiederholt fehl, wird der Container neu gestartet. Lassen Sie Liveness also nie die Datenbank prüfen: Ein kurzes Datenbank-Failover würde jeden Pod gleichzeitig neu starten und aus einer Störung einen Ausfall machen.
Dimensionieren wir den Bestellservice für den Ansturm. Jeder Pod fordert eine halbe CPU an, und der Autoscaler zielt auf sechzig Prozent davon, mit drei bis zwölf Replikas. Jeder Node hat etwa drei Komma acht CPUs verfügbar, passt also sieben dieser Pods. Um sechs Uhr abends liegen vier Replikas bei neunzig Prozent ihres Requests. Vier mal neunzig, geteilt durch sechzig, ergibt sechs Replikas. Zwanzig Minuten später liegen sechs Replikas bei fünfundachtzig Prozent. Sechs mal fünfundachtzig, geteilt durch sechzig, ist acht Komma fünf, aufgerundet neun. Sind die Nodes schon voll, warten drei neue Pods, und ein neuer Node braucht vielleicht zwei bis vier Minuten, um beizutreten. Autoscaling reagiert auf Last, die bereits angekommen ist. Erhöhen Sie also vor einem vorhersehbaren Ansturm das Minimum, halten Sie freie Node-Kapazität vor und halten Sie Images klein, damit neue Pods schnell starten.
Kubernetes ist eine Plattform zum Bauen von Plattformen, und das hat seinen Preis. Jemand muss Netzwerk, Identität, Secrets, Policies und Observability entwerfen. Es hat drei Minor Releases pro Jahr, jedes etwa vierzehn Monate unterstützt, und verwaltete Control Planes nehmen nur einen Teil der Arbeit ab. Für ein kleines Team mit wenigen zustandslosen Webservices bietet ein PaaS oder eine Serverless-Container-Plattform die meisten Vorteile für einen Bruchteil der Arbeit. Es lohnt sich bei vielen Services, mehreren Teams und einem Plattformteam, das es betreibt. Andernfalls bauen Organisationen darauf oft im Grunde ein schlechteres PaaS.
Der vollständige Abschnitt behandelt die zentralen Objekte, Beispielmanifeste und das kontrollierte Herunterfahren. Als Nächstes, in Abschnitt zehn Punkt drei, geht es um die Twelve-Factor App. Bis gleich.
Was Sie wissen müssen
- Ein Container-Image ist ein Stapel inhaltsadressierter Layer plus Konfiguration und Manifest; Tags können sich verschieben, Digests nicht, daher sollte die Produktion Images ausführen, die per Digest fixiert sind.
- Die OCI-Spezifikationen für Runtime, Image und Distribution erlauben, dass mit beliebigen Werkzeugen gebaute Images in jeder Registry gespeichert und von jeder konformen Runtime ausgeführt werden.
- Kubernetes hat eine Control Plane (API-Server, etcd, Scheduler, Controller Manager) und Worker Nodes (kubelet, Container-Runtime, kube-proxy); nur der API-Server spricht mit etcd, und die Nodes holen sich ihre Arbeit selbst.
- Controller führen pegelgesteuerte Reconciliation Loops aus, die den tatsächlichen mit dem gewünschten Zustand vergleichen und auf die Differenz reagieren; das macht das System robust gegen Ausfälle und verpasste Ereignisse.
- Pods sind wegwerfbar; Deployments rollen sie aus, Services geben ihnen stabile Namen, Gateway API leitet Verkehr hinein, StatefulSets geben Identität und Speicher, Jobs laufen bis zum Abschluss, und der HPA skaliert die Replikas.
- Requests steuern Scheduling und Autoscaling, Limits deckeln die Nutzung (CPU wird gedrosselt, bei Speicherüberschreitung wird beendet); Liveness Probes dürfen keine geteilten Abhängigkeiten prüfen.
- Kubernetes lohnt sich bei vielen Services, mehreren Teams und einem Plattformteam; für wenige zustandslose Services ist ein PaaS oder eine Serverless-Container-Plattform meist die bessere Wahl.
Images, Layer und Registrys
Kapitel 0 hat Container als isolierte Prozesse eingeführt, die sich den Kernel des Hosts teilen und als Images aus einem Dockerfile gebaut werden (Server und die Cloud). Ein Architekt braucht etwas mehr Detail, denn das Image-Design beeinflusst Build-Geschwindigkeit, Sicherheit und wie zuverlässig dieselbe Software jede Umgebung erreicht.
Ein Container-Image ist ein Stapel schreibgeschützter Layer, jeder eine komprimierte Menge von Dateiänderungen, plus ein kleines Konfigurationsdokument (der auszuführende Befehl, Umgebungsvariablen, der Benutzer) und ein Manifest, das sie auflistet. Jeder Layer und jedes Manifest wird durch einen Digest identifiziert, einen SHA-256-Hash seines Inhalts. Identische Layer werden nur einmal gespeichert und heruntergeladen, sodass hundert Images auf derselben Basis deren Layer teilen. Weil jede Anweisung in einem Dockerfile einen Layer erzeugt und Builder zwischengespeicherte Layer wiederverwenden, bis sich etwas ändert, ordnet man die Anweisungen von selten zu häufig geändert: Basis-Image, dann Abhängigkeiten, dann Anwendungscode. Multi-Stage-Builds kompilieren in einer Stufe und kopieren nur das Ergebnis in ein kleines finales Image; Compiler und Build-Werkzeuge bleiben zurück.
Images liegen in einer Registry: Docker Hub, GitHub Container Registry, Amazon ECR, Azure Container Registry, Google Artifact Registry oder eine selbst betriebene wie Harbor. Ein Tag wie orders:2.4.1 ist ein verschiebbares Etikett; wer Push-Rechte hat, kann es morgen auf ein anderes Image zeigen lassen. Ein Digest wie orders@sha256:3f1c... kann seine Bedeutung nie ändern. Produktions-Deployments sollten Tags deshalb in Digests auflösen, damit genau das läuft, was getestet wurde, und nie latest verwenden.
Kleine Images sind schneller heruntergeladen und haben weniger Komponenten, die gepatcht werden müssen. Übliche Praxis ist eine minimale Basis (eine schlanke Distribution oder ein Distroless-Image, das nur die Laufzeitumgebung enthält), die Ausführung als Nicht-Root-Benutzer, regelmäßiges Neubauen, um Sicherheitskorrekturen aufzunehmen, und das Scannen der Images auf bekannte Schwachstellen in der Pipeline. Das Signieren von Images und das Anhängen einer Software-Stückliste (SBOM) gehören zur Sicherheit der Software-Lieferkette, die Kapitel 8 behandelt (Sicherheit der Software-Lieferkette).
Die OCI-Standards
Docker machte Container ab 2013 populär, und 2015 gründeten Docker und andere unter dem Dach der Linux Foundation die Open Container Initiative (OCI), um eine Zersplitterung des Formats zu verhindern. Die OCI pflegt drei Spezifikationen. Die Runtime-Spezifikation legt fest, wie ein entpackter Container ausgeführt wird (Namespaces, Control Groups, Mounts, der zu startende Prozess); runc ist ihre Referenzimplementierung. Die Image-Spezifikation definiert Layer, Manifeste und Konfiguration. Die Distribution-Spezifikation definiert die HTTP-API, die Registrys für Push und Pull bereitstellen. Version 1.1 der Image- und der Distribution-Spezifikation (2024) brachte Standardwege, andere Artefakte in Registrys abzulegen und mit einem Image zu verknüpfen; so reisen Signaturen und Software-Stücklisten heute neben den Images, die sie beschreiben.
Das praktische Ergebnis ist Austauschbarkeit. Ein mit Docker, Podman, Buildah oder einem Cloud-Build-Dienst gebautes Image kann in jeder konformen Registry gespeichert und von jeder konformen Runtime ausgeführt werden: containerd und CRI-O auf einem Kubernetes-Node, eine Serverless-Container-Plattform oder der Laptop eines Entwicklers. Deshalb wurden Container, anders als die meisten Paketformate davor, zu einer gemeinsamen Sprache zwischen Entwicklung, Betrieb und jeder großen Cloud.
Warum Orchestrierung nötig ist
Einen Container zu starten, ist ein einziger Befehl. Die Essenslieferplattform zu betreiben, ist es nicht: vierzig Services mit je drei bis dreißig Kopien, verteilt über Dutzende Maschinen in drei Availability Zones, und stündlich geht irgendwo eine neue Version hinaus. Jemand muss entscheiden, auf welcher Maschine jede Kopie läuft, abgestürzte Kopien neu starten, ausgefallene Maschinen ersetzen, für den Abendansturm Kopien hinzufügen und danach wieder entfernen, neue Versionen ausrollen, ohne Anfragen zu verlieren, Services einander finden lassen, während Kopien kommen und gehen, ihnen Konfiguration und Secrets übergeben und den wenigen, die es brauchen, Speicher anhängen.
Container-Orchestrierung ist Software, die all das anhand einer Deklaration dessen erledigt, was laufen soll. Google betrieb dafür mehr als ein Jahrzehnt lang ein internes System namens Borg; 2014 kündigte es Kubernetes an, ein Open-Source-System, das auf dieser Erfahrung aufbaut, veröffentlichte im Juli 2015 Version 1.0 und übergab es der neu gegründeten Cloud Native Computing Foundation, wo es 2018 als erstes Projekt den Status „graduated“ erreichte. Es gibt Alternativen (HashiCorp Nomad, Amazon ECS, Serverless-Container-Plattformen), aber Kubernetes ist heute die gemeinsame Grundlage, die jeder große Anbieter und die meisten On-Premises-Plattformen anbieten.
Kubernetes-Architektur
Wie Kubernetes funktioniert: Control Plane und Nodes
Ein Kubernetes-Cluster hat eine Control Plane, die Entscheidungen trifft, und Worker Nodes, die die Arbeitslasten ausführen. Der API-Server ist die Eingangstür: Jeder Client, vom kubectl eines Entwicklers bis zu den clustereigenen Komponenten, liest und schreibt Objekte über seine REST-API, und er authentifiziert den Aufrufer, prüft die Autorisierung und führt Admission-Kontrollen aus, die jede Anfrage validieren oder verändern können (etwa Container ablehnen, die als Root laufen). Er ist die einzige Komponente, die mit etcd spricht, einem stark konsistenten Key-Value-Store, der den Cluster-Zustand mit dem Konsensprotokoll Raft über typischerweise drei oder fünf Mitglieder repliziert, sodass beim Verlust von einem oder zwei eine Mehrheit überlebt (Konsistenzmodelle, CAP und PACELC).
Der Scheduler beobachtet neue Pods ohne zugewiesenen Node, filtert Nodes aus, die sie nicht ausführen können (zu wenig unreservierte CPU oder zu wenig Speicher, falsche Zone, fehlende Hardware, Taints, die der Pod nicht toleriert), bewertet die übrigen (Kopien über Zonen verteilen, effizient packen) und hält seine Wahl über den API-Server fest. Der Controller Manager führt Dutzende Controller aus, jeder zuständig für eine Art von Objekt: Der ReplicaSet-Controller hält die richtige Zahl von Pods, der Node-Controller bemerkt Nodes, die sich nicht mehr melden, der Job-Controller führt Aufgaben bis zum Abschluss aus. In einer Cloud legt zusätzlich ein Cloud Controller Manager über die API des Anbieters Load Balancer an und hängt Datenträger an.
Auf jedem Worker Node läuft ein kubelet, ein Agent, der den API-Server auf Pods beobachtet, die seinem Node zugewiesen sind, die Container-Runtime (meist containerd oder CRI-O, über das Container Runtime Interface; der eingebaute Shim für Docker Engine wurde 2022 mit Version 1.24 entfernt) bittet, sie zu starten, ihre Health Probes ausführt und den Status zurückmeldet. kube-proxy programmiert die Paketweiterleitungsregeln des Nodes, sodass Verkehr an die virtuelle Adresse eines Service einen seiner Pods erreicht; manche Netzwerk-Plug-ins wie Cilium ersetzen es durch eine eBPF-Datenebene. Ein Netzwerk-Plug-in gibt jedem Pod eine eigene IP-Adresse, und ein Cluster-DNS-Dienst (meist CoreDNS) lässt Pods Services über ihren Namen finden.
Beachten Sie die Richtung der Pfeile. Die Control Plane meldet sich nie auf einem Node an, um einen Befehl auszuführen; die Nodes beobachten den Zustand, in dem sie sein sollen, und nähern sich ihm an. Dieses Pull-basierte Beobachten und Annähern macht Kubernetes robust: Ein Node, der zehn Minuten getrennt war, holt einfach auf, wenn er zurückkommt. Bei Managed Kubernetes (Amazon EKS, Azure Kubernetes Service, Google Kubernetes Engine und andere) betreibt und aktualisiert der Anbieter die Control Plane; der Kunde wählt weiterhin die Node-Typen, aktualisiert die Node-Pools und ist für alles verantwortlich, was in den Cluster deployt wird.
Deklarativer gewünschter Zustand und Reconciliation Loops
Die zentrale Idee von Kubernetes ist deklarative Konfiguration. Sie sagen dem Cluster nicht, er solle drei Container starten; Sie speichern ein Objekt, das besagt, dass drei Kopien eines bestimmten Images existieren sollen: den gewünschten Zustand. Controller führen dann Reconciliation Loops aus: den tatsächlichen Zustand beobachten, ihn mit dem gewünschten vergleichen und handeln, um die Differenz zu beseitigen, ohne Ende.
Der Reconciliation Loop
Verfolgen Sie, was geschieht, wenn während des Abendansturms ein Node in Zone B ausfällt. Der Node-Controller empfängt keine Heartbeats mehr und markiert den Node als nicht bereit; nach einem Timeout werden seine Pods zur Verdrängung (Eviction) vorgemerkt. Der ReplicaSet-Controller des Bestellservice beobachtet zwei laufende Pods, wo drei gewünscht sind, und legt ein neues Pod-Objekt an. Der Scheduler sieht einen nicht eingeplanten Pod und weist ihn einem gesunden Node zu, je nach Verteilungsregeln bevorzugt einem verbliebenen Node in Zone B oder einer anderen Zone. Das kubelet dieses Nodes sieht die Zuweisung, lädt das Image und startet den Container. Sobald seine Readiness Probe besteht, werden die Endpoints hinter dem Service orders aktualisiert, und Verkehr fließt zu ihm. Keine Komponente hat einer anderen gesagt, was zu tun ist; jede hat auf den Zustand reagiert, den sie beobachtet hat.
Weil Controller pegelgesteuert sind (sie reagieren auf die aktuelle Differenz) statt flankengesteuert (auf einzelne Ereignisse reagierend), kommt ein Controller, der abstürzt, eine Benachrichtigung verpasst oder doppelt läuft, trotzdem zum richtigen Ergebnis. Das Muster lässt sich erweitern: Custom Resource Definitions fügen neue Objekttypen hinzu, und das Operator-Muster, 2016 von CoreOS eingeführt, verpackt einen Controller, der Betriebswissen kodiert, etwa um einen PostgreSQL-Cluster aus einem einzigen deklarierten Objekt anzulegen, zu sichern und per Failover umzuschalten. Abschnitt 10.4 zeigt dieselbe Schleife, angewandt auf ganze Umgebungen durch GitOps.
Deklarative Steuerung hat ihren Preis: Sie müssen in Endzuständen denken und die genaue Abfolge loslassen. Wollen zwei Controller für dasselbe Feld unterschiedliche gewünschte Zustände, kämpfen sie gegeneinander. Ein klassisches Beispiel ist ein Deployment-Manifest in Git mit replicas: 3, während ein Autoscaler den Wert auf neun setzt; jede GitOps-Synchronisierung skaliert den Service mitten im Ansturm wieder herunter. Die Lösung ist, jedes Feld genau einem Eigentümer zu überlassen, hier durch das Entfernen von replicas aus dem Manifest.
Die zentralen Kubernetes-Objekte
| Objekt | Was er ist | Beispiel Essenslieferung |
|---|---|---|
| Pod | Ein oder mehrere Container, die gemeinsam auf einem Node eingeplant werden und sich eine IP-Adresse und Volumes teilen. Pods sind wegwerfbar und werden ersetzt, nie repariert. | Eine Kopie des Bestellservice, vielleicht mit einem kleinen Sidecar für den Log-Versand. |
| ReplicaSet | Hält eine festgelegte Zahl identischer Pods am Laufen. Wird selten von Hand geschrieben. | Vom Deployment orders angelegt und verwaltet. |
| Deployment | Deklariert die Pod-Vorlage und die Zahl der Replikas einer zustandslosen Anwendung und rollt Änderungen aus, indem es Pods innerhalb von Surge- und Nichtverfügbarkeitsgrenzen von einem alten ReplicaSet in ein neues verschiebt. | Bestell-, Speisekarten- und Preisgestaltungsservice. |
| Service | Ein stabiler Name und eine virtuelle IP für eine wechselnde, per Label ausgewählte Menge von Pods, mit Lastverteilung über sie. | orders.default.svc, genutzt vom Checkout-Service. |
| Ingress und Gateway API | Leiten externen HTTP(S)-Verkehr nach Host und Pfad an Services. Gateway API (allgemein verfügbar seit Version 1.0 im Oktober 2023) ist der ausdrucksstärkere, rollenorientierte Nachfolger: Das Plattformteam besitzt das Gateway, die Anwendungsteams besitzen ihre HTTPRoutes. | api.example.com/orders, mit TLS an den Service orders geleitet. |
| ConfigMap | Nicht geheime Konfiguration, an Pods als Umgebungsvariablen oder Dateien übergeben. | Feature-Einstellungen, Timeouts, die URL des Speisekarten-Service. |
| Secret | Sensible Werte, übergeben wie ConfigMaps. In etcd nur Base64-kodiert gespeichert, sofern keine Verschlüsselung im Ruhezustand konfiguriert ist; viele Teams synchronisieren sie aus einem externen Secrets-Manager. | Der API-Schlüssel des Zahlungsanbieters, das Datenbankpasswort. |
| StatefulSet | Pods mit stabilen Namen, stabilen Netzwerkidentitäten und eigenen persistenten Volumes, die der Reihe nach gestartet und gestoppt werden. | Ein selbst betriebener Kafka- oder Redis-Cluster, falls kein Managed Service genutzt wird. |
| DaemonSet | Genau ein Pod auf jedem (oder jedem ausgewählten) Node. | Log-Collector oder Monitoring-Agent für die Nodes. |
| Job und CronJob | Pods, die bis zum Abschluss laufen, einmalig oder nach Zeitplan, mit Wiederholungen. | Datenbankmigration vor einem Release; nächtlicher Auszahlungsbericht für die Restaurants. |
| HorizontalPodAutoscaler | Passt die Zahl der Replikas eines Deployments an, um eine Metrik nahe einem Zielwert zu halten. | Den Bestellservice von 3 auf 12 Replikas skalieren, wenn die CPU-Nutzung am Abend steigt. |
Ein Deployment und ein Service in YAML
Die folgenden Manifeste deklarieren den Bestellservice: Konfiguration aus einem Secret, Resource Requests und Probes, ein stabiler Service davor und ein Autoscaler, der zwischen drei und zwölf Replikas hält. Das Deployment hat bewusst kein Feld replicas, damit der Autoscaler der einzige Eigentümer der Anzahl ist. Angewendet werden sie mit kubectl apply oder, besser, durch eine Pipeline oder einen GitOps-Agenten aus einem Repository.
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders
spec:
# no replicas field: the autoscaler below owns the count
selector:
matchLabels: {app: orders}
template:
metadata:
labels: {app: orders}
spec:
containers:
- name: orders
image: registry.example.com/orders:2.4.1
ports: [{containerPort: 8080}]
env:
- name: DATABASE_URL
valueFrom: {secretKeyRef: {name: orders-db, key: url}}
resources:
requests: {cpu: 500m, memory: 512Mi}
limits: {memory: 512Mi} # no CPU limit: may burst
readinessProbe:
httpGet: {path: /ready, port: 8080}
periodSeconds: 5
livenessProbe:
httpGet: {path: /live, port: 8080}
periodSeconds: 10
failureThreshold: 3apiVersion: v1
kind: Service
metadata:
name: orders
spec:
selector: {app: orders}
ports: [{port: 80, targetPort: 8080}]
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: orders
spec:
scaleTargetRef: {apiVersion: apps/v1, kind: Deployment, name: orders}
minReplicas: 3
maxReplicas: 12
metrics:
- type: Resource
resource:
name: cpu
target: {type: Utilization, averageUtilization: 60}Resource Requests, Limits und Quality of Service
Requests und Limits sind die folgenreichsten Zeilen eines Manifests. Ein Request ist das, was der Scheduler reserviert: Ein Pod, der 500 Millicores (eine halbe CPU) und 512 MiB Speicher anfordert, wird nur auf einem Node mit so viel unreservierter Kapazität platziert, und dieser Anteil ist ihm bei Konkurrenz garantiert. Ein Limit ist eine vom Kernel durchgesetzte Obergrenze: Ein Container, der sein CPU-Limit überschreitet, wird gedrosselt (verlangsamt), und einer, der sein Speicherlimit überschreitet, wird beendet und neu gestartet, der bekannte Status OOMKilled. Kubernetes leitet aus diesen Einstellungen eine Quality-of-Service-Klasse ab: Guaranteed (Requests gleich Limits für jede Ressource), Burstable (Requests gesetzt, Limits höher oder nicht vorhanden) und BestEffort (nichts gesetzt, als Erstes verdrängt, wenn einem Node die Ressourcen ausgehen).
Bei CPU-Limits sind sich Fachleute uneinig. Das eine Lager setzt sie überall, um Vorhersehbarkeit und Fairness zwischen Teams zu erreichen; das andere setzt genaue CPU-Requests und keine CPU-Limits, weil Drosselung Latenzspitzen verursacht, selbst wenn der Node freie CPU hat, und Requests jedem Pod seinen Anteil ohnehin garantieren. Beim Speicher gibt es wenig Streit: Setzen Sie das Limit gleich dem Request, denn Speicher lässt sich einem Prozess nicht entziehen, ohne ihn zu beenden, und ein Pod, der mehr nutzt, als er angefordert hat, ist der erste Kandidat für die Verdrängung. So oder so müssen Requests auf Messungen beruhen; überhöhte Requests verschwenden Geld (der Node ist reserviert, aber untätig), und zu niedrige verursachen Noisy-Neighbour-Probleme und irreführendes Autoscaling.
Der HorizontalPodAutoscaler berechnet gewünschte Replikas = Aufrunden(aktuelle Replikas × aktuelle Metrik ÷ Zielwert) und misst die CPU-Nutzung als Prozentsatz des Requests. Er skaliert schnell hoch, wartet aber vor dem Herunterskalieren standardmäßig ein Stabilisierungsfenster von fünf Minuten ab, um ein Hin- und Herpendeln zu vermeiden. Passen neue Pods nicht auf bestehende Nodes, bleiben sie im Status Pending, und ein Node-Autoscaler (der Cluster Autoscaler oder bei manchen Anbietern Karpenter) fügt Maschinen hinzu, was Minuten statt Sekunden dauert. Pod-Autoscaling und Node-Autoscaling sind getrennte Schleifen mit getrennten Verzögerungen, und beide müssen für den Ansturm eingeplant werden.
Ausgearbeitetes Beispiel
Ausgearbeitetes Beispiel: den Bestellservice für den Abendansturm dimensionieren
Jeder Pod des Bestellservice fordert 500m CPU und 512 MiB an, mit einem Speicherlimit von 512 MiB und ohne CPU-Limit. Die Nodes haben 4 vCPUs und 16 GiB, von denen nach Systemreservierungen etwa 3,8 vCPUs und 14,5 GiB zuteilbar sind (veranschaulichende Zahlen). Der HPA zielt auf 60 % CPU, mit 3 bis 12 Replikas.
| Pods pro Node | CPU: 3,8 ÷ 0,5 = 7 Pods. Speicher: 14,5 GiB ÷ 0,5 GiB = 29 Pods. Die CPU ist die bindende Grenze: 7 Bestell-Pods pro Node, wenn dort nichts anderes läuft. |
|---|---|
| 18:00, der Ansturm beginnt | 4 Replikas bei durchschnittlich 90 % ihres CPU-Requests. Gewünscht = Aufrunden(4 × 90 ÷ 60) = 6 Replikas. |
| 18:20, weiter steigend | 6 Replikas bei 85 %. Gewünscht = Aufrunden(6 × 85 ÷ 60) = Aufrunden(8,5) = 9 Replikas, die zusammen 4,5 vCPUs anfordern. |
| Node-Kapazität | Wenn andere Services die Nodes bereits füllen, bleiben drei der neuen Pods im Status Pending; der Node-Autoscaler fügt einen Node hinzu, der vielleicht zwei bis vier Minuten braucht, um beizutreten und das Image zu laden. |
| Antwort im Entwurf | minReplicas vor dem vorhersehbaren Ansturm erhöhen (geplantes Skalieren), etwas freie Node-Kapazität vorhalten und das Image klein halten, damit neue Pods schnell starten. |
| Folge eines falschen Requests | Bräuchte jeder Pod in Wirklichkeit nur 250m, bliebe der Request aber bei 500m, würde dieselbe Ansturmlast (225m pro Pod) als 45 % statt 90 % erscheinen: Der HPA würde erst skalieren, wenn die Nutzung 300m pro Pod überschreitet, und der Cluster würde doppelt so viel CPU reservieren wie nötig. |
Autoscaling reagiert auf Last, die bereits angekommen ist; bei einer vorhersehbaren täglichen Spitze ist geplantes Vorskalieren billiger als verlorene Bestellungen.
Requests steuern sowohl den Scheduler als auch den Autoscaler; sie zu messen ist daher eine Architekturaufgabe ersten Ranges, keine Tuning-Kleinigkeit.
Liveness, Readiness und Startup Probes
Probes sind die Art, wie Kubernetes etwas über die Gesundheit erfährt. Eine Readiness Probe beantwortet, ob dieser Pod jetzt Verkehr erhalten soll; solange sie fehlschlägt, wird der Pod aus den Endpoints seines Service entfernt, läuft aber weiter. Eine Liveness Probe beantwortet, ob der Prozess unrettbar hängt; schlägt sie wiederholt fehl, startet das kubelet den Container neu. Eine Startup Probe hält die beiden anderen zurück, bis eine langsam startende Anwendung ihre Initialisierung abgeschlossen hat.
Der folgenschwerste Fehler ist eine Liveness Probe, die Abhängigkeiten prüft. Fragt der Liveness-Endpoint des Bestellservice die Datenbank ab, lässt ein dreißigsekündiges Datenbank-Failover jeden Pod des Bestellservice gleichzeitig an der Liveness scheitern, Kubernetes startet alle neu, sie verbinden sich alle im selben Moment erneut, und aus einer kurzen Störung wird ein Ausfall. Liveness sollte nur prüfen, ob der Prozess selbst antworten kann; Readiness darf berücksichtigen, ob der Pod nützliche Arbeit leisten kann, doch selbst dort vermeiden es viele Teams, wegen einer geteilten Abhängigkeit alle Pods als nicht bereit zu markieren, und lassen Anfragen lieber mit einem Circuit Breaker schnell fehlschlagen (siehe Resilienzmuster).
Probes arbeiten mit dem kontrollierten Herunterfahren zusammen. Wird ein Pod während eines Rollouts oder beim Herunterskalieren gelöscht, entfernt Kubernetes ihn aus den Endpoints und sendet dem Prozess SIGTERM; der Prozess sollte keine neue Arbeit mehr annehmen, laufende Anfragen abschließen und sich innerhalb der Schonfrist (standardmäßig 30 Sekunden) beenden, bevor er abgebrochen wird. Weil sich das Entfernen aus den Endpoints asynchron ausbreitet, verhindert eine kurze preStop-Verzögerung, dass Anfragen an einen Pod gehen, der bereits zu stoppen begonnen hat. Abschnitt 10.3 greift das als Twelve-Factor-Prinzip der Disposability wieder auf.
Wann Kubernetes überdimensioniert ist
Kubernetes ist eine Allzweckplattform zum Bauen von Plattformen, und für diese Allgemeinheit verlangt es seinen Preis. Jemand muss Netzwerk, Ingress, Identität, Secrets, Policies, Observability und Mandantenfähigkeit entwerfen; mit drei Minor Releases pro Jahr Schritt halten, die jeweils etwa vierzehn Monate unterstützt werden, dazu mit den Add-ons, die davon abhängen; und neben den Anwendungen auch für den Cluster Rufbereitschaft leisten. Verwaltete Control Planes nehmen einen Teil dieser Arbeit ab, nicht den größten.
Für ein kleines Team mit einer Handvoll zustandsloser Webservices bietet ein PaaS oder eine Serverless-Container-Plattform (Google Cloud Run, Azure Container Apps, Amazon ECS mit Fargate und Ähnliche) die meisten Vorteile (Container, Autoscaling, Rolling Deployments, verwaltetes TLS) bei einem Bruchteil der Betriebslast. Kubernetes beginnt sich zu lohnen, wenn es viele Services und mehrere Teams gibt, ein Plattformteam, das es betreibt, Arbeitslasten mit ungewöhnlichen Anforderungen (zustandsbehaftete Systeme, GPUs, Batch-Jobs, eigene Controller), die Anforderung, dieselbe Plattform vor Ort und in mehreren Clouds zu betreiben, oder ein Ökosystem von Werkzeugen, das die Organisation nutzen will. Viele Organisationen haben es eingeführt, weil es in Mode war, und dann Jahre damit verbracht, darauf im Grunde ein schlechteres PaaS zu bauen. Abschnitt 10.7 bespricht, wie Plattformteams diesem Schicksal entgehen (Platform Engineering).
Prüfen Sie Ihr Verständnis
Warum sollte ein Produktions-Deployment ein Image per Digest statt per Tag referenzieren?
Ein Tag ist ein verschiebbares Etikett, das auf anderen Inhalt umgelenkt werden kann; ein Digest ist ein Hash des Inhalts und identifiziert immer genau das Image, das getestet wurde.
Ein Node fällt aus. Welche Komponenten handeln in welcher Reihenfolge, um die dritte Replika wiederherzustellen?
Der Node-Controller markiert den Node als nicht bereit, und seine Pods werden verdrängt; der ReplicaSet-Controller sieht zu wenige Pods und legt einen an; der Scheduler weist ihn einem gesunden Node zu; das kubelet dieses Nodes startet ihn über die Runtime; sobald Readiness besteht, wird er in die Endpoints des Service aufgenommen.
Was geschieht mit einem Container, der sein Speicherlimit überschreitet, und mit einem, der sein CPU-Limit überschreitet?
Beim Überschreiten des Speicherlimits wird der Container beendet (OOMKilled) und neu gestartet; beim Überschreiten des CPU-Limits wird er nur gedrosselt.
Warum ist es gefährlich, in einer Liveness Probe die Datenbank zu prüfen?
Ein kurzes Datenbankproblem lässt jeden Pod gleichzeitig an der Liveness scheitern, sodass Kubernetes alle auf einmal neu startet und aus einer kurzen Störung einer Abhängigkeit ein vollständiger Ausfall wird.
Quellen und weiterführende Literatur
Haben Sie eine Frage zu diesem Abschnitt? ArchitectBot, der KI-Lernassistent des Kurses, antwortet zuerst aus dem Kurs.
Fragen: „Sollte die Essenslieferplattform CPU-Limits für ihre Kubernetes-Pods setzen oder nur Requests?“