Kubernetes init containers: setuptaken uitvoeren voor je applicatie start

Init containers draaien sequentieel tot voltooiing voordat je applicatiecontainer start. Ze zijn het ingebouwde Kubernetes-mechanisme voor dependency checks, databasemigraties, config ophalen en filesysteem-voorbereiding. Deze gids loopt door elke use case met productiewaardige manifesten, behandelt failure handling en debugging, en legt uit wanneer het nieuwere sidecar-containerpatroon beter past.

Wat je aan het einde hebt

Een Deployment-manifest met een of meer init containers die setuptaken uitvoeren (wachten op een dependency, databasemigraties draaien, configuratie ophalen, filesysteem-permissies fixen) voordat de applicatiecontainer start. Je weet ook hoe je falende init containers debugt en wanneer je beter sidecar containers kunt gebruiken.

Vereisten

  • Een draaiend Kubernetes-cluster (v1.28 of nieuwer voor sidecar-container-support; elke v1.6+ cluster voor gewone init containers)
  • kubectl geconfigureerd en verbonden met het cluster
  • Bekendheid met Deployment- en Pod-specs
  • Voor resource sizing op init containers, zie Kubernetes resource requests en limits

Wat init containers doen

Init containers zijn speciale containers in de initContainers-array van een pod spec. Ze draaien sequentieel tot voltooiing voordat een applicatiecontainer start. Elke init container moet afsluiten met exit code 0 voor de volgende begint. Faalt er een, dan herstart de kubelet hem volgens het restartPolicy van de pod.

Drie eigenschappen maken ze bruikbaar:

  • Ander image. Een init container kan busybox, mysql-client of vault gebruiken zonder de applicatie-image op te blazen.
  • Blokkeergarantie. De hoofdcontainers starten pas als elke init container geslaagd is. Geen race conditions.
  • Sequentiele volgorde. Init containers draaien in declaratievolgorde, dus elke container kan bouwen op de side-effects van de vorige.

Init containers ondersteunen geen livenessProbe, readinessProbe of startupProbe. Ze draaien tot voltooiing; het zijn geen langlopende processen.

Wachten op een dependency

Docker Compose gebruikt depends_on: condition: service_healthy om het starten van een container te blokkeren tot een andere service gezond is. Kubernetes heeft geen equivalent veld op pod-spec-niveau. Als je van Docker Compose migreert, bekijk dan de volledige migratietutorial voor de bredere context.

Een init container met een retry-loop vult dit gat:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: api-server
  template:
    metadata:
      labels:
        app: api-server
    spec:
      initContainers:
      - name: wait-for-postgres
        image: busybox:1.37
        command:
        - sh
        - -c
        - |
          until nc -z postgres-service 5432; do
            echo "Wachten op PostgreSQL op postgres-service:5432..."
            sleep 2
          done
      containers:
      - name: api
        image: mycompany/api-server:3.2.0
        ports:
        - containerPort: 8080

nc -z probeert een TCP-verbinding zonder data te versturen. Zodra PostgreSQL verbindingen accepteert, sluit de init container af met exit 0 en start de api-container.

Voor DNS-checks (wachten tot een Service bestaat in het cluster) gebruik je nslookup:

until nslookup postgres-service.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do
  echo "Wachten op DNS..."
  sleep 2
done

Dit werkt alleen binnen een pod. Cross-pod startup ordering (zorgen dat Pod A draait voordat Pod B wordt aangemaakt) vereist externe orkestratie: Helm hooks, Argo CD sync waves, of retry-logica op applicatieniveau.

Databasemigraties draaien

Een init container kan je migratietool draaien voordat de applicatie opstart, zodat het schema gegarandeerd klopt:

initContainers:
- name: db-migrate
  image: mycompany/api-migrations:3.2.0   # apart migration-image
  command: ["./migrate", "--target=latest"]
  env:
  - name: DATABASE_URL
    valueFrom:
      secretKeyRef:
        name: db-credentials
        key: url
  resources:
    requests:
      cpu: "250m"
      memory: "256Mi"
    limits:
      memory: "512Mi"

De concurrency-val. Wanneer een Deployment opschaalt naar meerdere replica's, draait elke pod zijn init containers onafhankelijk. Drie replica's betekent drie gelijktijdige migratieruns tegen dezelfde database. Migratietools als Atlas, Flyway en Liquibase gebruiken advisory locks om dit veilig te maken, maar niet alle tools doen dat. Ondersteunt jouw migratietool geen locking, gebruik dan een Kubernetes Job met parallelism: 1. Jobs bieden ook betere log-retentie en ontkoppelde rollback.

Voeg een timeout toe om hangende migraties af te vangen:

timeout 300 ./migrate --target=latest

Zonder timeout blokkeert een vastgelopen migratie de pod-startup oneindig. De pod toont dan Init:0/1 tot in de eeuwigheid.

Configuratie ophalen van een externe bron

Init containers kunnen configuratie ophalen uit Vault, AWS Secrets Manager of een remote config-service en die naar een gedeeld volume schrijven. De applicatiecontainer leest bij startup van dat volume.

initContainers:
- name: fetch-config
  image: vault:1.18
  command:
  - sh
  - -c
  - |
    vault login -method=kubernetes role=api-server
    vault kv get -field=config secret/api-server > /config/app.conf
  env:
  - name: VAULT_ADDR
    value: "https://vault.internal:8200"
  volumeMounts:
  - name: config-vol
    mountPath: /config
containers:
- name: api
  image: mycompany/api-server:3.2.0
  volumeMounts:
  - name: config-vol
    mountPath: /config
    readOnly: true
volumes:
- name: config-vol
  emptyDir: {}

Voor gevoelige data gebruik je emptyDir met medium: Memory, zodat de config nooit de disk raakt:

volumes:
- name: config-vol
  emptyDir:
    medium: Memory   # RAM-backed tmpfs

Native Kubernetes Secrets zijn base64-encoded, standaard niet versleuteld at rest. Voor multi-tenant clusters of compliance-gevoelige workloads is secrets ophalen via een init container vanuit een dedicated vault een sterker patroon.

Data delen via emptyDir-volumes

Het emptyDir-volume is het standaardmechanisme om data door te geven van een init container naar de applicatiecontainer. De lifecycle is simpel:

  1. Declareer een emptyDir-volume in de pod spec.
  2. Mount het in zowel de init container als de applicatiecontainer.
  3. De init container schrijft bestanden. Hij sluit af.
  4. De applicatiecontainer leest die bestanden.
  5. Het volume wordt vernietigd wanneer de pod stopt.

Omdat init containers klaar zijn voordat applicatiecontainers starten, heb je een ingebouwde timinggarantie: de applicatie leest nooit een half geschreven bestand.

Compleet voorbeeld dat content downloadt in een nginx-container:

apiVersion: v1
kind: Pod
metadata:
  name: content-init-demo
spec:
  initContainers:
  - name: download-content
    image: busybox:1.37
    command:
    - wget
    - "-O"
    - "/work-dir/index.html"
    - "http://info.cern.ch"
    volumeMounts:
    - name: workdir
      mountPath: /work-dir
  containers:
  - name: nginx
    image: nginx:1.27
    ports:
    - containerPort: 80
    volumeMounts:
    - name: workdir
      mountPath: /usr/share/nginx/html
  volumes:
  - name: workdir
    emptyDir: {}

Bron: Kubernetes configure pod initialization tutorial

Filesysteem-permissies fixen

Init containers kunnen als root draaien om ownership of permissies op een volume te corrigeren, ook als de applicatiecontainer als non-root draait:

initContainers:
- name: fix-permissions
  image: busybox:1.37
  command: ['sh', '-c', 'chown -R 1000:1000 /data']
  volumeMounts:
  - name: data-vol
    mountPath: /data
  securityContext:
    runAsUser: 0   # root, alleen tijdens init
containers:
- name: app
  image: mycompany/api-server:3.2.0
  securityContext:
    runAsUser: 1000
    runAsNonRoot: true
  volumeMounts:
  - name: data-vol
    mountPath: /data

Root-toegang is beperkt tot de init container. De applicatiecontainer draait zonder privileges.

Volgorde en failure handling

Init containers draaien in declaratievolgorde. Geen parallelisme. De kubelet wacht tot elke container afsluit met exit 0 voordat de volgende start.

De pod-status weerspiegelt de voortgang van init containers:

Status Betekenis
Init:0/3 Geen init containers afgerond
Init:1/3 Eerste afgerond, tweede draait
Init:Error Een init container sloot af met non-zero exit code
Init:CrashLoopBackOff Een init container faalt herhaaldelijk

Wat er bij een fout gebeurt, hangt af van het restartPolicy van de pod:

restartPolicy Gedrag bij init container failure
Always (Deployment-standaard) Kubelet herstart de falende init container met exponential backoff (10s, 20s, 40s, tot 5 min)
OnFailure Zelfde herstart met backoff
Never Pod wordt Failed, geen herstart

De kubelet herstart alleen de falende init container, niet de hele reeks. Init containers die al geslaagd zijn, draaien niet opnieuw.

Een pod die vastzit in Init:CrashLoopBackOff is iets anders dan een pod in ContainerCreating. De ContainerCreating-status betekent dat init containers al klaar zijn en de kubelet bezig is met de prerequisites van de hoofdcontainer (volumes, netwerk). Zie je ContainerCreating, bekijk dan pods debuggen die vasthangen in ContainerCreating.

Falende init containers debuggen

Begin met kubectl describe pod om Events en de init container-status te bekijken:

kubectl describe pod api-server-7f8b4c5d6-x9k2m

Zoek naar de Init Containers:-sectie in de output. Daar zie je de state, exit code en restart count van elke init container.

Logs lezen van een specifieke init container:

kubectl logs api-server-7f8b4c5d6-x9k2m -c wait-for-postgres

Als de init container al gecrasht en herstart is, lees dan de logs van de vorige run:

kubectl logs api-server-7f8b4c5d6-x9k2m -c wait-for-postgres --previous

Voeg set -x toe aan shell-based init containers voor verbose command tracing:

command:
- sh
- -c
- |
  set -x   # print elk commando voor uitvoering
  until nc -z postgres-service 5432; do
    sleep 2
  done

Bron: Kubernetes debug init containers guide

Resource requests en init container scheduling

De Kubernetes-scheduler berekent de effectieve resource-requirements van een pod als:

effective request = max(hoogste enkele init container request, som van alle app container requests)

Dat betekent dat een migratie-init-container die 4 Gi geheugen aanvraagt, de scheduling-footprint van de hele pod opblaast, ook al is dat geheugen maar een paar seconden nodig. De scheduler reserveert genoeg capaciteit voor welke fase (init of run) dan ook de grootste is.

Maak init container resource requests passend. Een init container die alleen nc -z draait heeft geen 512 Mi geheugen nodig. Te grote init container requests kunnen voorkomen dat pods op kleinere nodes worden ingepland.

Meer over hoe requests en limits scheduling, QoS class-toewijzing en overcommit-strategie beinvloeden: Kubernetes resource requests en limits.

Sidecar containers vs. init containers

Kubernetes 1.28 introduceerde native sidecar containers als een speciale vorm van init container. De feature bereikte stable (GA) in Kubernetes 1.33. De feature gate SidecarContainers staat standaard aan sinds Kubernetes 1.29.

Een sidecar is gewoon een init container met restartPolicy: Always:

initContainers:
- name: log-shipper
  image: fluentd:v1.17
  restartPolicy: Always    # dit maakt het een sidecar
  volumeMounts:
  - name: log-vol
    mountPath: /app/logs
containers:
- name: app
  image: mycompany/api-server:3.2.0
  volumeMounts:
  - name: log-vol
    mountPath: /app/logs
volumes:
- name: log-vol
  emptyDir: {}

De gedragsverschillen zijn flink:

Gedrag Gewone init container Sidecar (restartPolicy: Always)
Levensduur Stopt voordat app start Draait de hele pod-levensduur
Blokkeert volgende container Tot exit 0 Tot startupProbe slaagt (als gedefinieerd)
Probe-support Geen livenessProbe, readinessProbe, startupProbe
Resource accounting max(init, app) Opgeteld bij app container som
Job-voltooiing N.v.t. Blokkeert Job-voltooiing niet

Gebruik een gewone init container voor eenmalige setup: dependency waiting, migraties, config ophalen, permissies fixen. Gebruik een sidecar voor processen die naast de applicatie moeten draaien: log shippers, metrics exporters, service mesh proxies (Envoy, Linkerd).

Voor native sidecar-support was het draaien van een log shipper als gewone container in een Job problematisch: de shipper hield de pod in leven nadat de hoofdcontainer van de Job klaar was. Native sidecars blokkeren Job-voltooiing niet.

Resultaat verifieren

Na het applyen van een Deployment met init containers:

# Pods bekijken tijdens startup
kubectl get pods -l app=api-server -w
# Verwacht: Init:0/1 -> Init:1/1 -> Running

# Bevestig dat de init container klaar is
kubectl describe pod <pod-name>
# Zoek naar "Init Containers:" sectie; state moet "Terminated: Completed" zijn

# Check applicatielogs om te bevestigen dat alles correct startte
kubectl logs <pod-name> -c api

Veelvoorkomende problemen

Symptoom Waarschijnlijke oorzaak Oplossing
Pod hangt vast in Init:0/1 Init container wacht op een service die niet bestaat of niet klaar is Controleer de doelservice met kubectl get svc en kubectl get endpoints
Init:CrashLoopBackOff Init container-commando sluit af met non-zero exit code Lees logs met kubectl logs <pod> -c <init-name> en fix het commando of image
Meerdere replica's veroorzaken dubbele migraties Migratietool gebruikt geen advisory locks Stap over naar een Job met parallelism: 1 of gebruik een tool met lockingsupport
Pod niet inplanbaar na toevoegen init container Init container resource request te groot voor beschikbare nodes Pas de resources.requests van de init container aan
Init container werkt lokaal maar faalt in het cluster Image pull error of verkeerde registry credentials Check Events in kubectl describe pod voor ImagePullBackOff

Terugkerende server- of deploymentproblemen?

Ik help teams productie betrouwbaar maken met CI/CD, Kubernetes en cloud—zodat fixes blijven en deploys geen stress meer zijn.

Bekijk DevOps consultancy

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.