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:set、cron:list、cron:suspend、cron:resume、cron:run、cron: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.Cron是CronTask的列表,每个任务包含command、maintenance、schedule、concurrency_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条目 |
schedule | cron 兼容的调度定义,决定命令何时运行。秒通常不支持 |
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_policy非allow/forbid/replace时会返回"Invalid cron concurrency policy"错误;command与schedule为空时也会在部署阶段报错(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 模板中只指定了
PATH与SHELL两个环境变量: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/shell的shell.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_app与dokku_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:默认属性包含mailfrom、mailto、maintenance,其中mailfrom与mailto为全局属性。
列出 Cron 任务:cron:list
使用cron:list命令列出应用的 cron 任务,命令接收app参数:
dokku cron:list node-js-appID 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 --globalID Schedule Command 5cruaotm4yzzpnjlsdunblj8qyjp @daily /bin/true从源码看(plugins/cron/subcommands.go),stdout 格式的表格会额外展示Concurrency与Maintenance列;Maintenance列对任务级挂起显示true (task),对应用级维护显示true (app)。--format仅支持stdout与json两种值。任务 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> true,cron: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_CONTAINER、DOKKU_DISABLE_TTY(分离模式)、DOKKU_CONCURRENCY_POLICY、DOKKU_CRON_ID、DOKKU_RM_CONTAINER=1、DOKKU_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-mailto、computed-mailto、maintenance)。带cron-前缀的旧键(如cron-global-mailto)在 0.38.x 弃用窗口期仍会输出,并将在未来大版本中移除。
| 属性 | 作用域 | 默认值 | Report flags | 描述 |
|---|---|---|---|---|
mailfrom | 仅全局 | 无 | --cron-global-mailfrom、--cron-computed-mailfrom | cron 失败邮件使用的From:地址 |
mailto | 仅全局 | 无 | --cron-global-mailto、--cron-computed-mailto | cron 失败邮件的收件地址;为空则禁用邮件 |
maintenance | 应用 + 全局 | false | --cron-maintenance、--cron-global-maintenance、--cron-computed-maintenance | 为true时挂起应用(或全局)的所有 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.json的cron键)— 验证(部署期校验时间表与命令分词)— 调度(宿主 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),仅供参考