Kubernetes Jobs en CronJobs: batch-workloads draaien

Een Kubernetes Job draait een of meer pods tot ze klaar zijn en stopt dan. Een CronJob doet hetzelfde, maar dan op een cron-schema. Samen dekken ze databasemigraties, nachtelijke backups, rapportgeneratie en elk ander werk dat eenmalig of op een timer moet draaien in plaats van continu. Deze gids loopt door beide resources heen: van een minimale Job via parallelisme en failure handling tot timezone-aware CronJobs met concurrency control en automatische opruiming.

Wat je aan het einde hebt

Een werkend begrip van beide batch/v1 resources (Job en CronJob) en een set productieklare manifests voor de meest voorkomende patronen: eenmalige taken, parallelle batchverwerking, failure-tolerant indexed werk, en timezone-aware scheduled jobs met concurrency control en automatische opruiming.

Vereisten

  • Een draaiend Kubernetes-cluster (v1.27 of nieuwer voor timezone-support; v1.31+ voor stabiele pod failure policies)
  • kubectl geconfigureerd en verbonden met het cluster
  • Bekendheid met pod specs en YAML-manifests
  • Voor resourceconfiguratie op batch-pods, zie Kubernetes resource requests en limits

Job vs. CronJob

Een Job maakt pods aan, houdt bij hoeveel er succesvol afronden, en markeert zichzelf als klaar wanneer dat aantal bereikt is. Hij draait eenmalig en herhaalt niet. Gebruik een Job voor databasemigraties, data-imports, eenmalige scripts en taken die vanuit een deploymentpipeline of handmatig getriggerd worden.

Een CronJob maakt Jobs aan op een cron-schema. Het is het Kubernetes-equivalent van een Unix crontab-entry. De CronJob-controller maakt alleen Job-objecten aan; elke Job beheert vervolgens zijn eigen pods. Gebruik een CronJob voor nachtelijke backups, periodieke rapportgeneratie, cache warming en log-opruiming.

De keten is altijd CronJob -> Job -> Pod. Elk veld dat op een Job spec beschikbaar is, werkt ook in .spec.jobTemplate van een CronJob.

Minimale Job

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
spec:
  backoffLimit: 4
  template:
    spec:
      containers:
      - name: migrate
        image: myapp/migrate:2.4.0
        command: ["./migrate", "--target=latest"]
        resources:
          requests:
            cpu: "250m"
            memory: "256Mi"
          limits:
            memory: "256Mi"
      restartPolicy: Never

Toepassen en volgen:

kubectl apply -f job.yaml
kubectl get jobs --watch
# Zodra de STATUS-kolom "Complete" toont, is de migratie klaar.

# Logs van de pod bekijken:
kubectl logs -l job-name=db-migrate

Parallelisme en completions

Jobs ondersteunen drie patronen, aangestuurd door twee velden:

Patroon .spec.completions .spec.parallelism Wanneer gebruiken
Enkele pod 1 (standaard) 1 (standaard) Een taak, een keer draaien
Vast aantal completions N M (waarbij M <= N) N pods moeten slagen; M draaien tegelijk
Work queue niet gezet (null) M M pods halen werk uit een externe queue; klaar zodra een pod met exit 0 stopt

parallelism is muteerbaar op een draaiende Job, dus je kunt batchwerk halverwege op- of afschalen. Bij fixed-completion Jobs is de feitelijke concurrency min(parallelism, resterende completions).

spec:
  completions: 10   # 10 pods moeten slagen
  parallelism: 3    # maximaal 3 tegelijk

Kubernetes draait batches van maximaal 3 pods totdat er 10 in totaal geslaagd zijn.

Indexed completion mode

Met .spec.completionMode: Indexed (stabiel sinds Kubernetes v1.24) krijgt elke pod een unieke index van 0 tot completions - 1. De index wordt geïnjecteerd als de JOB_COMPLETION_INDEX omgevingsvariabele en als pod-annotatie batch.kubernetes.io/job-completion-index. De Job is klaar wanneer er voor elke index een pod succesvol is afgerond.

Gebruik indexed Jobs als elke pod een deterministische partitie verwerkt: frame N van een render, shard N van een dataset, testsuite N van een matrix.

apiVersion: batch/v1
kind: Job
metadata:
  name: render-frames
spec:
  completions: 120
  parallelism: 30
  completionMode: Indexed
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: renderer
        image: studio/renderer:4.1.0
        command: ["./render", "--frame=$(JOB_COMPLETION_INDEX)"]
        resources:
          requests:
            cpu: "2"
            memory: "4Gi"
          limits:
            memory: "4Gi"

Voortgang controleren:

kubectl describe job render-frames
# Kijk naar "Completed Indexes:" — dat toont welke indexes klaar zijn.

Failure handling

Drie velden bepalen wat er gebeurt als pods falen. Ze werken samen, dus het is belangrijk om ze in combinatie in te stellen.

backoffLimit

spec:
  backoffLimit: 4   # standaard is 6

Het maximale aantal pod-failures voordat de Job als Failed wordt gemarkeerd. De controller probeert het opnieuw met exponential backoff: 10s, 20s, 40s, tot een maximum van 6 minuten. Zodra het limiet bereikt is, worden alle draaiende pods gestopt.

Met restartPolicy: Never telt elke gefaalde pod mee. Met restartPolicy: OnFailure tellen container-restarts binnen dezelfde pod niet mee; alleen volledige pod-failures. Gebruik Never als je logs van gefaalde pods wilt kunnen inspecteren. Gebruik OnFailure voor korte taken waar log-retentie minder belangrijk is.

activeDeadlineSeconds

spec:
  activeDeadlineSeconds: 3600   # harde wall-clock limiet van 1 uur

Een wall-clock deadline op de hele Job, niet per pod. Bij overschrijding worden alle draaiende pods gestopt en krijgt de Job de status Failed met reden DeadlineExceeded. Dit gaat voor op backoffLimit: de Job faalt ook als er nog retries over zijn.

Combineer beide:

spec:
  backoffLimit: 4
  activeDeadlineSeconds: 3600

De Job faalt op welke conditie het eerst bereikt wordt: 4 pod-failures of 1 uur verstreken.

Pod failure policy

Stabiel sinds Kubernetes v1.31. Pod failure policies geven fijnmazige controle voorbij de botte backoffLimit. Drie acties zijn beschikbaar:

  • FailJob: stopt de hele Job direct. Gebruik dit voor niet-herwinbare fouten (bekende foute exit codes).
  • Ignore: telt de failure niet mee voor backoffLimit. Gebruik dit voor infrastructuurverstoringen zoals node drains.
  • Count: het standaardgedrag.

Regels worden op volgorde geëvalueerd; de eerste match wint.

spec:
  backoffLimit: 6
  podFailurePolicy:
    rules:
    - action: FailJob
      onExitCodes:
        containerName: worker
        operator: In
        values: [42]               # applicatie-signaal "niet opnieuw proberen"
    - action: Ignore
      onPodConditions:
      - type: DisruptionTarget     # node drain of preemption

Per-index failure budgets (indexed Jobs)

Bij indexed Jobs kan een snel falende index het globale backoffLimit uitputten voordat andere indexes een kans krijgen. Sinds Kubernetes v1.33 lossen backoffLimitPerIndex en maxFailedIndexes dit op:

spec:
  completionMode: Indexed
  completions: 50
  parallelism: 10
  backoffLimitPerIndex: 2     # elke index heeft een eigen retry-budget
  maxFailedIndexes: 5         # stop de hele Job als >5 indexes falen

Gefaalde indexes verschijnen in status.failedIndexes. Dit werkt samen met de FailIndex pod failure policy action, die een enkele index stopt zonder de hele Job te killen.

CronJob-schema en tijdzone

Het .spec.schedule veld gebruikt standaard vijf-velden cron-syntax:

# ┌───────────── minuut (0-59)
# │ ┌───────────── uur (0-23)
# │ │ ┌───────────── dag van de maand (1-31)
# │ │ │ ┌───────────── maand (1-12)
# │ │ │ │ ┌───────────── dag van de week (0-6, zondag=0)
  * * * * *

Veelgebruikte expressies:

Expressie Betekenis
0 2 * * * Dagelijks om 02:00
*/15 * * * * Elke 15 minuten
30 6 * * 1-5 Doordeweeks om 06:30
@daily Afkorting voor 0 0 * * *
@hourly Afkorting voor 0 * * * *

Tijdzone-ondersteuning

Zonder .spec.timeZone draait het schema in de tijdzone van het kube-controller-manager proces (meestal UTC). Sinds Kubernetes v1.27 is het timeZone veld GA en accepteert het elke IANA tijdzonenaam:

spec:
  schedule: "0 9 * * 1-5"
  timeZone: "Europe/Amsterdam"   # 9 uur CET/CEST, ma-vr

Kubernetes handelt zomer-/wintertijdovergangen automatisch af. Geef expliciet timeZone: "Etc/UTC" op als je UTC wilt en niet afhankelijk wilt zijn van de lokale klok van de controller-manager.

Gebruik geen TZ= of CRON_TZ= in de schedule-string. Kubernetes weigert dit met een validatiefout.

CronJob-naamlimiet: CronJob-namen moeten 52 tekens of korter zijn. De controller voegt tot 11 tekens toe om child Job-namen te vormen, en Job-namen zijn gelimiteerd tot 63.

Concurrency policy

.spec.concurrencyPolicy bepaalt wat er gebeurt als een nieuwe geplande uitvoering start terwijl een vorige Job van dezelfde CronJob nog draait.

Waarde Gedrag Typisch gebruik
Allow (standaard) Beide draaien tegelijk Onafhankelijke, korte taken
Forbid Nieuwe uitvoering wordt overgeslagen (niet in queue gezet) Database-operaties, exclusive locks
Replace Draaiende Job wordt verwijderd, nieuwe start Freshness-gevoelige snapshots

Forbid is de veilige default voor de meeste batch-taken. Twee kopieën van een databasebackup of rapportgenerator tegelijk draaien geeft vrijwel altijd problemen.

startingDeadlineSeconds

spec:
  startingDeadlineSeconds: 3600

Het maximale aantal seconden na een gepland tijdstip waarbinnen de controller nog probeert een Job te starten. Als het venster voorbij is, wordt de uitvoering overgeslagen. Stel dit in op minimaal 60 seconden; waarden onder 10 riskeren dat de Job nooit start, omdat de CronJob-controller ongeveer elke 10 seconden controleert.

Als er meer dan 100 schema's gemist worden binnen het startingDeadlineSeconds venster (of sinds het laatste succesvolle schema als het veld niet is gezet), stopt de CronJob helemaal met het aanmaken van Jobs.

Opruimen van afgeronde Jobs

Zonder opruiming hopen voltooide en gefaalde Jobs zich op, wat na verloop van tijd de API-server vertraagt.

Standalone Jobs: ttlSecondsAfterFinished

Stabiel sinds Kubernetes v1.23. Zodra een Job de status Complete of Failed bereikt, verwijdert de TTL-controller hem (en zijn pods) na het opgegeven aantal seconden.

spec:
  ttlSecondsAfterFinished: 86400   # 24 uur bewaren, dan verwijderen

De waarde op 0 zetten verwijdert direct na voltooiing. Niet instellen betekent dat de Job voor altijd blijft bestaan.

CronJob history limits

CronJobs hebben ingebouwd historybeheer dat meestal beter past dan TTL:

spec:
  successfulJobsHistoryLimit: 3   # standaard: 3
  failedJobsHistoryLimit: 3       # standaard: 1

Deze bewaren de N meest recente Jobs per status en verwijderen oudere. Gebruik bij CronJobs deze velden in plaats van ttlSecondsAfterFinished in het job template.

Jobs en CronJobs pauzeren

Beide resources ondersteunen een suspend veld. Het gedrag verschilt:

Job suspend: suspend: true zetten verwijdert alle actieve pods. Het aantal eerder geslaagde of gefaalde pods blijft bewaard. Terug naar false zetten hervat de Job waar hij gebleven was. Handig om clusterresources vrij te maken tijdens onderhoud of om voorrang te geven aan belangrijker werk.

# Een draaiende Job pauzeren:
kubectl patch job render-frames -p '{"spec":{"suspend":true}}'

# Hervatten:
kubectl patch job render-frames -p '{"spec":{"suspend":false}}'

CronJob suspend: suspend: true zetten voorkomt dat de controller nieuwe Jobs aanmaakt. Jobs die al draaien gaan gewoon door tot ze klaar zijn. Handig tijdens incidenten of gepland onderhoud.

kubectl patch cronjob nightly-backup -p '{"spec":{"suspend":true}}'

Compleet CronJob-voorbeeld

Dit manifest combineert de velden uit dit artikel tot een productieklare nachtelijke backup-CronJob:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-db-backup              # <= 52 tekens
spec:
  schedule: "0 2 * * *"                # dagelijks om 02:00
  timeZone: "Europe/Amsterdam"          # GA sinds v1.27
  concurrencyPolicy: Forbid             # overslaan als vorige run nog actief is
  startingDeadlineSeconds: 3600         # maximaal 1 uur te laat starten
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      activeDeadlineSeconds: 3600       # harde wall-clock limiet van 1 uur
      backoffLimit: 2
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: backup
            image: myapp/db-backup:1.8.0
            command: ["/bin/sh", "-c", "pg_dump -h db-primary.internal mydb | gzip > /backup/mydb-$(date +%Y%m%d).sql.gz"]
            resources:
              requests:
                cpu: "200m"
                memory: "128Mi"
              limits:
                memory: "256Mi"
            volumeMounts:
            - name: backup-vol
              mountPath: /backup
          volumes:
          - name: backup-vol
            persistentVolumeClaim:
              claimName: backup-pvc

Verwacht resultaat: kubectl get cronjob nightly-db-backup toont een LAST SCHEDULE timestamp die dagelijks om 02:00 CET/CEST bijwerkt. kubectl get jobs --selector=batch.kubernetes.io/cronjob-name=nightly-db-backup toont de drie meest recente Jobs.

Handige operationele commando's

# Handmatig een CronJob triggeren (handig voor testen):
kubectl create job --from=cronjob/nightly-db-backup manual-test-$(date +%s)

# Jobs tonen die door een CronJob zijn aangemaakt:
kubectl get jobs --selector=batch.kubernetes.io/cronjob-name=nightly-db-backup

# Logs ophalen van alle pods van een specifieke Job:
kubectl logs -l job-name=nightly-db-backup-28950720

# Een CronJob en al zijn child Jobs verwijderen:
kubectl delete cronjob nightly-db-backup

Veelvoorkomende problemen

Symptoom Waarschijnlijke oorzaak Oplossing
Job zit vast in Active, geen pods Namespace heeft een ResourceQuota en de pod-spec mist resource requests/limits Voeg resources.requests en resources.limits toe aan de container spec
CronJob start nooit startingDeadlineSeconds onder 10 gezet Verhoog naar minimaal 60
CronJob stopt met schedulen na storing Meer dan 100 gemiste schema's opgehoopt Verwijder en hermaak de CronJob, of stel startingDeadlineSeconds in om dit te voorkomen
Pods stapelen zich op, API-server wordt traag Geen TTL of history limit geconfigureerd Voeg ttlSecondsAfterFinished toe (standalone Jobs) of stel successfulJobsHistoryLimit / failedJobsHistoryLimit in (CronJobs)
Dubbele backup-runs op hetzelfde moment concurrencyPolicy staat op Allow (de standaard) Stel concurrencyPolicy: Forbid in

Voor resource sizing op batch-pods (CPU-requests, memory limits, QoS-klasse-implicaties), zie Kubernetes resource requests en limits.

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.