Kubernetes

CronJob plant einen Pod. Orchestriert wird damit nichts.

Kubernetes plant bereits. Was es nicht tut: Reihenfolge zwischen Jobs ausdrücken, die Logs behalten, sobald der Pod eingesammelt ist, oder etwas außerhalb des Clusters erreichen. Dagu führt jeden Schritt als Kubernetes Job aus und ergänzt Graph, Historie und Grenzüberschreitung.

Jeder Schritt ist ein echter Kubernetes Job, kein Shell-Wrapper um kubectl
Pod-Logs landen in Dagus Laufhistorie und überleben den Pod
Ein Workflow kann über kubeconfig-Kontexte mehrere Cluster umfassen
Cluster-Schritte stehen neben SSH-, HTTP- und Freigabeschritten im selben Graphen
01

Was CronJob nicht leistet

Als Scheduler für einen Pod ist CronJob gut. Die Lücken zeigen sich, sobald ein Batch mehr als ein Pod ist, und jedes Team trifft dieselben drei.

  • Zwischen CronJobs gibt es keine Abhängigkeit. Reihenfolge wird durch geratene Zeitversätze ausgedrückt, was beim ersten langen Lauf still bricht
  • Die Historie ist standardmäßig flach: drei erfolgreiche und ein fehlgeschlagener Lauf bleiben erhalten, und die Logs verschwinden mit den Pods. Den Fehler vom letzten Dienstag zu erklären ist oft unmöglich
  • Werden mehr als einhundert Zeitpunkte in Folge verpasst und ist startingDeadlineSeconds nicht gesetzt, plant der CronJob gar nicht mehr und wartet darauf, dass es jemand bemerkt
02

Schritte sind Jobs, und die Logs kommen zurück

Dagu erzeugt pro Schritt einen Job mit einem Container, wartet darauf, streamt die Pod-Logs und verwendet den Exit-Code des beendeten Containers. Die Logs werden in die Laufhistorie geschrieben und sind noch da, wenn Pod und Job längst weg sind.

  • depends drückt die Reihenfolge direkt aus, sodass ein fehlgeschlagenes Extract das Laden stoppt, statt es mit nichts zu füttern
  • retry_policy wiederholt den Schritt, indem ein frischer Job erzeugt wird, was etwas anderes ist als ein backoffLimit, das einen Pod an Ort und Stelle neu startet
  • Jobs werden standardmäßig nach Abschluss gelöscht, sodass ein nächtlicher Graph keine Spur beendeter Jobs hinterlässt

Kubernetes stellt einen zusammengeführten Container-Logstream bereit, deshalb kommen stdout und stderr bei diesem Schritttyp als ein einziger, verschränkter Stream an.

Ein dreistufiger Batch als echte Kubernetes Jobs
# k8s-nightly-etl.yaml
schedule: "0 2 * * *"
max_active_runs: 1

kubernetes:
  namespace: batch
  service_account: dagu-runner
  resources:
    requests:
      cpu: "250m"
      memory: "512Mi"

steps:
  - id: extract
    action: k8s.run
    with:
      image: ghcr.io/example/extract:1.4.0
      command: extract --source warehouse
    retry_policy:
      limit: 2
      interval_sec: 120

  - id: transform
    action: k8s.run
    with:
      image: ghcr.io/example/transform:1.4.0
    depends: extract

  - id: load
    action: k8s.run
    with:
      image: ghcr.io/example/load:1.4.0
      resources:
        limits:
          cpu: "2"
          memory: "4Gi"
    depends: transform

handler_on:
  failure:
    run: ./scripts/page-oncall.sh

mail_on:
  failure: true
03

Arbeit, die nicht in einen Cluster passt

Das ist der Teil, den CronJob mit keinem Aufwand leisten kann, denn ein CronJob ist auf den Cluster beschränkt, in dem er lebt. Ein Workflow, der zwei Cluster berührt oder einen Cluster und ein On-Premises-System, hat innerhalb von Kubernetes keinen Ort.

  • kubeconfig und context sind pro Schritt setzbar, sodass Staging und Produktion zwei Schritte eines Workflows sind statt zwei Deployments desselben Manifests
  • Ein Cluster-Schritt kann zwischen einem SSH-Schritt auf einem Altsystem und einem HTTP-Aufruf an eine SaaS-API stehen, weil der Orchestrator selbst keine Cluster-Ressource ist
  • Ein human.task-Schritt hält den Workflow für eine typisierte Freigabe an, belegt während des Wartens keinen Prozess und läuft danach in den Produktionsschritt weiter
Eine Migration clusterübergreifend befördern, mit Freigabe
# k8s-promote-across-clusters.yaml
max_active_runs: 1

kubernetes:
  namespace: batch
  service_account: dagu-runner

steps:
  - id: migrate_staging
    action: k8s.run
    with:
      context: staging
      image: ghcr.io/example/migrator:3.2
      command: migrate up

  - id: verify_staging
    run: ./scripts/verify-schema.sh staging
    depends: migrate_staging

  - id: approve
    action: human.task
    with:
      prompt: Promote the migration to production?
      form:
        type: object
        properties:
          change_ticket:
            type: string
          confirmed:
            type: boolean
        required: [change_ticket, confirmed]
    depends: verify_staging

  - id: migrate_production
    action: k8s.run
    with:
      context: production
      image: ghcr.io/example/migrator:3.2
      command: migrate up
    depends: approve

  - id: record
    run: ./scripts/record-change.sh "${steps.approve.outputs.change_ticket}"
    depends: migrate_production

handler_on:
  failure:
    run: ./scripts/page-oncall.sh

mail_on:
  failure: true
04

Wo Argo Workflows die bessere Antwort ist

Argo Workflows ist die Kubernetes-native Antwort auf dasselbe Problem und für eine große Klasse von Arbeit die richtige. Der Unterschied liegt darin, wo der Orchestrator wohnt.

  • Argo läuft als CRDs und Controller im Cluster, ein Vorteil, wenn alles Orchestrierte ohnehin dort liegt, und eine Einschränkung, wenn nicht
  • Dagu ist eine Binary und kann außerhalb des Clusters sitzen, was cluster- und grenzübergreifende Graphen erst möglich macht
  • Wenn Workflow-Definitionen Kubernetes-Objekte sein sollen, verwaltet vom selben RBAC- und GitOps-Fluss wie der Rest des Clusters, ist das Argos Entwurf und nicht Dagus
05

Was der Executor bewusst nicht bietet

Der Kubernetes-Schritttyp ist absichtlich schmal. Er deckt die übliche Form eines Batch-Schritts gut ab und versucht nicht, ein allgemeiner Manifest-Anwender zu sein.

  • Ein Container pro Schritt mit einem einzelnen Kommando. Ein Durchreichen roher Pod- oder Job-Specs gibt es nicht
  • Job-parallelism, completions, completion mode und success policy werden nicht bereitgestellt, indizierte und parallele Jobs bleiben also außerhalb
  • restart_policy ist nicht konfigurierbar, und Windows-spezifische, SELinux-, AppArmor- sowie proc_mount-Einstellungen stehen nicht zur Verfügung

Ein nicht dokumentiertes Feld gilt als nicht unterstützt. Arbeit, die eine vollständige Job-Spec braucht, wird besser mit kubectl aus einem Kommandoschritt angewendet oder Argo überlassen.

FAQ

Practical questions before adopting

Muss Dagu im Cluster laufen?

Nein. Es löst den Cluster über eine explizite kubeconfig, dann die normalen kubeconfig-Ladepfade und schließlich die In-Cluster-Konfiguration auf, funktioniert also in beiden Fällen. Außerhalb zu laufen ermöglicht cluster- und grenzübergreifende Workflows; innerhalb ist in Ordnung, wenn alles Berührte in diesem Cluster liegt.

Worin unterscheidet sich das von einem CronJob, der ein kubectl-Skript ausführt?

Dieses Muster gibt Ihnen Reihenfolge und sonst nichts: Der Exit-Code des Wrapper-Pods verbirgt, welche Stufe fehlschlug, Wiederholungen starten das ganze Skript neu, und die Logs sind ein ununterscheidbarer Strom, der mit dem Pod verschwindet. Dagu erzeugt einen Job pro Schritt, sodass jede Stufe eigenen Status, eigene Wiederholung und ein eigenes aufbewahrtes Log hat.

Teilen Schritte ein Dateisystem?

Nein. Jeder Schritt ist ein eigener Job und damit ein eigener Pod. Reichen Sie Daten über Objektspeicher, ein in jeden Schritt gemountetes persistentes Volume oder für kleine Werte über Schrittausgaben weiter, genau wie zwischen separaten Jobs.

Was passiert mit dem Job, wenn ein Lauf abgebrochen wird?

Abbruch-, Kill- und Timeout-Pfade erzwingen die Bereinigung, selbst wenn cleanup_policy auf keep steht, ein gestoppter Lauf lässt also keinen Job zurück. Im Normalbetrieb wird der Job standardmäßig nach Abschluss gelöscht.

Sollten wir das statt Argo Workflows verwenden?

Nur wenn es Ihnen nützt, dass der Orchestrator außerhalb des Clusters liegt. Sind alle Tasks Container in einem Cluster und sollen Workflows Kubernetes-Objekte unter demselben RBAC- und GitOps-Fluss sein, passt Argo besser. Dagu eignet sich für Graphen, die Cluster-Jobs mit Hosts, APIs und menschlichen Freigaben mischen.

Next step

Start with one workflow.

Install Dagu, move one script that runs on cron today into YAML, and decide from a real run history.