Skip to content

Jobs und CronJobs

Ein Container, der eine Aufgabe erledigt und sich beendet, gehört nicht in ein Deployment. Ein Deployment startet ihn endlos neu, und genau daher kommen die meisten CrashLoopBackOff-Meldungen über einen völlig gesunden Container.

Eine einmalige Aufgabe

yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: migrate
spec:
  backoffLimit: 3
  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - name: migrate
          image: registry.itsh.dev/<dein-slug>/myapp:1.0.0
          command: ["./migrate"]
          securityContext:
            allowPrivilegeEscalation: false
            runAsNonRoot: true
            seccompProfile:
              type: RuntimeDefault
            capabilities:
              drop: ["ALL"]
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 256Mi

restartPolicy muss OnFailure oder Never sein, Always ist für einen Job nicht zulässig. backoffLimit begrenzt die Wiederholungen, bevor der Job als fehlgeschlagen gilt.

Der securityContext ist derselbe Block wie bei jedem anderen Pod, und er steht aus demselben Grund da: Ohne ihn gibt Kubernetes beim Apply vier Warnungen aus. Siehe Versionen und Standardwerte.

Geplante Arbeit

yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-report
spec:
  schedule: "0 3 * * *"
  timeZone: "Europe/Berlin"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: report
              image: registry.itsh.dev/<dein-slug>/myapp:1.0.0
              command: ["./report"]
              securityContext:
                allowPrivilegeEscalation: false
                runAsNonRoot: true
                seccompProfile:
                  type: RuntimeDefault
                capabilities:
                  drop: ["ALL"]

Normales fünfstelliges Cron, anders als die sechsstellige Form, die der Datenbank-Operator für geplante Backups verwendet.

Drei Felder solltest du bewusst setzen:

  • timeZone. Ohne die Angabe läuft der Zeitplan in UTC, und das war nicht gemeint, wenn du 0 3 * * * in Ortszeit gedacht hast.
  • concurrencyPolicy: Forbid lässt einen Lauf aus, wenn der vorherige noch läuft. Der Standard lässt sie sich überlappen, was ein Report oder ein Aufräumjob selten will.
  • Die History-Limits. Fertige Pods bleiben liegen, damit du ihre Logs lesen kannst. Behalte ein paar; viele machen den Namespace unübersichtlich.

Was sie kosten

Ein Job wird nach den Requests seines Pods abgerechnet, und zwar nur so lange, wie dieser Pod tatsächlich läuft, minutengenau. Ein Job mit fünf Minuten Laufzeit kostet fünf Minuten, keine Stunde.

Fertige Pods, die als History liegen bleiben, werden nicht abgerechnet. Sie laufen nicht, kosten also nichts, und ein paar für die Logs aufzuheben ist gratis.

Damit ist geplante Arbeit das Günstigste auf der Plattform, und deshalb ist ein CronJob die bessere Antwort als ein Service, der schläft: Ein CronJob kostet zwischen den Läufen nichts, ganz ohne die Vorbehalte von Scale-to-Zero. Siehe Scale-to-Zero.

Wenn ein Job scheitert

kubectl get jobs zeigt die Abschlüsse. Die Pods bleiben liegen, also:

bash
kubectl get pods --selector=job-name=migrate
kubectl logs job/migrate

Ein CronJob, der nie zu laufen scheint, hat meistens einen Zeitplan in der falschen Zeitzone, oder concurrencyPolicy: Forbid lässt Läufe aus, weil der vorherige nie fertig wurde.

Wie es weitergeht