Kubernetes

CronJob 调度一个 Pod,但它并不编排任何东西。

Kubernetes 已经会调度。它做不到的是表达作业之间的顺序、在 Pod 被回收后保留日志,以及触达集群之外的系统。Dagu 把每个步骤作为 Kubernetes Job 运行,并在其周围补上图、历史与跨边界能力。

每个步骤都是真正的 Kubernetes Job,不是包着 kubectl 的脚本
Pod 日志进入 Dagu 的运行历史,比 Pod 活得更久
通过 kubeconfig context,一个工作流可跨多个集群
集群步骤可与 SSH、HTTP 和审批步骤同处一图
01

CronJob 做不到的事

作为单个 Pod 的调度器,CronJob 相当好用。缺口出现在批处理超过一个 Pod 的那一刻,而每支团队都会撞上同样的三点。

  • CronJob 之间没有依赖。顺序只能靠猜测时间偏移来表达,一旦某个作业跑久了就会悄悄失效
  • 默认历史很浅:保留三次成功和一次失败,日志随 Pod 一起消失。要解释上周二的失败往往无从查起
  • 如果连续错过超过一百次调度且未设置 startingDeadlineSeconds,CronJob 会彻底停止调度,直到有人发现
02

步骤即 Job,日志会回来

Dagu 为每个步骤创建一个单容器 Job,等待其结束,流式读取 Pod 日志,并使用已终止容器的退出码。日志写入运行历史,因此在 Pod 和 Job 都消失之后依然存在。

  • depends 直接表达顺序,抽取失败时加载不会执行,而不是把空数据灌进去
  • retry_policy 通过创建新的 Job 重试该步骤,这与 backoffLimit 原地重启 Pod 是两回事
  • 默认在完成后删除 Job,因此夜间的图不会留下一串已完成的 Job

Kubernetes 暴露的是合并后的容器日志流,因此该步骤类型的 stdout 与 stderr 会作为单一交错的流到达。

把三步批处理作为真正的 Kubernetes Job 运行
# 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

装不进一个集群的工作

这是 CronJob 无论如何都做不到的部分,因为 CronJob 的作用域就是它所在的集群。跨两个集群、或跨集群与本地系统的工作流,在 Kubernetes 内部没有容身之处。

  • kubeconfig 与 context 可按步骤设置,因此预发与生产是同一个工作流的两个步骤,而不是同一份清单的两次部署
  • 集群步骤可以夹在对遗留主机的 SSH 与对 SaaS API 的 HTTP 之间,因为编排器本身不是集群资源
  • human.task 步骤为一次带类型的审批暂停工作流,等待期间不占用进程,随后继续进入生产步骤
带审批门的跨集群迁移晋级
# 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

Argo Workflows 更合适的场景

Argo Workflows 是同一问题的 Kubernetes 原生答案,对相当一类工作而言它才是正确选择。区别在于编排器住在哪里。

  • Argo 以 CRD 和控制器的形式运行在集群内部:若你编排的一切都已在那里,这是优势;若不是,就是约束
  • Dagu 是一个二进制文件,可以放在集群之外,这正是跨集群与跨边界图得以成立的原因
  • 如果你希望工作流定义就是 Kubernetes 对象,受同一套 RBAC 与 GitOps 管理,那是 Argo 的设计,而不是 Dagu 的
05

该执行器有意不暴露的部分

Kubernetes 步骤类型是刻意收窄的。它很好地覆盖了批处理步骤的常见形态,但并不试图成为通用的清单应用工具。

  • 每个步骤一个容器、一条命令。没有原始 Pod 或 Job spec 的透传
  • 不暴露 Job 的 parallelism、completions、completion mode 与 success policy,因此索引作业与并行作业不在范围内
  • restart_policy 不可配置,Windows 专有选项、SELinux、AppArmor 和 proc_mount 也不可用

文档未列出的字段请一律视为不支持。需要完整 Job spec 的工作,更适合用命令步骤调用 kubectl,或交给 Argo。

FAQ

Practical questions before adopting

Dagu 必须运行在集群内部吗?

不必。它按显式 kubeconfig、常规 kubeconfig 加载规则、集群内配置的顺序解析集群,两种方式都可行。放在集群外正是跨集群与跨边界工作流成立的前提;当它触达的一切都在该集群内时,放在集群内也没问题。

这和用 CronJob 跑一个调用 kubectl 的脚本有何不同?

那种做法只给你顺序:包装 Pod 的退出码掩盖了具体哪个阶段失败,重试会重跑整个脚本,日志则是一团无法区分的流并随 Pod 消失。Dagu 为每个步骤创建一个 Job,因此每个阶段都有自己的状态、自己的重试和自己保留的日志。

步骤之间共享文件系统吗?

不共享。每个步骤是独立的 Job,因而是独立的 Pod。步骤间传递数据请使用对象存储、挂载到各步骤的持久卷,或用步骤输出传递小值,与在独立 Job 之间的做法一致。

取消运行时 Job 会怎样?

取消、终止与超时路径即使在 cleanup_policy 设为 keep 时也会强制清理,因此被中止的运行不会遗留 Job。正常运行时的默认行为是完成后删除 Job。

我们应该用它替代 Argo Workflows 吗?

只有当编排器位于集群之外对你有价值时才应如此。若所有任务都是同一集群内的容器,并且你希望工作流是受同一 RBAC 与 GitOps 管理的 Kubernetes 对象,Argo 更合适。Dagu 适合把集群作业与主机、API 和人工审批混合在一起的图。

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.