Veröffentlicht am

Kubernetes Init Containers: Setup vor dem Haupt-Container

Teilen:
Authors

Kubernetes Init Containers: Initialisierung und Setup vor dem Haupt-Container

TL;DR

  • Init Containers laufen vor dem Haupt-Container, sequenziell und muessen erfolgreich beenden, bevor der naechste startet.
  • Typische Einsatzfelder: Warten auf Datenbank-Verfuegbarkeit, Konfig-Dateien herunterladen, Permissions setzen, Schema-Migrationen.
  • Jeder Init Container hat eigene Resource Requests und Limits -- der hoechste Wert zaehlt fuer das Scheduling.
  • Debugging mit kubectl logs <pod> -c <init-container-name> und kubectl describe pod.
  • Seit Kubernetes 1.28 gibt es native Sidecar Containers (restartPolicy: Always in initContainers), die neben dem Haupt-Container weiterlaufen.

Was sind Init Containers?

Init Containers sind spezielle Container in einem Pod, die vor den eigentlichen Anwendungs-Containern starten. Sie erfuellen Setup-Aufgaben, die abgeschlossen sein muessen, bevor die Anwendung startet.

Die Regeln sind klar:

  1. Init Containers laufen sequenziell -- einer nach dem anderen, nicht parallel.
  2. Jeder Init Container muss mit Exit Code 0 beenden, bevor der naechste startet.
  3. Wenn ein Init Container fehlschlaegt, startet Kubernetes den gesamten Pod neu (je nach restartPolicy).
  4. Init Containers unterstuetzen keine Liveness-, Readiness- oder Startup-Probes.
  5. Init Containers koennen andere Images verwenden als der Haupt-Container.
Pod-Start:
  Init Container 1  */}  Init Container 2  */}  App Container
  (muss erfolgreich)     (muss erfolgreich)     (laeuft dauerhaft)

Der letzte Punkt ist besonders nuetzlich: Sie koennen ein minimales Alpine-Image fuer Setup-Tasks verwenden, waehrend der Haupt-Container ein schlankes Distroless-Image nutzt.

Praxis-Beispiel 1: Warten auf Datenbank

Das haeufigste Pattern: Der Anwendungs-Container braucht eine Datenbankverbindung, aber die Datenbank ist noch nicht bereit.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api-server
  template:
    metadata:
      labels:
        app: api-server
    spec:
      initContainers:
      - name: wait-for-postgres
        image: busybox:1.36
        command:
        - sh
        - -c
        - |
          echo "Warte auf PostgreSQL..."
          until nc -z postgresql.database.svc.cluster.local 5432; do
            echo "PostgreSQL nicht erreichbar, warte 2 Sekunden..."
            sleep 2
          done
          echo "PostgreSQL ist bereit."
        resources:
          requests:
            cpu: 50m
            memory: 32Mi
          limits:
            cpu: 100m
            memory: 64Mi
      containers:
      - name: api-server
        image: registry.internal/api-server:2.5.0
        ports:
        - containerPort: 8080
        env:
        - name: DB_HOST
          value: "postgresql.database.svc.cluster.local"
        - name: DB_PORT
          value: "5432"
        resources:
          requests:
            cpu: 250m
            memory: 256Mi
          limits:
            cpu: 500m
            memory: 512Mi

Ohne den Init Container wuerde der API-Server starten, die Datenbankverbindung versuchen, fehlschlagen und in einen CrashLoopBackOff geraten. Mit dem Init Container wartet der Pod sauber, bis PostgreSQL erreichbar ist.

Wer CrashLoopBackOff-Probleme systematisch debuggen moechte, findet einen ausfuehrlichen Workflow im Beitrag zu CrashLoopBackOff Troubleshooting.

Praxis-Beispiel 2: Warten auf mehrere Services

Manchmal muss die Anwendung auf mehrere Abhaengigkeiten warten -- Datenbank, Cache und Message Queue:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      initContainers:
      - name: wait-for-postgres
        image: busybox:1.36
        command:
        - sh
        - -c
        - |
          until nc -z postgresql.database.svc.cluster.local 5432; do
            echo "Warte auf PostgreSQL..."
            sleep 2
          done
          echo "PostgreSQL bereit."
        resources:
          requests:
            cpu: 50m
            memory: 32Mi
          limits:
            cpu: 100m
            memory: 64Mi
      - name: wait-for-redis
        image: busybox:1.36
        command:
        - sh
        - -c
        - |
          until nc -z redis.cache.svc.cluster.local 6379; do
            echo "Warte auf Redis..."
            sleep 2
          done
          echo "Redis bereit."
        resources:
          requests:
            cpu: 50m
            memory: 32Mi
          limits:
            cpu: 100m
            memory: 64Mi
      - name: wait-for-rabbitmq
        image: busybox:1.36
        command:
        - sh
        - -c
        - |
          until nc -z rabbitmq.messaging.svc.cluster.local 5672; do
            echo "Warte auf RabbitMQ..."
            sleep 2
          done
          echo "RabbitMQ bereit."
        resources:
          requests:
            cpu: 50m
            memory: 32Mi
          limits:
            cpu: 100m
            memory: 64Mi
      containers:
      - name: order-service
        image: registry.internal/order-service:3.1.0
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: 500m
            memory: 512Mi
          limits:
            cpu: "1"
            memory: 1Gi

Die Init Containers laufen nacheinander: Erst PostgreSQL, dann Redis, dann RabbitMQ. Erst wenn alle drei verfuegbar sind, startet der Order-Service.

Praxis-Beispiel 3: Konfig-Dateien herunterladen

Init Containers koennen Konfigurationsdateien von einem zentralen Config-Service oder S3-Bucket herunterladen und ueber ein Shared Volume dem Haupt-Container bereitstellen:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-with-config
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-custom
  template:
    metadata:
      labels:
        app: nginx-custom
    spec:
      initContainers:
      - name: download-config
        image: curlimages/curl:8.5.0
        command:
        - sh
        - -c
        - |
          echo "Lade Nginx-Konfiguration herunter..."
          curl -sSL https://config.internal.corp/nginx/default.conf \
            -o /config/default.conf
          curl -sSL https://config.internal.corp/nginx/ssl.conf \
            -o /config/ssl.conf
          echo "Konfiguration geladen."
          ls -la /config/
        volumeMounts:
        - name: nginx-config
          mountPath: /config
        resources:
          requests:
            cpu: 50m
            memory: 32Mi
          limits:
            cpu: 100m
            memory: 64Mi
      containers:
      - name: nginx
        image: nginx:1.25
        ports:
        - containerPort: 80
        - containerPort: 443
        volumeMounts:
        - name: nginx-config
          mountPath: /etc/nginx/conf.d
          readOnly: true
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 500m
            memory: 256Mi
      volumes:
      - name: nginx-config
        emptyDir: {}

Das emptyDir-Volume wird von beiden Containern geteilt. Der Init Container schreibt, der Haupt-Container liest.

Praxis-Beispiel 4: Permissions und Verzeichnisse vorbereiten

Manche Anwendungen laufen als Non-Root-User und brauchen bestimmte Verzeichnisse mit korrekten Permissions:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: elasticsearch
spec:
  serviceName: elasticsearch
  replicas: 3
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      initContainers:
      - name: fix-permissions
        image: busybox:1.36
        command:
        - sh
        - -c
        - |
          chown -R 1000:1000 /usr/share/elasticsearch/data
          chmod 750 /usr/share/elasticsearch/data
        securityContext:
          runAsUser: 0
          privileged: false
        volumeMounts:
        - name: data
          mountPath: /usr/share/elasticsearch/data
        resources:
          requests:
            cpu: 50m
            memory: 32Mi
          limits:
            cpu: 100m
            memory: 64Mi
      - name: increase-vm-max-map
        image: busybox:1.36
        command:
        - sysctl
        - -w
        - vm.max_map_count=262144
        securityContext:
          privileged: true
        resources:
          requests:
            cpu: 50m
            memory: 32Mi
          limits:
            cpu: 100m
            memory: 64Mi
      containers:
      - name: elasticsearch
        image: docker.elastic.co/elasticsearch/elasticsearch:8.12.0
        ports:
        - containerPort: 9200
        - containerPort: 9300
        securityContext:
          runAsUser: 1000
          runAsGroup: 1000
        volumeMounts:
        - name: data
          mountPath: /usr/share/elasticsearch/data
        resources:
          requests:
            cpu: "1"
            memory: 2Gi
          limits:
            cpu: "2"
            memory: 4Gi
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 50Gi

Zwei Init Containers: Der erste setzt die Verzeichnis-Permissions, der zweite erhoeht den vm.max_map_count Kernel-Parameter, den Elasticsearch benoetigt. Beide muessen als Root laufen, waehrend Elasticsearch selbst als User 1000 laeuft.

Praxis-Beispiel 5: Datenbank-Migrationen

Schema-Migrationen vor dem Anwendungsstart sind ein klassischer Init-Container-Einsatz:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      initContainers:
      - name: run-migrations
        image: registry.internal/web-app:2.5.0
        command:
        - python
        - manage.py
        - migrate
        ---noinput
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: web-app-secrets
              key: database-url
        resources:
          requests:
            cpu: 200m
            memory: 256Mi
          limits:
            cpu: 500m
            memory: 512Mi
      containers:
      - name: web-app
        image: registry.internal/web-app:2.5.0
        ports:
        - containerPort: 8000
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: web-app-secrets
              key: database-url
        resources:
          requests:
            cpu: 500m
            memory: 512Mi
          limits:
            cpu: "1"
            memory: 1Gi

Achtung bei Replicas: Bei mehreren Replicas laufen die Migrationen in jedem Pod. Die meisten Migrations-Frameworks (Django, Rails, Flyway) sind idempotent und verwenden Locking, sodass das kein Problem ist. Pruefen Sie aber, ob Ihr Framework das unterstuetzt.

Resource Requests fuer Init Containers

Init Containers haben eigene Resource Requests und Limits. Der Scheduler berechnet die Pod-Ressourcen wie folgt:

Effektive CPU Requests = max(
  hoechster Init Container CPU Request,
  Summe aller App Container CPU Requests
)

Ein Beispiel:

initContainers:
- name: init-1
  resources:
    requests:
      cpu: 100m      # Init 1
      memory: 128Mi
- name: init-2
  resources:
    requests:
      cpu: 500m      # Init 2 (hoechster Init)
      memory: 256Mi

containers:
- name: app
  resources:
    requests:
      cpu: 250m      # App Container
      memory: 512Mi

Effektive Requests fuer Scheduling:

  • CPU: max(500m, 250m) = 500m
  • Memory: max(256Mi, 512Mi) = 512Mi

Das bedeutet: Ein Init Container mit hohen Resource Requests kann dazu fuehren, dass der Pod auf einem groesseren Node gescheduled wird, obwohl der Haupt-Container weniger braucht. Halten Sie Init Container daher so schlank wie moeglich.

Mehr Details zum Resource Management finden Sie im Beitrag zu Kubernetes Resource Management.

Init Containers debuggen

Wenn ein Pod im Status Init:0/2 oder Init:CrashLoopBackOff haengt, helfen diese Befehle:

# Status aller Container (Init + App) anzeigen
kubectl describe pod my-pod

# Beispiel-Output:
# Init Containers:
#   wait-for-postgres:
#     State:          Running
#     Started:        Wed, 10 Feb 2026 10:15:00 +0100
#   run-migrations:
#     State:          Waiting
#     Reason:         PodInitializing

# Logs eines bestimmten Init Containers
kubectl logs my-pod -c wait-for-postgres

# Logs des vorherigen (gecrashteten) Init Containers
kubectl logs my-pod -c run-migrations --previous

# Events fuer den Pod
kubectl get events --field-selector involvedObject.name=my-pod --sort-by='.lastTimestamp'

Init Container haengt endlos

Wenn ein Init Container nie beendet (z.B. weil der Service nie erreichbar wird), bleibt der Pod im Status Init:0/1. Setzen Sie immer ein Timeout:

initContainers:
- name: wait-for-service
  image: busybox:1.36
  command:
  - sh
  - -c
  - |
    TIMEOUT=120
    ELAPSED=0
    until nc -z my-service.default.svc.cluster.local 8080; do
      if [ $ELAPSED -ge $TIMEOUT ]; then
        echo "FEHLER: Timeout nach ${TIMEOUT}s. Service nicht erreichbar."
        exit 1
      fi
      echo "Warte... (${ELAPSED}s/${TIMEOUT}s)"
      sleep 2
      ELAPSED=$((ELAPSED + 2))
    done
    echo "Service erreichbar."
  resources:
    requests:
      cpu: 50m
      memory: 32Mi
    limits:
      cpu: 100m
      memory: 64Mi

Fuer allgemeines Kubernetes-Troubleshooting empfehle ich den Beitrag zu Kubernetes Health Checks und Probes.

Anti-Patterns: Was Sie vermeiden sollten

Zu viele Init Containers

Jeder Init Container verlaengert die Pod-Startzeit. Fuenf Init Containers, die jeweils 10 Sekunden brauchen, ergeben 50 Sekunden Verzoegerung -- bei jedem Pod-Start, bei jedem Rolling Update.

# Schlecht: 5 separate Wait-Container
initContainers:
  - name: wait-for-postgres    # 5s
  - name: wait-for-redis       # 3s
  - name: wait-for-rabbitmq    # 4s
  - name: wait-for-config      # 8s
  - name: wait-for-vault       # 6s
  # Gesamt: 26s Minimum vor dem App-Start

Besser: Kombinieren Sie mehrere Checks in einem Init Container:

initContainers:
- name: wait-for-dependencies
  image: busybox:1.36
  command:
  - sh
  - -c
  - |
    echo "Pruefe Abhaengigkeiten..."
    until nc -z postgresql.database.svc.cluster.local 5432; do sleep 1; done
    echo "PostgreSQL OK"
    until nc -z redis.cache.svc.cluster.local 6379; do sleep 1; done
    echo "Redis OK"
    until nc -z rabbitmq.messaging.svc.cluster.local 5672; do sleep 1; done
    echo "RabbitMQ OK"
    echo "Alle Abhaengigkeiten bereit."
  resources:
    requests:
      cpu: 50m
      memory: 32Mi
    limits:
      cpu: 100m
      memory: 64Mi

Lang laufende Init Containers

Init Containers sind fuer kurze Setup-Tasks gedacht. Wenn ein Init Container Minuten braucht (z.B. ein grosser Datenbank-Dump), ueberdenken Sie Ihre Architektur. Moegliche Alternativen:

  • Kubernetes Jobs fuer einmalige Batch-Tasks
  • Persistent Volumes fuer Daten, die nicht bei jedem Start neu geladen werden muessen
  • Readiness Gates fuer komplexe Startup-Checks

Init Container als Sidecar missbrauchen

Ein Init Container beendet sich und laeuft nicht weiter. Wenn Sie einen dauerhaft laufenden Helfer-Prozess brauchen (z.B. Log-Forwarding, Config-Sync), nutzen Sie einen Sidecar-Container oder seit Kubernetes 1.28 native Sidecar Containers.

Native Sidecar Containers ab Kubernetes 1.28

Seit Kubernetes 1.28 gibt es native Sidecar Containers. Diese werden als Init Containers definiert, aber mit restartPolicy: Always:

apiVersion: v1
kind: Pod
metadata:
  name: app-with-sidecar
spec:
  initContainers:
  # Normaler Init Container -- laeuft einmal und beendet sich
  - name: setup
    image: busybox:1.36
    command:
    - sh
    - -c
    - echo "Setup abgeschlossen"
    resources:
      requests:
        cpu: 50m
        memory: 32Mi
      limits:
        cpu: 100m
        memory: 64Mi

  # Nativer Sidecar -- laeuft dauerhaft neben dem Haupt-Container
  - name: log-forwarder
    image: fluent/fluent-bit:2.2
    restartPolicy: Always
    volumeMounts:
    - name: app-logs
      mountPath: /var/log/app
    resources:
      requests:
        cpu: 100m
        memory: 64Mi
      limits:
        cpu: 200m
        memory: 128Mi

  containers:
  - name: app
    image: registry.internal/my-app:1.0.0
    ports:
    - containerPort: 8080
    volumeMounts:
    - name: app-logs
      mountPath: /var/log/app
    resources:
      requests:
        cpu: 250m
        memory: 256Mi
      limits:
        cpu: 500m
        memory: 512Mi

  volumes:
  - name: app-logs
    emptyDir: {}

Die Vorteile nativer Sidecars gegenueber regulaeren Containern:

  • Definierte Startreihenfolge: Der Sidecar startet vor dem Haupt-Container (wie ein Init Container).
  • Definiertes Shutdown: Der Sidecar wird nach dem Haupt-Container gestoppt.
  • Kein Race Condition: Der Haupt-Container startet erst, wenn der Sidecar bereit ist.

Wer mehr ueber das Sidecar Pattern und weitere Multi-Container-Patterns erfahren moechte, findet detaillierte Beispiele in unseren Beitraegen zum Kubernetes Adapter Pattern und zum Kubernetes Ambassador Pattern.

Init Containers in der Praxis: Checkliste

Bevor Sie Init Containers in Ihre Deployments einbauen, pruefen Sie folgende Punkte:

# 1. Brauche ich wirklich einen Init Container?
#    Oder reicht ein Startup Probe / Readiness Probe?

# 2. Hat der Init Container ein Timeout?
#    Endlos-Loops ohne Timeout sind gefaehrlich.

# 3. Sind Resource Requests gesetzt?
#    Init Containers ohne Requests koennten OOMKilled werden.

# 4. Ist das Image schlank?
#    busybox (1.4 MB) statt ubuntu (72 MB).

# 5. Logge ich genug fuer Debugging?
#    echo-Statements helfen bei der Fehlersuche.

# 6. Wie wirkt sich die Startzeit auf Rolling Updates aus?
#    Kalkulieren Sie die Init-Container-Zeit in die Update-Strategie ein.

Zusammenfassung

Init Containers sind ein essentielles Kubernetes-Feature fuer zuverlaessige Anwendungsstarts. Sie stellen sicher, dass Abhaengigkeiten verfuegbar sind, Konfigurationen geladen und Permissions gesetzt sind, bevor der Haupt-Container startet. Mit den nativen Sidecar Containers seit Kubernetes 1.28 schliessen Init Containers auch die Luecke zu dauerhaft laufenden Helfer-Prozessen.

Halten Sie Init Containers schlank, setzen Sie immer Timeouts und Resource Requests, und kombinieren Sie verwandte Checks in einem einzigen Init Container, um die Pod-Startzeit nicht unnoetig zu verlaengern.

Verwandte Artikel

Wenn Sie Unterstuetzung bei der Optimierung Ihrer Kubernetes-Deployments brauchen, sprechen Sie uns an unter /kontakt.

Kubernetes-Beratung gesucht?

Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen