Artikel

Eine Produktions-Baseline für Kubernetes-Workloads

Requests, Probes, Disruption Budgets, sauberes Herunterfahren und Pod-Sicherheit: die Einstellungen, die ein produktives Deployment vor seinem ersten Incident haben sollte.

Die Beispiele sind generisch und dienen der Veranschaulichung. Sie stammen aus keiner Kundenumgebung: Passen Sie Schwellenwerte und Einstellungen an Ihr eigenes System an.

Warum eine Baseline

Kubernetes führt fast alles mit fast keiner Konfiguration aus, und genau das ist das Problem: Die Standardwerte sind für den Einstieg gedacht, nicht für den stabilen Betrieb. Die folgenden Einstellungen kosten am ersten Tag wenig und viel, wenn man sie erst während eines Ausfalls entdeckt. Sie gelten für zustandslose Deployments; zustandsbehaftete Workloads brauchen mehr.

Requests und Limits

Der Scheduler platziert Pods anhand ihrer Requests, und der Horizontal Pod Autoscaler misst die Auslastung daran. Ein Pod ohne Requests wird blind platziert und kann nicht nach CPU skaliert werden.

Speicher lässt sich nicht komprimieren: Ein Container, der sein Speicherlimit überschreitet, wird beendet. Setzen Sie das Speicherlimit gleich oder nahe dem Request, damit ein Pod nie auf Speicher eingeplant wird, den er nicht behalten kann. CPU ist anders — ein Container über seinem CPU-Limit wird gedrosselt, was sich als Latenz zeigt. Viele Teams setzen CPU-Requests und lassen CPU-Limits bei latenzkritischen Services weg; was Sie auch wählen, messen Sie die Drosselung.

Drei Probes, drei Aufgaben

  • Readiness entscheidet, ob der Pod Traffic erhält. Vorsicht bei gemeinsamen Abhängigkeiten: Schlagen alle Pods gleichzeitig fehl, hat der Service überhaupt keine Endpoints mehr.
  • Liveness entscheidet, ob der Container neu gestartet wird. Beschränken Sie sie auf den Prozess selbst: Prüft sie die Datenbank, startet ein Datenbankausfall alle Pods auf einmal neu.
  • Startup hält die anderen Probes zurück, während eine langsame Anwendung startet, statt die Liveness-Verzögerung aufzublähen.

Node-Drains und Zonenausfälle überstehen

Node-Upgrades und Autoscaling verdrängen ständig Pods. Ein PodDisruptionBudget begrenzt, wie viele Replikate eine freiwillige Unterbrechung gleichzeitig herunterfahren darf, und Topology Spread Constraints verteilen Replikate auf verschiedene Zonen, damit der Ausfall einer Zone nicht den ganzen Service mitnimmt.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api
spec:
  maxUnavailable: 1
  selector:
    matchLabels: { app: api }

Sauberes Herunterfahren

Wird ein Pod gelöscht, beginnen seine Entfernung aus den Service-Endpoints und das Herunterfahren seiner Container (erst der preStop-Hook, dann SIGTERM) gleichzeitig. Eine Anwendung, die sofort beendet, verliert Anfragen, die noch zu ihr geroutet werden. Fügen Sie eine kurze preStop-Pause ein, damit Load Balancer nachziehen, behandeln Sie SIGTERM, indem Sie laufende Arbeit abschließen, und halten Sie terminationGracePeriodSeconds länger als Pause plus Drain.

Security Context und Images

Laufen Sie als Nicht-Root-Benutzer, entfernen Sie alle Linux-Capabilities, blockieren Sie Rechteausweitung und mounten Sie das Root-Dateisystem schreibgeschützt; der Pod Security Standard restricted erzwingt das meiste davon pro Namespace. Referenzieren Sie Images per Digest statt über ein veränderliches Tag, damit genau das läuft, was getestet wurde.

Alles zusammen

Ein Deployment mit angewandter Baseline. Die Werte sind Platzhalter: Bemessen Sie Requests nach gemessener Nutzung, nicht nach diesem Beispiel.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels: { app: api }
  template:
    metadata:
      labels: { app: api }
    spec:
      terminationGracePeriodSeconds: 45
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels: { app: api }
      securityContext:
        runAsNonRoot: true
        seccompProfile: { type: RuntimeDefault }
      containers:
        - name: api
          image: registry.example.com/api@sha256:<digest>
          resources:
            requests: { cpu: 250m, memory: 256Mi }
            limits: { memory: 256Mi }
          readinessProbe:
            httpGet: { path: /ready, port: 8080 }
            periodSeconds: 5
          livenessProbe:
            httpGet: { path: /live, port: 8080 }
            periodSeconds: 10
            failureThreshold: 3
          startupProbe:
            httpGet: { path: /live, port: 8080 }
            periodSeconds: 5
            failureThreshold: 30
          lifecycle:
            preStop:
              sleep: { seconds: 5 }   # native sleep action, Kubernetes 1.30+
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities: { drop: ["ALL"] }
Kontakt

Eine zweite Meinung zu Ihrem Setup?

Schicken Sie eine kurze Beschreibung Ihres Systems. Ein erfahrener Engineer antwortet innerhalb eines Werktags schriftlich.

Über Ihre Infrastruktur sprechen