news 2026/9/10 13:53:15

Dokku 定时任务(Scheduled Cron Tasks)完全指南:从 app.json 声明到 cron:run 实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Dokku 定时任务(Scheduled Cron Tasks)完全指南:从 app.json 声明到 cron:run 实战

Dokku 定时任务(Scheduled Cron Tasks)完全指南:从 app.json 声明到 cron:run 实战

【免费下载链接】dokkuA docker-powered PaaS that helps you build and manage the lifecycle of applications项目地址: https://gitcode.com/GitHub_Trending/do/dokku

本篇技术指南围绕 Dokku 内置的定时任务(Scheduled Cron Tasks)能力展开,详细讲解如何通过app.json中的cron键为应用声明周期性执行的命令,如何用cron:setcron:listcron:suspendcron:resumecron:runcron:report等命令管理任务生命周期,以及如何通过 vector 集成持久化任务输出。读完本文,你将能够为任何 Dokku 应用配置可靠的定时任务,理解其执行环境、超时回收机制与调度器差异,并掌握自管理 cron 的高级用法。

功能概述与命令速览

Dokku 从 0.23.0 版本开始提供内置的定时任务支持(cron插件,位于 plugins/cron)。它把应用app.json中的cron声明转换为调度器可执行的定时任务:对使用宿主 crontab 的调度器(如docker-local)写入dokku用户 crontab,对自带 cron 后端的调度器(如k3s)则原生调度。所有命令如下:

cron:list <app> [--format json|stdout] # 列出应用的定时任务 cron:report [<app>] [<flag>] # 显示应用 cron 报告 cron:resume <app> <cron_id> # 恢复一个 cron 任务 cron:run <app> <cron_id> [--detach] [--ttl-seconds SECONDS] # 即时运行一个 cron 任务 cron:set [--global|<app>] <key> <value> # 设置或清除应用的 cron 属性 cron:suspend <app> <cron_id> # 挂起一个 cron 任务

Dokku 托管 Cron:通过 app.json 声明任务

Dokku 自动调度dokku run命令,其入口是应用app.json文件中的cron键。从源码看,app.json由 plugins/app-json/appjson.go 解析,其中AppJSON.CronCronTask的列表,每个任务包含commandmaintenancescheduleconcurrency_policy四个字段。

声明任务

以下app.json示例等效于每天执行一次dokku run $APP npm run send-email

{ "cron": [ { "command": "npm run send-email", "schedule": "@daily" } ] }

app.json的默认搜索路径与部署方式相关;如需从 monorepo 等场景指定其他位置,可通过app-json:set <app> appjson-path <path>设置(值为相对于基础搜索目录的路径),详见 deployment-tasks.md。

任务属性说明

每个 cron 任务支持以下属性:

属性说明
command在构建出的应用镜像内执行的命令,也可以直接引用Procfile条目
schedulecron 兼容的调度定义,决定命令何时运行。秒通常不支持
maintenance布尔值,决定该任务是否处于维护(不可执行)状态
concurrency_policy字符串(默认allow),控制任务与自身是否可并发执行。合法值:allow(允许并发)、forbid(已有任务运行则新任务直接退出)、replace(终止已有任务并启动新任务)

每个应用可以声明零个或多个 cron 任务。任务的验证发生在构建产物生成之后、应用部署之前;cron 调度表则在部署后阶段(post-deploy)更新。也就是说,一份非法的时间表或命令会在部署时直接报错,而不是等到调度触发时才暴露。

从 plugins/cron/cron.go 的实现可以看到,schedule使用robfig/cron/v3解析器校验,其标志位组合为Minute | Hour | Dom | Month | Dow | Descriptor——因此不支持秒字段,但支持@daily@hourly等描述符。concurrency_policyallow/forbid/replace时会返回"Invalid cron concurrency policy"错误;commandschedule为空时也会在部署阶段报错(WarnToFailure模式)。

任务执行时长上限与回收

cron 任务最长可运行 24 小时,超过后会被系统回收:

  • docker-local调度器通过每 5 分钟运行一次的dokku ps:retire扫描回收超时任务,因此任务实际可能超时最多约 5 分钟;
  • k3s调度器则直接通过 Job 的activeDeadlineSeconds强制执行期限。

源码中DefaultTTLSeconds常量定义为86400(plugins/cron/cron.go),docker-local 会将其作为com.dokku.active-deadline-seconds标签盖印到容器上,k3s 则渲染为 CronJob 的activeDeadlineSeconds

任务执行环境须知

运行定时任务时有以下几点需要注意:

  • 定时任务在应用运行时的环境中执行;如果应用镜像不存在,命令可能执行失败;
  • 调度基于宿主服务器时区(通常为 UTC);
  • 目前 cron 模板中只指定了PATHSHELL两个环境变量:
    • MAILTO可通过cron:set设置;
    • MAILFROM可通过cron:set设置;
  • 每个定时任务都在一个一次性run容器内执行,因此会继承为run容器配置的所有 docker-options;任务之间绝不共享资源;
  • 定时任务按调度器分别支持:使用宿主 crontab 的调度器(如docker-local)会把app.json中的 cron 任务写入dokku用户 crontab;自己管理 cron 后端的调度器(如k3s)则原生调度;
  • 宿主 crontab 调度器(如docker-local)管理的所有应用的任务都写入同一个、归属于dokku用户的 crontab 文件,该 crontab 应视为 Dokku 专用,不要手动写入其他条目;
  • command会被分词(tokenize)后直接在容器内 exec,不会解释;&&|>等 shell 特性。包含裸 shell 操作符的命令在部署时验证app.json会被拒绝,因此格式错误的 cron 命令会导致部署失败而非静默运行失败。如果需要 shell 语义,请显式包裹命令,例如"sh -c 'do-thing > /var/log/x.log'"
  • 任务输出写入容器的 stdout 和 stderr,可通过 Dokku 的 vector 集成持久化(见下文);
  • cron 任务不能在app.json中声明日志文件路径。写入dokku用户 crontab 的只有dokku cron:run <app> <cron_id>行,任何来自部署仓库的路径都不会被插值进去。

关于命令分词,plugins/cron/cron.go 的ValidateCronCommand使用mvdan.cc/sh/v3/shellshell.Fields解析命令;cron:run在派发时使用同一解析器,因此部署时能通过校验的命令一定可以执行。对应的单元测试见 plugins/cron/cron_test.go:"sh -c 'echo CRON_OK; echo hi > /tmp/x.txt'"这类显式包裹的命令会被接受,而"echo CRON_OK; echo hi > /tmp/x.txt""cmd1 && cmd2""cmd | other""cmd > file""cmd $(other)"都会被拒绝。

持久化 Cron 任务输出

如果不做额外配置,任务输出只会投递到 cron 配置的MAILTO地址。要保留输出,可以通过 Dokku 的 vector 集成配置一个 sink,详见 logs.md 的 vector 日志投递章节。

为应用配置的任何 sink 都会与应用的其余日志一起收到 cron 任务输出:

dokku logs:set node-js-app vector-sink "console://?encoding[codec]=json"

若要单独保留 cron 输出,改用vector-cron-sink,cron 输出就会被路由到这里而不是应用主 sink:

dokku logs:set node-js-app vector-cron-sink "console://?encoding[codec]=text"

要写入宿主机上的文件,可指向/var/log/dokku/apps目录(该目录已挂载进 vector 容器)。dokku_cron_id字段可用于模板化,让每个任务拥有自己的日志文件:

dokku logs:set node-js-app vector-cron-sink "file://?path=/var/log/dokku/apps/node-js-app/cron-{{ dokku_cron_id }}.log&encoding[codec]=text"

需要注意的路由规则(logs.md):设置 cron sink 是移动而非复制 cron 输出——vector 把每行日志恰好路由到两个 sink 之一;仅设置vector-cron-sink时 cron 输出去 cron sink、其余输出无处可去;两者都设置时 cron 输出去 cron sink、其余去vector-sink。cron 分支的事件额外带dokku_appdokku_cron_id两个字段(只有它们保证存在,模板中引用其他字段可能导致日志被静默丢弃);对非常短命的任务还存在相关注意事项。

管理 cron 设置:cron:set

cron插件提供若干可按应用管理的设置项。下表列出了本文其他章节未覆盖的属性:

名称描述级别全局默认值
mailfrom在 cron 文件中设置MAILFROM变量,用于 cron 报告仅全局空字符串
maintenance是否让应用运行 cron应用与全局false
mailto在 cron 文件中设置MAILTO变量,用于 cron 报告仅全局空字符串

所有设置都通过cron:set命令完成。以maintenance为例:

dokku cron:set node-js-app maintenance true

传入空值即可恢复默认值:

dokku cron:set node-js-app maintenance

如果属性可以全局设置(如mailto),使用--global标志;应用未设置时,若全局值存在则生效:

dokku cron:set --global maintenance true

同样,传空值可恢复全局默认值:

dokku cron:set --global maintenance

从实现看(plugins/cron/subcommands.go),cron:set在写入属性后还会触发scheduler-cron-write触发器(对--global只传调度器参数),让调度器重新生成 crontab;同时它也是cron:suspend/cron:resume的底层实现。属性定义见 plugins/cron/cron.go:默认属性包含mailfrommailtomaintenance,其中mailfrommailto为全局属性。

列出 Cron 任务:cron:list

使用cron:list命令列出应用的 cron 任务,命令接收app参数:

dokku cron:list node-js-app
ID Schedule Command cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5 @daily node index.js cGhwPT09dHJ1ZT09PSogKiAqICogKg== * * * * * true

输出也支持 JSON 格式:

dokku cron:list node-js-app --format json
[{"id":"cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5","app":"node-js-app","command":"node index.js","schedule":"@daily"}]

获取全局任务,使用--global标志:

dokku cron:list --global
ID Schedule Command 5cruaotm4yzzpnjlsdunblj8qyjp @daily /bin/true

从源码看(plugins/cron/subcommands.go),stdout 格式的表格会额外展示ConcurrencyMaintenance列;Maintenance列对任务级挂起显示true (task),对应用级维护显示true (app)--format仅支持stdoutjson两种值。任务 ID 由GenerateCommandID生成:对appName + "===" + Command + "===" + Schedule做 base36 编码(plugins/cron/cron.go),这也是为什么示例中同样的命令与时间表在不同应用会得到不同 ID。

挂起与恢复指定 Cron 任务

cron 任务可以临时挂起(暂停按计划执行),之后恢复,适用于维护或调试场景。

挂起指定任务,使用cron:suspend并带上应用名与 cron ID:

dokku cron:suspend node-js-app cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5

被挂起的任务将不再按计划执行。可通过cron:list输出的Maintenance列确认任务已挂起——挂起的任务会显示true (task)

恢复挂起的任务,使用cron:resume

dokku cron:resume node-js-app cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5

恢复后任务将重新按计划执行。cron ID 可从cron:list输出获取。

实现细节:cron:suspend等价于cron:set <app> maintenance.<cron_id> truecron:resume等价于cron:set <app> maintenance.<cron_id>(清除该属性)(plugins/cron/subcommands.go 与 plugins/cron/subcommands.go)。属性前缀maintenance.定义于 plugins/cron/cron.go。任务级维护属性不能全局设置(cron:set --global maintenance.<id>会报错),且仅当属性值为true时才会覆盖app.json中声明的maintenance(见FetchCronTasks中的合并逻辑,plugins/cron/cron.go)。

即时执行 Cron 任务:cron:run

cron:run命令可以即时调用 cron 任务,接收app参数和 cron ID(可从cron:list输出获取):

dokku cron:run node-js-app cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5

默认情况下任务在附加(attached)容器中运行(视调度器支持而定)。要在后台分离容器中运行,指定--detach标志:

dokku cron:run node-js-app cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5 --detach

即时调用默认也有 24 小时(86400 秒)的运行上限,与计划调度的任务相同。可用--ttl-seconds指定不同期限:

dokku cron:run node-js-app cGhwPT09cGhwIHRlc3QucGhwPT09QGRhaWx5 --detach --ttl-seconds 600

该值只作用于本次调用——由调度计划启动的任务仍保持 24 小时默认值。所有一次性 cron 执行的容器在调用结束后都会被终止。

实现细节(plugins/cron/subcommands.go):cron:run会先校验--ttl-seconds必须为正整数(validateTTLSeconds,plugins/cron/cron.go,对应测试见 plugins/cron/cron_test.go),校验任务 ID 存在,然后用shell.Fields对命令分词,设置DOKKU_DETACH_CONTAINERDOKKU_DISABLE_TTY(分离模式)、DOKKU_CONCURRENCY_POLICYDOKKU_CRON_IDDOKKU_RM_CONTAINER=1DOKKU_RUN_TTL_SECONDS等环境变量,最终通过scheduler-run触发器派发给应用的调度器执行。

查看 Cron 报告:cron:report

使用cron:report命令查看应用的 cron 配置报告:

dokku cron:report
=====> node-js-app cron information Cron task count: 2 =====> python-sample cron information Cron task count: 0 =====> ruby-sample cron information Cron task count: 10

也可以只查看指定应用:

dokku cron:report node-js-app
=====> node-js-app cron information Cron task count: 2

还可以传标志,只输出你关心的特定信息:

dokku cron:report node-js-app --cron-task-count

可设置的属性及其对应的 report 标志、JSON 键名详见下文"属性参考"一节(完整实现见 plugins/cron/report.go)。

属性参考

以下属性可通过cron:set设置,并通过cron:report查看:

[!NOTE]Report flags列是cron:report接受的 CLI 参数名。cron:report --format json输出的 JSON 键为去掉--cron-前缀后的同名(如global-mailtocomputed-mailtomaintenance)。带cron-前缀的旧键(如cron-global-mailto)在 0.38.x 弃用窗口期仍会输出,并将在未来大版本中移除。

属性作用域默认值Report flags描述
mailfrom仅全局--cron-global-mailfrom--cron-computed-mailfromcron 失败邮件使用的From:地址
mailto仅全局--cron-global-mailto--cron-computed-mailtocron 失败邮件的收件地址;为空则禁用邮件
maintenance应用 + 全局false--cron-maintenance--cron-global-maintenance--cron-computed-maintenancetrue时挂起应用(或全局)的所有 cron 任务
maintenance.<cron-id>仅应用false--cron-maintenance-<cron-id>(按任务动态生成)按计算出的 ID 挂起单个 cron 任务(每个任务一行);由cron:suspend/cron:resume写入

底层原理:crontab 生成与调度器协作

理解"谁能把任务写进 crontab"有助于排障。核心逻辑位于 plugins/cron/crontab.go:

  • usesHostCron通过scheduler-uses-host-cron触发器询问某调度器是否使用宿主 crontab(空调度器或未实现该触发器的视为false,crontab.go);
  • generateCronTasks会收集所有使用宿主 crontab 的应用的app.json任务,再加上通过cron-entries触发器注入的任务(格式为$SCHEDULE;$COMMAND[;$LOGFILE]),并过滤掉处于维护状态的任务(crontab.go);
  • writeCronTab每次都是全量重新生成dokku用户 crontab(先crontab -r -u dokku再写入),因此多个调度器共用宿主 crontab 时不会互相覆盖;任务列表为空时直接删除 crontab(crontab.go)。

最终渲染使用的模板为 plugins/cron/templates/cron.tmpl,内容大致为:

MAILFROM={{ .Mailfrom }} MAILTO={{ .Mailto }} PATH=/usr/local/bin:/usr/bin:/bin SHELL=/bin/bash {{ $task.Schedule }} {{ $task.DokkuRunCommand }}

DokkuRunCommand对应用任务恒输出dokku cron:run <app> <cron_id>(plugins/cron/cron.go),绝不把用户命令直接写进 crontab——测试 plugins/cron/cron_test.go 专门断言 crontab 行不包含;>|&`$等 shell 元字符,且不会把用户命令泄漏到 crontab 行中。

自管理 Cron(高级用法)

[!WARNING] 自管理 cron 属于高级用法。虽然下文提供操作说明,但强烈建议优先使用内置定时任务支持,除非确有必要。

某些安装场景可能需要更细粒度的 cron 控制,以下是配置 cron 的高级指引。

使用 run 执行 cron 任务

可以随时使用一次性容器运行应用任务:

dokku run node-js-app some-command

对于不应被打断的任务,run是处理 cron 任务的首选方式,因为即使发生部署或扩缩容事件,容器也会继续运行。代价是多个并发任务同时运行时内存占用会增加。

使用 enter 执行 cron 任务

Procfile中加入以下条目:

cron: sleep infinity

cron进程扩到1

dokku ps:scale node-js-app cron=1

然后即可在该容器中运行所有命令:

dokku enter node-js-app cron some-command

注意也可以同时运行多个命令以减少内存占用,但这可能会污染容器环境。

对于需要正确恢复的任务,应当使用上述方式——因为部署和扩缩容事件会中断正在运行的任务,且后续命令始终运行在最新容器中。注意如果把 cron 容器缩容,可能会中断任务正常运行。

通用 cron 建议

定期任务在 Dokku 上需要一些额外注意,以下通用建议有助于保证任务成功运行:

  • 在 cron 任务中使用dokku用户;
    • 否则dokku二进制会尝试用sudo执行,cron 运行会失败并报sudo: no tty present and no askpass program specified
  • 添加MAILTO环境变量,把 cron 邮件发给自己;
  • 添加PATH环境变量,或指定宿主机上二进制文件的完整路径;
  • 添加SHELL环境变量,运行命令时指定 Bash;
  • 让 cron 任务按时间排序存放;
  • 保持服务器时间为 UTC,读取 cronfile 时无需换算夏令时;
  • 尽量在流量最低的时段运行任务;
  • 用 cron 来触发任务,而不是运行任务本体——用 rabbitmq 之类的真实队列系统处理实际任务;
  • 尽量让任务保持安静,只在出错时发邮件;
  • 不要屏蔽标准错误或标准输出:屏蔽前者会错过失败信息;屏蔽后者意味着你其实应该通过修改应用来调整日志级别;
  • 使用 Dead Man's Snitch 之类的服务验证 cron 任务是否成功完成;
  • 在 cronfile 中写大量注释,说明每个任务在做什么,免得日后花时间解读文件;
  • 将 cronfile 放在如/etc/cron.d/APP的模式路径下;
  • 不要在 cronfile 文件名中使用非 ASCII 字符,cron 对此很挑剔;
  • 记得 cronfile 末尾要有换行符,cron 同样很挑剔。

以下是一份可直接参考的应用 cronfile 示例:

# server cron jobs MAILTO="mail@dokku.me" PATH=/usr/local/bin:/usr/bin:/bin SHELL=/bin/bash # m h dom mon dow username command # * * * * * dokku command to be executed # - - - - - # | | | | | # | | | | +----- day of week (0 - 6) (Sunday=0) # | | | +------- month (1 - 12) # | | +--------- day of month (1 - 31) # | +----------- hour (0 - 23) # +----------- min (0 - 59) ### HIGH TRAFFIC TIME IS B/W 00:00 - 04:00 AND 14:00 - 23:59 ### RUN YOUR TASKS FROM 04:00 - 14:00 ### KEEP SORTED IN TIME ORDER ### PLACE ALL CRON TASKS BELOW # removes unresponsive users from the subscriber list to decrease bounce rates 0 0 * * * dokku dokku run node-js-app some-command # sends out our email alerts to users 0 1 * * * dokku dokku ps:scale node-js-app cron=1 && dokku enter node-js-app cron some-other-command && dokku ps:scale node-js-app cron=0 ### PLACE ALL CRON TASKS ABOVE, DO NOT REMOVE THE WHITESPACE AFTER THIS LINE

小结

Dokku 的定时任务体系由"声明(app.jsoncron键)— 验证(部署期校验时间表与命令分词)— 调度(宿主 crontab 或调度器原生后端)— 执行(一次性 run 容器)— 回收(24 小时 TTL)"五段组成。日常使用推荐完全走内置方案:用app.json声明、用cron:list/cron:report观察、用cron:suspend/cron:resume维护、用cron:run应急触发,并配合vector-cron-sink持久化输出;只有在需要细粒度控制或特殊约束时才考虑自管理 cron。相关可进一步阅读的仓库资料包括 cron 插件源码、cron 单元测试、app.json 格式定义 与 vector 日志集成文档。

【免费下载链接】dokkuA docker-powered PaaS that helps you build and manage the lifecycle of applications项目地址: https://gitcode.com/GitHub_Trending/do/dokku

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 13:50:47

AI 与传统办公自动化对比解读:选型指南与平台能力盘点

两类工具的演进脉络 办公自动化并不是新概念。从电子表格宏、脚本批处理&#xff0c;到 RPA 机器人流程自动化、BPM 流程管理系统&#xff0c;传统工具已经把大量规则固定、重复度高的工作自动化了。近两年进入办公场景的 AI 工作助手&#xff0c;尤其是 Work Agent 类平台&…

作者头像 李华
网站建设 2026/9/10 13:49:51

Android小窗口模式导航栏优化实践

1. Android小窗口模式导航栏调整需求解析 在Android 16系统中&#xff0c;小窗口模式&#xff08;Freeform Window&#xff09;的导航栏默认位置可能不符合某些应用场景的交互需求。特别是在横屏状态下&#xff0c;传统侧边导航栏会导致操作区域与内容区域的比例失衡。将导航栏…

作者头像 李华
网站建设 2026/9/10 13:47:33

mise bootstrap repos:在 mise.toml 中声明式管理 Git 仓库克隆与更新

mise bootstrap repos&#xff1a;在 mise.toml 中声明式管理 Git 仓库克隆与更新 【免费下载链接】mise dev tools, env vars, task runner 项目地址: https://gitcode.com/GitHub_Trending/mi/mise mise 的 bootstrap 系统可以在 [bootstrap.repos] 配置块中声明 Git …

作者头像 李华