Docker

Geplante Container-Jobs, ohne dafür Kubernetes einzuführen.

Wer Docker betreibt, aber kein Kubernetes, hat für einen geplanten Container-Job keinen guten Ort. Dagu ist eine einzelne Binärdatei, die den vorhandenen Docker-Daemon ansteuert und Containern Abhängigkeiten, Retries, Logs und eine Web-UI hinzufügt.

Kein Kubernetes und keine Container-Plattform nötig
Schritte teilen einen Container, oder jeder Schritt bringt sein Image mit
Exec in Container, die Ihr Compose-Stack bereits betreibt
Podman funktioniert über dieselbe Docker-kompatible API
01

Die Lücke zwischen docker run und Kubernetes CronJob

Einen Container zu planen ist der Punkt, an dem die guten Optionen ausgehen. Kubernetes CronJob ist die ausgereifte Antwort, aber nur mit vorhandenem Cluster. Darunter ruft üblicherweise cron ein docker run auf und liefert sonst nichts.

  • cron plus docker run bietet keine Retries, keine Abhängigkeit zwischen Jobs, keine Laufhistorie und keinen Weg zu sehen, warum es letzte Nacht scheiterte
  • Kubernetes einzuführen, um einen nächtlichen Report zu planen, ist sehr viel Plattform für sehr wenig Job
  • Ein allgemeiner Orchestrator, der Container als Schritttyp statt als Deployment-Ziel behandelt, passt genau dazwischen
02

Ein Container für den ganzen Workflow

Wird ein Container auf Workflow-Ebene deklariert, laufen alle Schritte im selben langlebigen Container. Sie teilen das Dateisystem, sodass Pakete aus einem Schritt im nächsten noch vorhanden sind.

  • Abhängigkeiten einmal installieren und über Schritte hinweg nutzen, statt Images neu zu bauen oder pro Task neu zu installieren
  • Host-Pfade über volumes einhängen und Umgebungsvariablen einmal für den ganzen Workflow setzen
  • Retries, Abhängigkeiten und Fehlerbehandlung bleiben gewöhnliche Workflow-Felder, unabhängig davon, dass Schritte im Container laufen

In diesem Modus laufen Schritte über docker exec, daher werden ENTRYPOINT und CMD des Images für Schrittbefehle nicht ausgeführt. Der Befehl gehört in den Schritt.

Ein nächtlicher Job vollständig in einem Image
# nightly-report.yaml
schedule: "0 3 * * *"
max_active_runs: 1

container:
  image: python:3.12
  volumes:
    - ./data:/data
  env:
    - TZ=Asia/Tokyo

steps:
  - id: install
    run: pip install -r /data/requirements.txt

  - id: build_report
    run: python /data/build_report.py
    depends: install
    retry_policy:
      limit: 2
      interval_sec: 120

  - id: publish
    run: python /data/publish.py
    depends: build_report

mail_on:
  failure: true
03

Wartung in bereits laufenden Containern

Der Exec-Modus richtet einen Workflow auf einen bereits laufenden Container, etwa einen von Docker Compose gestarteten. Die geplante Arbeit passiert im echten Anwendungscontainer statt in einer frischen Kopie.

  • Datenbankmigrationen, Cache-Leerungen und Queue-Wartung laufen gegen den tatsächlich laufenden Dienst
  • Den Container als String zu benennen ist die gesamte Konfiguration
  • Der Workflow behält Abhängigkeiten und Fehlerbenachrichtigung, die eine Compose-Datei nicht ausdrücken kann
Geplante Wartung gegen einen laufenden Compose-Dienst
# app-maintenance.yaml
schedule: "0 4 * * *"
max_active_runs: 1

# docker compose で起動済みのコンテナに exec する
container: myapp-web

steps:
  - id: migrate
    run: php artisan migrate --force

  - id: prune_sessions
    run: php artisan session:prune
    depends: migrate

  - id: clear_cache
    run: php artisan cache:clear
    depends: prune_sessions

mail_on:
  failure: true
04

Ein anderes Image je Schritt

Wenn eine Pipeline mehrere Werkzeuge kreuzt, kann jeder Schritt sein eigenes Image mitbringen. Der Workflow bleibt eine Datei mit einem Zeitplan, die Schritte bleiben unabhängig.

  • Extrahieren mit einem Datenbank-Client-Image, Transformieren mit einem Sprach-Image, Laden mit einem weiteren Client, ohne ein Image, das alle drei enthalten muss
  • Ein Container auf Schrittebene überschreibt den auf Workflow-Ebene, sodass ein weitgehend einheitlicher Workflow Ausnahmen zulässt
  • Die Image-Pull-Policy ist je Schritt konfigurierbar, für fixierte und für häufig neu gebaute Images
Ein Workflow, drei Images
# etl-pipeline.yaml
schedule: "0 2 * * *"
max_active_runs: 1

steps:
  - id: extract
    container:
      image: mysql:8
      volumes:
        - ./work:/work
    run: mysqldump --host db.internal -u svc app > /work/dump.sql

  - id: transform
    container:
      image: python:3.12
      volumes:
        - ./work:/work
    run: python /work/transform.py
    depends: extract

  - id: load
    container:
      image: postgres:16
      volumes:
        - ./work:/work
    run: psql -h dw.internal -f /work/out.sql
    depends: transform

handler_on:
  failure:
    run: ./scripts/notify-failure.sh

mail_on:
  failure: true
05

Was der Host bereitstellen muss

Container-Schritte sprechen mit einer Docker-kompatiblen API. Das ist die einzige Voraussetzung und zugleich die Einschränkung, die man vor einer Deployment-Planung kennen sollte.

  • Ein lokaler Docker-Socket oder ein entfernter Daemon über DOCKER_HOST funktionieren beide
  • Podman wird über seine Docker-kompatible API unterstützt, indem DAGU_CONTAINER_RUNTIME=podman gesetzt wird
  • Da Dagu eine einzelne Binärdatei ist, braucht der Scheduler selbst weder Cluster noch Metadatenbank noch Broker

Verwaltete Dagu-Cloud-Instanzen laufen mit gVisor-Isolierung und stellen keinen Container-Daemon-Socket bereit, dort sind Container-Schritte also nicht verfügbar. Nutzen Sie selbst gehostetes Dagu oder leiten Sie den Workflow an einen selbst gehosteten Worker.

06

Wo Kubernetes weiterhin richtig ist

Diese Seite plädiert für ein kleineres Werkzeug in einer bestimmten Lage, nicht generell gegen Kubernetes.

  • Wer bereits einen Cluster betreibt, findet in CronJob einen vernünftigen Ort für geplante Container und braucht nichts Neues
  • Wenn Jobs Pod-Scheduling, Autoscaling oder Bin-Packing über Knoten brauchen, ist das Aufgabe eines Clusters, nicht eines Orchestrators
  • Für Workflows, die Arbeit an einen Cluster geben, die Orchestrierung aber außerhalb halten wollen, hat Dagu einen Kubernetes-Schritt

FAQ

Practical questions before adopting

Brauche ich Kubernetes, um containerisierte Jobs zu planen?

Nein. Dagu spricht direkt mit einem Docker-kompatiblen Daemon, ein einzelner Host mit Docker genügt. Kubernetes lohnt sich, wenn Sie Scheduling und Skalierung auf Clusterebene brauchen, nicht bloß um 3 Uhr nachts einen Container zu starten.

Wie unterscheidet sich das vom Docker-Operator in Airflow?

Vor allem im Betriebsaufwand. Airflow braucht Scheduler, Metadatenbank und ein Python-DAG-Framework, bevor ein Container läuft. Dagu ist eine Binärdatei mit dateibasiertem Zustand, und Container sind ein Feld an Workflow oder Schritt statt ein Python-Operator.

Können Schritte Dateien untereinander teilen?

Ja. Mit einem Container auf Workflow-Ebene laufen die Schritte im selben Container und teilen dessen Dateisystem direkt. Bei Images je Schritt hängen Sie einen gemeinsamen Host-Pfad in jeden Schritt ein.

Funktioniert Podman?

Ja, über die Docker-kompatible API von Podman. Setzen Sie DAGU_CONTAINER_RUNTIME=podman in einer selbst gehosteten Installation, das Verhalten der Container-Schritte bleibt gleich.

Kann ein Schritt in einem von Docker Compose gestarteten Container laufen?

Ja, das ist der Exec-Modus. Benennen Sie den laufenden Container, und die Schritte des Workflows laufen darin. Geplante Migrationen und Cache-Wartung gegen einen laufenden Compose-Stack werden üblicherweise so verdrahtet.

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.