Veröffentlicht am

Jobs und CronJobs: Batch-Workloads auf Kubernetes

Teilen:
Authors

Jobs und CronJobs: Batch-Workloads auf Kubernetes

TL;DR

  • Kubernetes Jobs führen Aufgaben einmalig bis zum erfolgreichen Abschluss aus -- im Gegensatz zu Deployments, die dauerhaft laufen.
  • Drei Job-Typen: Einzelner Pod, parallele Verarbeitung mit fester Completion-Anzahl und Indexed Jobs für partitionierte Workloads.
  • CronJobs erstellen Jobs nach einem Zeitplan im Cron-Format und unterstützen Concurrency-Policies (Allow, Forbid, Replace).
  • TTL-Controller räumt abgeschlossene Jobs automatisch auf, damit der Cluster nicht mit alten Pods volläuft.
  • Häufigster Fehler: CronJobs mit concurrencyPolicy: Allow erzeugen bei langsamen Jobs parallele Instanzen, die sich gegenseitig blockieren.

Jobs: Einmalige Aufgaben ausführen

Ein Deployment startet Pods und hält sie am Laufen. Ein Job startet Pods und wartet, bis sie erfolgreich beendet werden. Typische Einsatzgebiete: Datenbank-Migrationen, Batch-Imports, Report-Generierung, ML-Training-Runs.

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migration
spec:
  ttlSecondsAfterFinished: 3600
  backoffLimit: 3
  activeDeadlineSeconds: 600
  template:
    spec:
      containers:
        - name: migrate
          image: myapp:1.5
          command: ["python", "manage.py", "migrate"]
          env:
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: url
      restartPolicy: Never

Die wichtigsten Felder im Überblick:

FeldBedeutungEmpfehlung
backoffLimitMax. Neustarts bei Fehler3-6 für idempotente Jobs
activeDeadlineSecondsMax. Laufzeit gesamtImmer setzen als Sicherheitsnetz
ttlSecondsAfterFinishedAufräumzeit nach Abschluss3600 (1h) für Debugging-Fenster
restartPolicyNever oder OnFailureNever für bessere Fehleranalyse

restartPolicy: Never erstellt bei Fehlern neue Pods (bis backoffLimit). restartPolicy: OnFailure startet den Container im selben Pod neu. Never ist besser für Debugging, weil die Logs alter Pods erhalten bleiben.

Parallele Jobs

Für Workloads, die sich aufteilen lassen, unterstützt Kubernetes parallele Ausführung.

Feste Completion-Anzahl

apiVersion: batch/v1
kind: Job
metadata:
  name: video-encoding
spec:
  completions: 10
  parallelism: 3
  backoffLimit: 5
  template:
    spec:
      containers:
        - name: encoder
          image: encoder:2.1
          command: ["encode", "--chunk-from-queue"]
          resources:
            requests:
              cpu: "2"
              memory: "4Gi"
            limits:
              cpu: "4"
              memory: "8Gi"
      restartPolicy: Never

Dieser Job braucht 10 erfolgreiche Completions und startet maximal 3 Pods gleichzeitig. Kubernetes erstellt neue Pods, sobald einer fertig ist, bis alle 10 Durchläufe abgeschlossen sind.

Indexed Jobs

Seit Kubernetes 1.24 stabil. Jeder Pod bekommt einen eindeutigen Index über die Umgebungsvariable JOB_COMPLETION_INDEX. Damit lassen sich Daten partitionieren.

apiVersion: batch/v1
kind: Job
metadata:
  name: data-processing
spec:
  completionMode: Indexed
  completions: 5
  parallelism: 5
  template:
    spec:
      containers:
        - name: processor
          image: processor:1.0
          command:
            - "sh"
            - "-c"
            - |
              echo "Processing partition $JOB_COMPLETION_INDEX of 5"
              python process.py --partition=$JOB_COMPLETION_INDEX --total=5
      restartPolicy: Never

Pod 0 verarbeitet Partition 0, Pod 1 Partition 1 und so weiter. Kein externer Koordinationsmechanismus nötig.

CronJobs: Zeitgesteuerte Ausführung

CronJobs erstellen Jobs nach einem Cron-Zeitplan. Die Syntax entspricht dem klassischen Unix-Cron-Format.

apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-backup
spec:
  schedule: "0 2 * * *"
  timeZone: "Europe/Berlin"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 5
  startingDeadlineSeconds: 300
  suspend: false
  jobTemplate:
    spec:
      ttlSecondsAfterFinished: 86400
      backoffLimit: 2
      template:
        spec:
          containers:
            - name: backup
              image: backup-tool:3.0
              command:
                - "sh"
                - "-c"
                - |
                  pg_dump $DATABASE_URL | gzip > /backups/db-$(date +%Y%m%d).sql.gz
                  aws s3 cp /backups/db-$(date +%Y%m%d).sql.gz s3://backups/
              env:
                - name: DATABASE_URL
                  valueFrom:
                    secretKeyRef:
                      name: db-credentials
                      key: url
          restartPolicy: OnFailure

Concurrency-Policies

Die concurrencyPolicy bestimmt, was passiert, wenn ein neuer CronJob-Lauf fällig ist, während der vorherige noch läuft.

# Aktive CronJobs auflisten
kubectl get cronjobs

# Letzte Job-Laeufe eines CronJobs anzeigen
kubectl get jobs --selector=job-name=nightly-backup

# CronJob manuell ausloesen (zum Testen)
kubectl create job --from=cronjob/nightly-backup manual-backup-test
PolicyVerhaltenEinsatz
Allow (Default)Mehrere Jobs laufen parallelUnabhängige, idempotente Tasks
ForbidNeuer Lauf wird übersprungenDatenbank-Backups, Locks
ReplaceAlter Job wird abgebrochenReports, bei denen nur der neueste zählt

Wichtig: Allow ist der Default. Wenn ein Backup-Job 3 Stunden läuft und stündlich geplant ist, laufen ohne Forbid drei Backup-Prozesse gleichzeitig -- das kann die Datenbank überlasten.

Suspend und Resume

CronJobs lassen sich pausieren, ohne sie zu löschen. Das ist nützlich bei Wartungsfenstern oder wenn ein nachgelagertes System nicht verfügbar ist.

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

# CronJob wieder aktivieren
kubectl patch cronjob nightly-backup -p '{"spec":{"suspend":false}}'

# Alle CronJobs im Namespace pausieren
kubectl get cronjobs -o name | xargs -I {} kubectl patch {} -p '{"spec":{"suspend":true}}'

Auch laufende Jobs lassen sich nachträglich suspendieren (seit Kubernetes 1.24):

# Laufenden Job pausieren
kubectl patch job video-encoding -p '{"spec":{"suspend":true}}'

# Job fortsetzen
kubectl patch job video-encoding -p '{"spec":{"suspend":false}}'

Beim Suspendieren eines Jobs werden aktive Pods beendet. Beim Fortsetzen erstellt Kubernetes neue Pods. Der Job-Controller merkt sich, wie viele Completions bereits erreicht wurden.

TTL-Cleanup: Alte Jobs aufräumen

Ohne Cleanup sammeln sich abgeschlossene Jobs und deren Pods im Cluster an. Der TTL-Controller löscht sie automatisch.

# Am Job:
spec:
  ttlSecondsAfterFinished: 3600  # 1 Stunde nach Abschluss loeschen

# Am CronJob (History-Limits):
spec:
  successfulJobsHistoryLimit: 3   # Nur die letzten 3 erfolgreichen behalten
  failedJobsHistoryLimit: 5       # Nur die letzten 5 fehlgeschlagenen behalten

Für bestehende Cluster mit vielen alten Jobs hilft ein einmaliger Cleanup:

# Abgeschlossene Jobs aelter als 24h loeschen
kubectl get jobs --field-selector status.successful=1 \
  -o jsonpath='{.items[?(@.status.completionTime)].metadata.name}' | \
  tr ' ' '\n' | xargs -I {} kubectl delete job {}

Häufige Fehler und Lösungen

Verpasste Schedules

Wenn der CronJob-Controller ausfällt oder der Cluster neustartet, können Schedules verpasst werden. startingDeadlineSeconds steuert, wie lange ein verpasster Lauf noch nachgeholt wird.

Fehlt das Feld, werden verpasste Läufe unbegrenzt nachgeholt. Bei 100 verpassten Läufen startet Kubernetes 100 Jobs gleichzeitig. Das Feld immer setzen -- 300 Sekunden sind ein guter Wert.

BackoffLimit erreicht

Wenn ein Job backoffLimit Mal fehlschlägt, markiert Kubernetes ihn als Failed. Die Backoff-Zeiten steigen exponentiell: 10s, 20s, 40s bis maximal 6 Minuten. Logs der fehlgeschlagenen Pods prüfen:

# Fehlgeschlagene Pods des Jobs finden
kubectl get pods --selector=job-name=db-migration --field-selector=status.phase=Failed

# Logs des letzten fehlgeschlagenen Pods
kubectl logs job/db-migration --tail=50

Ressourcen-Limits vergessen

Jobs ohne Ressourcen-Limits können den gesamten Node belegen. Besonders bei parallelen Jobs mit parallelism: 10 wichtig. Immer requests und limits setzen -- oder LimitRanges und ResourceQuotas auf Namespace-Ebene konfigurieren.

FAQ

Was ist der Unterschied zwischen Jobs und Deployments?

Deployments halten Pods dauerhaft am Laufen und starten sie bei Ausfällen neu. Jobs führen Pods bis zum erfolgreichen Abschluss aus und stoppen dann. Jobs eignen sich für einmalige oder wiederkehrende Batch-Aufgaben, Deployments für langlebige Services.

Kann ich einen fehlgeschlagenen Job neu starten?

Nicht direkt. Ein fehlgeschlagener Job bleibt im Failed-Status. Man muss den Job löschen und neu erstellen. Bei CronJobs passiert das automatisch beim nächsten Zeitplan-Lauf. Für manuelle Jobs: kubectl delete job my-job && kubectl apply -f job.yaml.

Wie überwache ich CronJobs in Produktion?

Mit Prometheus und dem kube-state-metrics Exporter. Die Metrik kube_cronjob_next_schedule_time zeigt den nächsten geplanten Lauf. kube_job_failed zählt fehlgeschlagene Runs. Alerting-Regel: Alarm wenn kube_cronjob_next_schedule_time - time() < 0 -- der CronJob hat seinen Zeitplan verpasst.

Unterstützen CronJobs Zeitzonen?

Ja, seit Kubernetes 1.27 stabil. Das Feld timeZone akzeptiert IANA-Zeitzonen wie Europe/Berlin. Ohne dieses Feld nutzt der Controller die Zeitzone des kube-controller-manager -- das ist in Cloud-Umgebungen meist UTC.

Wie viele parallele Jobs verträgt ein Cluster?

Das hängt von den Cluster-Ressourcen ab. Jeder Job-Pod braucht CPU und Memory. Als Faustregel: parallelism so setzen, dass die Gesamtlast der parallelen Pods 60-70% der verfügbaren Node-Kapazität nicht übersteigt. ResourceQuotas auf Namespace-Ebene verhindern, dass ein einzelner CronJob den Cluster überlastet.

Kubernetes-Expertise gesucht?

Managed Services, Beratung, Training oder Security – wir unterstützen deutsche Unternehmen bei allen Kubernetes-Themen.

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