Kubernetes Grundlagen: Pod, Deployment und Service

1. Überblick

Kubernetes hat dutzende Resource-Typen, aber 80 % der täglichen Arbeit dreht sich um drei Konzepte: Pod, Deployment, Service. Dieser Artikel hilft dir, das Wesen jeder Resource zu verstehen, wie sie zusammenhängen, und wie du ein Bündel YAML-Manifeste schreibst, um eine echte Anwendung im Cluster zum Laufen zu bringen.

Danach kannst du selbst eine Spring-Boot-Anwendung auf Kubernetes deployen, sie auf mehrere Replicas skalieren und über Service nach außen exponieren – alles nur mit kubectl und ein paar YAML-Dateien.

Hinweis: Dieser Artikel setzt voraus, dass du bereits einen Cluster hast (Minikube, Kind oder Cloud) und kubectl auf den richtigen Context zeigt.

2. Pod – kleinste Bereitstellungseinheit

2.1. Was ist ein Pod?

Ein Pod ist eine Gruppe von einem oder mehreren Containern, die sich einen Network-Namespace (gleiche IP, gleicher Port-Bereich) und Volumes teilen. In der Praxis enthalten die meisten Pods nur einen Application-Container.

apiVersion: v1
kind: Pod
metadata:
  name: order-service-pod
  labels:
    app: order-service
spec:
  containers:
    - name: order-service
      image: nonladev/order-service:1.0
      ports:
        - containerPort: 8080
      resources:
        requests:
          cpu: "250m"
          memory: "256Mi"
        limits:
          cpu: "500m"
          memory: "512Mi"

Erklärung:

  • metadata.labels vergeben das Label app: order-service – so „erkennt” Kubernetes verschiedene Ressourcen wieder, und Service sowie Deployment nutzen dieses Label, um verwandte Objekte auszuwählen.
  • resources.requests sind die minimalen Ressourcen, die der Scheduler haben muss, um den Pod auf einem Node zu platzieren; limits sind die Obergrenzen – bei Überschreitung werden Ressourcen gedrosselt (CPU) oder der Pod wird beendet (Memory – OOMKilled).
  • containerPort: 8080 ist nur eine Deklaration zu Informationszwecken; sie öffnet den Port nicht automatisch nach außen.

Anwenden und überprüfen:

kubectl apply -f order-service-pod.yaml
kubectl get pods
NAME                 READY   STATUS    RESTARTS   AGE
order-service-pod    1/1     Running   0          5s

2.2. Warum sollte man keine Pods direkt erstellen?

Pods, die wie oben „nackt” erstellt werden, haben ein großes Problem: Wenn der Node, der den Pod enthält, abstürzt, oder wenn der Pod crasht, erstellt ihn niemand automatisch neu. Ein Pod hat keinen Self-Healing-Mechanismus und keine Skalierungsfunktion.

Hinweis: In Production-Umgebungen solltest du Pods fast nie direkt erstellen. Nutze immer einen Deployment (oder StatefulSet, Job), um Pods zu verwalten.

3. Deployment – Verwaltung des Pod-Lebenszyklus

3.1. Einen Deployment deklarieren

Ein Deployment erstellt und verwaltet eine Menge von Pods über einen ReplicaSet, stellt sicher, dass immer die richtige Anzahl von Replicas läuft, und unterstützt Rolling Updates ohne Downtime.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: nonladev/order-service:1.0
          ports:
            - containerPort: 8080
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 10

Erklärung:

  • replicas: 3 erfordert, dass der Deployment immer 3 Pods am Laufen hält – wenn ein Pod stirbt, erstellt der Deployment automatisch einen neuen Pod als Ersatz.
  • selector.matchLabels muss mit template.metadata.labels übereinstimmen. Das ist die obligatorische Verbindung zwischen Deployment und den von ihm verwalteten Pods.
  • readinessProbe bestimmt, ob ein Pod Traffic erhalten soll oder nicht (wird vom Service verwendet); livenessProbe entscheidet, ob ein Pod neu gestartet werden muss. Diese beiden Probes zu verwechseln ist ein sehr häufiger Fehler für Kubernetes-Anfänger.

Anwenden und beobachten:

kubectl apply -f order-service-deployment.yaml
kubectl get deployments
kubectl get pods -l app=order-service
NAME            READY   UP-TO-DATE   AVAILABLE   AGE
order-service   3/3     3            3           12s

NAME                             READY   STATUS    RESTARTS   AGE
order-service-7d4f9c8b6d-2kx9p   1/1     Running   0          12s
order-service-7d4f9c8b6d-7h2mn   1/1     Running   0          12s
order-service-7d4f9c8b6d-xq1lw   1/1     Running   0          12s

3.2. Skalierung und Rolling Update

Scale erhöhen/reduzieren, ohne YAML zu bearbeiten:

kubectl scale deployment order-service --replicas=5

Rolling Update beim Ändern des Image – der Deployment ersetzt alte Pods automatisch durch neue gemäß einer Rollierungs-Strategie, ohne Downtime:

kubectl set image deployment/order-service order-service=nonladev/order-service:1.1
kubectl rollout status deployment/order-service
Waiting for deployment "order-service" rollout to finish: 2 out of 3 new replicas have been updated...
deployment "order-service" successfully rolled out

Wenn die neue Version einen Bug hat, schnell auf die vorherige Version zurückrollen:

kubectl rollout undo deployment/order-service

3.3. Rolling-Update-Strategie konfigurieren

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1

maxUnavailable: 1 erlaubt maximal 1 Pod, während des Updates „ausfallen”; maxSurge: 1 erlaubt, maximal 1 zusätzliche Pod über die deklarierten Replicas hinaus zu erstellen. Mit replicas: 3 wird der Cluster während der Umstellung 2–4 Pods ausführen, aber niemals weniger als 2.

4. Service – stabile Pod-Exposition

4.1. Warum ist ein Service nötig?

Pods haben ihre eigene IP, aber diese IP ändert sich, wenn Pods neu erstellt werden (Crash, Rolling Update, Skalierung). Ein Service bietet eine stabile Adresse (ClusterIP + DNS-Name), die vor einer Gruppe von Pods steht, und verteilt Traffic automatisch basierend auf Label-Selektoren – unabhängig davon, wie oft sich die Pods darunter ändern.

apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
    - port: 80
      targetPort: 8080
  type: ClusterIP

Erklärung:

  • selector.app: order-service passt zu dem Label auf den Pods (das vom Deployment in template.metadata.labels gesetzt wird) – der Service findet und verfolgt diese Pods automatisch über ein zwischengelagertes Objekt namens Endpoints.
  • port: 80 ist der Port, auf dem der Service innerhalb des Clusters lauscht; targetPort: 8080 ist der Port, den der Container tatsächlich freigibt. Diese beiden Zahlen müssen nicht gleich sein.
  • type: ClusterIP (Standardwert) ist nur von innerhalb des Clusters erreichbar – ideal für interne Services zwischen Microservices.

Service und Endpoints überprüfen:

kubectl apply -f order-service-service.yaml
kubectl get svc order-service
kubectl get endpoints order-service
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
order-service   ClusterIP   10.96.142.201   <none>        80/TCP    4s

NAME            ENDPOINTS                                   AGE
order-service   10.244.1.5:8080,10.244.1.6:8080,+1 more     4s

4.2. Service-Typen

Type Zugriffsbereich Verwendung
ClusterIP Nur innerhalb des Clusters Kommunikation zwischen internen Microservices
NodePort Fester Port (30000–32767) auf allen Nodes Schnelle Tests, Dev-/On-Prem-Umgebung
LoadBalancer Public-IP vom Cloud-Provider Service ins Internet auf der Cloud freilegen

Beispiel: Schnelle Freigabe für Tests auf Minikube:

apiVersion: v1
kind: Service
metadata:
  name: order-service-external
spec:
  selector:
    app: order-service
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30080
  type: NodePort
minikube service order-service-external --url

Hinweis: NodePort für Production zu verwenden, ist ein Anti-Pattern – der Port-Bereich ist begrenzt, es gibt keine TLS-Terminierung oder health-basiertes Routing wie bei Ingress. Für Production in der Cloud bevorzuge LoadBalancer oder einen Ingress-Controller.

5. Alle drei in einer echten Anwendung kombinieren

Die gesamte Pipeline: Client ruft den Service auf → Service verteilt zu Pods, die vom Deployment verwaltet werden.

Client
  │
  ▼
Service (order-service, ClusterIP 10.96.142.201:80)
  │  (label selector: app=order-service)
  ▼
┌─────────────┬─────────────┬─────────────┐
│  Pod #1     │  Pod #2     │  Pod #3     │
│  :8080      │  :8080      │  :8080      │
└─────────────┴─────────────┴─────────────┘
       ▲ verwaltet durch
  Deployment (order-service, replicas: 3)

Zur Vereinfachung kannst du den Deployment und Service in einer Datei zusammenfassen und durch --- trennen:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: nonladev/order-service:1.0
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
    - port: 80
      targetPort: 8080
kubectl apply -f order-service-all.yaml

6. Tests für Readiness-Endpoint vor dem Deployment schreiben

Bevor die Probe von Kubernetes sich auf /actuator/health/readiness stützt, solltest du im Spring-Boot-Code sicherstellen, dass dieser Endpoint den korrekten Status zurückgibt.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management.endpoint.health.probes.enabled=true
management.health.readiness-state.enabled=true
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class OrderServiceHealthTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void readinessProbe_shouldReturnUp() {
        ResponseEntity<String> response =
                restTemplate.getForEntity("/actuator/health/readiness", String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).contains("\"status\":\"UP\"");
    }
}

Dieser Test ruft den echten Readiness-Endpoint über TestRestTemplate auf und bestätigt HTTP 200 sowie einen Body mit UP. Das ist genau der Endpoint, den die readinessProbe im Deployment regelmäßig aufruft – wenn der Test hier fehlschlägt, wird der Pod im Cluster nie „Ready”.

7. Häufige Fehler

  • Selector passt nicht zu Labels: Service oder Deployment findet keine Pods (Endpoints leer), weil selector und labels falsch geschrieben oder unterschiedlich sind. Immer kubectl get endpoints <svc-name> ausführen, um das zu überprüfen.
  • Fehlende readinessProbe: Der Service leitet Traffic an Pods, deren Anwendung noch nicht vollständig gestartet ist, verursacht 502/503-Fehler unmittelbar nach dem Deployment.
  • Fehlende Resource-Anforderungen/Limits: Der Scheduler könnte den Pod auf dem falschen Node platzieren oder der Pod wird unerwartet OOMKilled bei erhöhtem Traffic.
  • „Nackte” Pods für Production: kein Self-Healing, wenn ein Pod weg ist, ist er weg, du musst ihn manuell neu erstellen.
  • Image-Tag latest: Rolling Updates erkennen Änderungen nicht, Kubernetes denkt, das Image hat sich nicht geändert, und zieht es nicht erneut. Nutze immer ein spezifisches Version-Tag.

8. Fazit

Ein Pod ist die kleinste Container-Ausführungseinheit, ein Deployment verwaltet den Lebenszyklus und die Skalierung einer Menge von Pods, und ein Service bietet eine stabile Adresse, um auf sie zuzugreifen, obwohl sich die Pods darunter ständig ändern. Die Beherrschung dieser drei Bausteine ist die Grundlage, um später zu Ingress, ConfigMap/Secret oder Helm Charts fortzuschreiten. Nächste Schritte zum Ausprobieren: Deploye eine echte Spring-Boot-Anwendung, beobachte das Rolling Update und versuche, einen Pod manuell zu löschen, um zu sehen, wie sich der Deployment selbst wiederherstellt.

kubernetesdevopscontainerorchestration