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)
kubectlgeconfigureerd 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 voorbackoffLimit. 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.