Kubernetes-Jobs und CronJobs ausführen

KubernetesBeginner
Jetzt üben

Einführung

Deployments sorgen dafür, dass Services dauerhaft ausgeführt werden. Bei Batch-Aufgaben gilt jedoch eine andere Erfolgskriterien: Die Aufgabe wird bis zum Ende ausgeführt, das Ergebnis wird erfasst und anschließend wird sie beendet. Kubernetes bildet einmalige Aufgaben mit Jobs und wiederkehrende Zeitpläne mit CronJobs ab.

Sie werden einen Job ausführen, seinen Pod und dessen Logs untersuchen, eine kontrollierte Parallelität hinzufügen, einen CronJob planen und lernen, wie Aussetzung und Verlaufsgrenzen wiederkehrende Aufgaben im Betrieb handhabbar machen.

Betrachten Sie dieses Lab als Vergleich verschiedener Erfolgskriterien. Ein Deployment ist erfolgreich, wenn die gewünschte Anzahl an Replikaten verfügbar bleibt. Ein Job ist erfolgreich, wenn die erforderliche Anzahl von Pods mit dem Exit-Code 0 beendet wird. Ein CronJob ist erfolgreich, wenn er Jobs zu den vorgesehenen Zeitpunkten erstellt. Wenn Sie diese drei Ebenen getrennt betrachten, lässt sich das YAML deutlich leichter lesen.

Einen einmaligen Job ausführen

In diesem Schritt erstellen Sie einen Job, dessen Pod einen Bericht ausgibt und erfolgreich beendet wird. Erstellen Sie das Manifest mit einem Here-Dokument. Das quotierte EOF verhindert, dass die Shell Zeichen innerhalb des YAML erweitert.

Bevor Sie das YAML eingeben, betrachten Sie zunächst seinen Aufbau: kind: Job wählt den zuständigen Controller aus, spec.template beschreibt den zu erstellenden Pod, und der Pod enthält einen kurzlebigen Container. Anders als bei einem Deployment-Template muss ein Job-Pod restartPolicy: Never oder OnFailure verwenden, da eine erfolgreich abgeschlossene Aufgabe beendet werden soll.

cd /home/labex/project/batch-workloads
cat <<'EOF' > report-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: report-job
  namespace: practice-batch
spec:
  backoffLimit: 2
  template:
    metadata:
      labels:
        app: report-job
    spec:
      restartPolicy: Never
      containers:
        - name: reporter
          image: busybox:1.36
          imagePullPolicy: IfNotPresent
          command: ["sh", "-c", "echo report-complete"]
EOF

Bitten Sie Kubernetes zunächst, die Datei lokal zu validieren. --dry-run=client prüft die Struktur des Objekts, ohne etwas zu erstellen. Das ist eine nützliche Gewohnheit, wenn Sie ein neues Manifest verfassen:

kubectl apply --dry-run=client -f report-job.yaml

Wenn das Ergebnis job.batch/report-job created (dry run) lautet, ist die Datei strukturell gültig. Wenden Sie sie anschließend tatsächlich an und warten Sie auf die Bedingung Complete des Jobs, statt die benötigte Dauer zu schätzen:

kubectl apply -f report-job.yaml
kubectl wait --for=condition=Complete job/report-job -n practice-batch --timeout=60s

Mit restartPolicy: Never kann der Job-Controller entscheiden, ob ein weiterer Pod benötigt wird. backoffLimit: 2 begrenzt die Anzahl fehlgeschlagener Wiederholungsversuche. Lesen Sie die Ausgabe des abgeschlossenen Pods über den Controller:

kubectl logs job/report-job -n practice-batch

Sie sollten report-complete sehen. Durch die Angabe von job/report-job findet kubectl den Pod des Jobs automatisch. Den generierten Pod-Namen müssen Sie daher nicht kopieren.

Die drei Befehle bilden einen kurzen, gut lesbaren Lebenszyklus ab:

job.batch/report-job created
job.batch/report-job condition met
report-complete

condition met bedeutet, dass der Job-Controller den Abschluss registriert hat. Die folgende Logzeile stammt aus der Anwendung. Auf einem gerade gestarteten lokalen Cluster kann selbst dieser kleine Job einige Sekunden benötigen, während sich die Netzwerkkomponenten stabilisieren. Das Warten auf eine Bedingung macht den Ablauf zuverlässig, ohne eine feste Wartezeit erraten zu müssen.

Besitzverhältnisse und Abschluss eines Jobs nachvollziehen

In diesem Schritt vergleichen Sie einen abgeschlossenen Job mit einem dauerhaft laufenden Deployment. Ein abgeschlossener Job behält seinen Pod, damit Logs und Beendigungsstatus weiterhin untersucht werden können.

Beginnen Sie mit der gewöhnlichen Tabellenansicht. Der Job sollte einen erfolgreichen Abschluss anzeigen, während der Pod je nach angezeigter Spalte die Phase Completed oder Succeeded aufweist:

kubectl get jobs,pods -n practice-batch
kubectl get pods -n practice-batch -l job-name=report-job -o custom-columns='POD:.metadata.name,PHASE:.status.phase,OWNER:.metadata.ownerReferences[0].name,EXIT_CODE:.status.containerStatuses[0].state.terminated.exitCode'
kubectl describe job report-job -n practice-batch

Die Pod-Phase sollte Succeeded lauten, sein Besitzer sollte report-job sein und der Exit-Code sollte 0 betragen. Ein Job ist abgeschlossen, sobald die erforderliche Anzahl von Pods erfolgreich beendet wurde. Einen erfolgreichen Container startet er nicht erneut, nur damit dieser weiterläuft.

Die Abfrage mit benutzerdefinierten Spalten fasst vier verschachtelte Felder in einer übersichtlichen Beweistabelle zusammen. Das ist hilfreicher, als das gesamte Pod-YAML zu lesen, wenn Sie konkret wissen möchten: „Wer besitzt diesen Pod, und wurde sein Prozess erfolgreich beendet?“

Das getestete Ergebnis sah folgendermaßen aus:

POD                PHASE       OWNER        EXIT_CODE
report-job-cd54c   Succeeded   report-job   0

Das erzeugte Suffix ist bei Ihnen anders. Lesen Sie die Zeile von links nach rechts: OWNER=report-job verknüpft den Pod mit seinem Controller, und PHASE=Succeeded zusammen mit EXIT_CODE=0 bestätigt, dass der Prozess erfolgreich abgeschlossen wurde.

Mehrere Abschlüsse parallel ausführen

In diesem Schritt erstellen Sie einen Job, der vier erfolgreiche Pods erfordert und dabei höchstens zwei gleichzeitig ausführt.

Vor dem Erstellen des Manifests müssen Sie zwei voneinander unabhängige Werte verstehen:

  • completions: 4 bedeutet, dass insgesamt vier erfolgreiche Arbeitseinheiten erforderlich sind.
  • parallelism: 2 bedeutet, dass Kubernetes höchstens zwei dieser Einheiten gleichzeitig ausführen darf.

completionMode: Indexed weist den Arbeitseinheiten die Indizes 0 bis 3 zu. Jeder Pod erhält seinen eigenen Index über die Umgebungsvariable JOB_COMPLETION_INDEX. Auf diese Weise können echte Batch-Programme einen Datensatz aufteilen, ohne dass jeder Pod dieselbe Partition verarbeitet.

cat <<'EOF' > indexed-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: indexed-job
  namespace: practice-batch
spec:
  completionMode: Indexed
  completions: 4
  parallelism: 2
  backoffLimitPerIndex: 1
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: worker
          image: busybox:1.36
          imagePullPolicy: IfNotPresent
          command: ["sh", "-c", "echo worker-index=${JOB_COMPLETION_INDEX}; sleep 2"]
EOF

Wenden Sie das Manifest an und warten Sie, bis alle vier Indizes abgeschlossen sind:

Der letzte Befehl verwendet -L, um den Wert des Labels batch.kubernetes.io/job-completion-index als zusätzliche Tabellenspalte anzuzeigen. So erkennen Sie, welcher Arbeitsindex zu welchem erzeugten Pod gehört, ohne dessen vollständiges YAML zu öffnen.

kubectl apply -f indexed-job.yaml
kubectl wait --for=condition=Complete job/indexed-job -n practice-batch --timeout=90s
kubectl get pods -n practice-batch -l job-name=indexed-job -L batch.kubernetes.io/job-completion-index

completions bezeichnet die Gesamtzahl erfolgreich abzuschließender Arbeitseinheiten; parallelism bezeichnet die maximale Anzahl gleichzeitig laufender Pods. Durch die indizierte Verarbeitung erhält jeder Pod über JOB_COMPLETION_INDEX einen stabilen Index.

Die abschließende Tabelle sollte vier abgeschlossene Pods mit unterschiedlichen Index-Labels enthalten. Sie werden möglicherweise nicht in numerischer Reihenfolge angezeigt, da die Abschlusszeiten parallel laufender Aufgaben nicht deterministisch sind.

Beispiel:

NAME                  STATUS      JOB-COMPLETION-INDEX
indexed-job-0-gw5wq   Completed   0
indexed-job-1-vw4qk   Completed   1
indexed-job-2-mpvnr   Completed   2
indexed-job-3-8z5j8   Completed   3

Vier Zeilen bedeuten nicht, dass vier Pods gleichzeitig ausgeführt wurden. Die vier Indizes belegen den Gesamtumfang der Arbeit, während parallelism: 2 die Anzahl der gleichzeitig von Kubernetes gestarteten Pods auf zwei begrenzt hat.

Einen CronJob planen

In diesem Schritt erstellen Sie einen CronJob, der jede Minute einen Job startet. Die Cron-Syntax besteht aus fünf Feldern: Minute, Stunde, Tag des Monats, Monat und Wochentag.

Der Zeitplan */1 * * * * bedeutet „jede Minute“. Er wurde absichtlich so häufig gewählt, damit Sie das Verhalten innerhalb eines kurzen Labs beobachten können. In Produktionsumgebungen werden normalerweise größere Abstände verwendet. Das Manifest enthält außerdem ein verschachteltes jobTemplate, weil ein CronJob Jobs erstellt und jeder dieser Jobs anschließend Pods erstellt.

cat <<'EOF' > heartbeat-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: heartbeat
  namespace: practice-batch
spec:
  schedule: "*/1 * * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 2
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: Never
          containers:
            - name: heartbeat
              image: busybox:1.36
              imagePullPolicy: IfNotPresent
              command: ["sh", "-c", "date -u; echo heartbeat-complete"]
EOF

Wenden Sie den Zeitplan an und sehen Sie sich seine Zusammenfassung an:

kubectl apply -f heartbeat-cronjob.yaml
kubectl get cronjob heartbeat -n practice-batch

concurrencyPolicy: Forbid verhindert, dass ein neuer Lauf beginnt, während ein früherer Lauf noch nicht beendet ist. Erstellen Sie sofort einen Job aus der CronJob-Vorlage, damit Sie ihn untersuchen können, ohne bis zur nächsten Minute zu warten:

kubectl create job --from=cronjob/heartbeat heartbeat-manual -n practice-batch
kubectl wait --for=condition=Complete job/heartbeat-manual -n practice-batch --timeout=60s
kubectl logs job/heartbeat-manual -n practice-batch

Das Log enthält einen UTC-Zeitstempel, gefolgt von heartbeat-complete. Der manuell erstellte Job verwendet dieselbe Vorlage, verändert aber nicht den zukünftigen Zeitplan des CronJobs.

Eine typische Ausgabe sieht so aus:

job.batch/heartbeat-manual condition met
Thu Aug 13 07:48:29 UTC 2026
heartbeat-complete

Ihr Zeitstempel wird anders sein. Beachten Sie, dass der Container UTC verwendet, weil sein Befehl date -u lautet. Dadurch vermeiden Sie Verwechslungen zwischen der Zeitplanung des Clusters und der lokalen Zeitzone des Terminals.

Geplante Aufgaben aussetzen und fortsetzen

In diesem Schritt pausieren Sie den CronJob, ohne seine Definition zu löschen. Eine Aussetzung ist beispielsweise während Wartungsarbeiten oder bei der Bearbeitung eines Vorfalls hilfreich.

kubectl patch ändert ausschließlich das im JSON-Payload angegebene Feld. Hier wird spec.suspend auf true gesetzt. Das CronJob-Objekt, sein Zeitplan und bereits vorhandene Jobs bleiben erhalten.

kubectl patch cronjob heartbeat -n practice-batch --type=merge -p '{"spec":{"suspend":true}}'
kubectl get cronjob heartbeat -n practice-batch -o custom-columns='NAME:.metadata.name,SCHEDULE:.spec.schedule,SUSPEND:.spec.suspend,LAST_SCHEDULE:.status.lastScheduleTime'

Bereits vorhandene Jobs laufen weiter. Die Aussetzung verhindert lediglich die Erstellung neuer, planmäßiger Jobs. Setzen Sie den CronJob nach der Untersuchung wieder fort:

kubectl patch cronjob heartbeat -n practice-batch --type=merge -p '{"spec":{"suspend":false}}'
kubectl get jobs -n practice-batch --sort-by=.metadata.creationTimestamp

Nun können Sie die drei Ebenen unterscheiden: Der CronJob definiert einen Zeitplan, jeder geplante Lauf erstellt einen Job, und jeder Job erstellt einen oder mehrere Pods.

Da die gesamte Übung möglicherweise vor dem nächsten Minutenwechsel endet, kann LAST_SCHEDULE weiterhin <none> anzeigen. Das ist kein Fehler: heartbeat-manual wurde mit --from auf Anfrage erstellt und nicht durch den CronJob-Scheduler. Entscheidend ist der abschließende Wert SUSPEND=false, durch den zukünftige geplante Läufe wieder zugelassen werden.

Zusammenfassung

Sie haben Jobs für zeitlich begrenzte Aufgaben, indizierte Abschlüsse für parallele Arbeitsvorgänge und CronJobs für wiederkehrende Zeitpläne verwendet. Dabei haben Sie Besitzverhältnisse der Controller, Logs, Abschlussstatus, Parallelitätsrichtlinien, Verlaufslimits und die Aussetzung untersucht. Diese Unterschiede im Lebenszyklus sind entscheidend für die Auswahl des passenden Kubernetes-Workload-Controllers.