Docker

コンテナの定期実行のために、Kubernetes を導入しなくていい。

Docker は使っているが Kubernetes はない。この状態でコンテナの定期実行を置く場所は、実はどこにもありません。Dagu は既存の Docker デーモンを操作する単一バイナリで、コンテナに依存関係、リトライ、ログ、Web UI を足します。

Kubernetes もコンテナ基盤も不要
全ステップで 1 つのコンテナを共有、またはステップごとに別イメージ
compose で起動済みのコンテナに exec できる
Podman も Docker 互換 API 経由で動作
01

docker run と Kubernetes CronJob の間にある空白

コンテナをスケジュール実行しようとすると、たいていここで選択肢が尽きます。Kubernetes CronJob は成熟した答えですが、クラスタがすでにある場合に限られます。その下の層では cron が docker run を叩く形になり、スケジュール以外は何も手に入りません。

  • cron と docker run の組み合わせには、リトライも、ジョブ間の依存関係も、実行履歴も、昨夜なぜ落ちたかを見る手段もありません
  • 夜間のレポート 1 本のために Kubernetes を導入するのは、ジョブの小ささに対して基盤が大きすぎます
  • コンテナをデプロイ先ではなく 1 つのステップ種別として扱う汎用オーケストレータが、この中間に収まります
02

ワークフロー全体を 1 つのコンテナで

ワークフロー階層でコンテナを宣言すると、全ステップが同じ長命コンテナの中で動きます。ファイルシステムを共有するので、あるステップが入れたパッケージは次のステップでもそのまま使えます。

  • 依存関係のインストールを 1 回で済ませてステップ間で使い回せます。イメージを作り直したりタスクごとに入れ直したりする必要がありません
  • volumes でホストのパスをマウントし、環境変数もワークフロー単位で 1 回だけ設定します
  • リトライ、依存関係、失敗時の処理は通常のワークフローの項目のままです。コンテナで動くことによる違いはありません

このモードでは docker exec 経由でステップが実行されるため、イメージの ENTRYPOINT と CMD はステップのコマンドには使われません。コマンドはステップ側に書いてください。

1 つのイメージの中だけで完結する夜間ジョブ
# 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

すでに動いているコンテナの中で保守処理を回す

exec モードは、Docker Compose などで起動済みのコンテナをワークフローの実行先に指定します。定期処理が、コピーではなく稼働中のアプリケーションコンテナの中で行われます。

  • DB マイグレーション、キャッシュクリア、キューの整理を、実際に動いているサービスに対して実行します
  • 設定はコンテナ名を文字列で書くだけです
  • compose ファイルでは表現できない依存関係と失敗通知が、ワークフロー側に付きます
稼働中の compose サービスに対する定期保守
# 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

ステップごとに別のイメージ

複数のツールをまたぐパイプラインでは、ステップごとに自分のイメージを持てます。ワークフローは 1 ファイル 1 スケジュールのまま、ステップは独立します。

  • 抽出は DB クライアントのイメージ、変換は言語のイメージ、投入はまた別のクライアント。3 つ全部を含んだイメージを作る必要がありません
  • ステップ階層のコンテナはワークフロー階層のものを上書きするので、ほぼ統一されたワークフローの中で例外を作れます
  • イメージの pull ポリシーはステップごとに設定できます。固定したいイメージと頻繁に作り直すイメージを分けられます
1 つのワークフロー、3 つのイメージ
# 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

ホスト側に必要なもの

コンテナステップは Docker 互換 API と通信します。必要なのはそれだけで、同時にそれが構成を考えるうえで知っておくべき制約でもあります。

  • ローカルの Docker ソケットでも、DOCKER_HOST 経由のリモートデーモンでも動きます
  • Podman は DAGU_CONTAINER_RUNTIME=podman を設定することで、Docker 互換 API 経由で利用できます
  • Dagu 自体は単一バイナリなので、スケジューラ側にクラスタもメタデータ DB もブローカーも要りません

Dagu Cloud のマネージドインスタンスは gVisor で分離されておりコンテナデーモンのソケットを公開していないため、コンテナステップは利用できません。セルフホストの Dagu を使うか、セルフホストのワーカーにワークフローを振り分けてください。

06

Kubernetes のほうが正しい場合

このページが主張しているのは特定の状況で小さい道具を使うことであって、Kubernetes を避けることではありません。

  • すでにクラスタを運用しているなら、CronJob はコンテナの定期実行の置き場所として妥当で、新しく増やすものがありません
  • Pod 単位のスケジューリング、オートスケール、ノード間の詰め込みが必要なら、それはオーケストレータではなくクラスタの仕事です
  • オーケストレーションはクラスタの外に置いたままクラスタへ処理を投げたい場合のために、Dagu には Kubernetes ステップがあります

FAQ

導入前によくある質問

コンテナを定期実行するのに Kubernetes は必要ですか?

必要ありません。Dagu は Docker 互換のデーモンと直接やり取りするので、Docker が入ったホストが 1 台あれば足ります。Kubernetes が必要になるのはクラスタ単位のスケジューリングやスケーリングが要るときであって、深夜 3 時にコンテナを 1 本動かすためではありません。

Airflow の Docker operator とはどう違いますか?

主に運用コストが違います。Airflow はコンテナを 1 本動かす前に、スケジューラ、メタデータ DB、Python の DAG フレームワークが必要です。Dagu は状態をファイルに持つ単一バイナリで、コンテナは Python の operator ではなくワークフローやステップの項目として宣言します。

ステップ間でファイルを受け渡せますか?

できます。ワークフロー階層でコンテナを指定した場合、ステップは同じコンテナで動くのでファイルシステムをそのまま共有します。ステップごとにイメージを分ける場合は、共有するホストのパスを各ステップにマウントすれば、前のステップの出力が次から見えます。

Podman でも動きますか?

Podman の Docker 互換 API 経由で動きます。セルフホストの環境で DAGU_CONTAINER_RUNTIME=podman を設定すれば、コンテナステップの挙動は同じです。

Docker Compose で起動したコンテナの中でステップを実行できますか?

できます。それが exec モードです。稼働中のコンテナ名を指定すると、ワークフローのステップがその中で実行されます。稼働中の compose 環境に対する定期マイグレーションやキャッシュ保守は、通常この形で組みます。

次のステップ

まず 1 本から始める。

Dagu を入れて、いま cron で動いているスクリプトを 1 本だけ YAML に移し、実際の実行履歴で判断してください。