
一台服务器上运行 60 个容器
一个裸金属服务器运行数十到数百个 Hoody 容器。KSM 和 BTRFS 去重使边际成本接近零。
把 `.hoody/crontab` 签入仓库,与任务脚本放在一起。当部署脚本为 `main`、`feature/billing-v2` 或任何预览分支拉起容器时,它会把这份文件 PUT 到新容器的 Cron API。调度随分支一起发布——分支消失时,调度也跟着消失。
每个分支一份文件 · 所有仓库统一路径 · 不依赖共享的 cron 服务器
每个分支容器都运行 Hoody Cron。部署脚本读取签入的 crontab,把它 PUT 到新容器的 raw-crontab 端点。容器按文件描述运行调度——多一行没有,少一行也没有。
#!/bin/sh
# 为这个分支创建一个全新容器。
BRANCH=$(git branch --show-current)
CTR=$(hoody containers create --from main-snapshot)
# 用仓库里的 crontab 替换容器的 crontab。
curl -X PUT --data-binary @.hoody/crontab \
-H "Content-Type: text/plain" \
https://$CTR-cron-1.hoody.com/users/root/crontab
# 完成。分支的调度存活在它自己的容器里。
echo "deployed $BRANCH → $CTR"# Hoody Cron raw-crontab 端点——原子地替换整个文件。
PUT /users/root/crontab HTTP/1.1
Host: ctr_4d72b9-cron-1.hoody.com
Content-Type: text/plain
0 2 * * * /srv/jobs/billing-rollup-v2.sh
*/15 * * * * /srv/jobs/sync-stripe.py
@hourly curl -fsS http://localhost/healthz
*/5 * * * * /srv/jobs/diff-v1-v2.sh
HTTP/1.1 200 OK
# 200 OK:cron 守护进程重新加载,不到一秒调度生效。crontab 是分支随身携带的数据,而不是 cron 服务器需要记住的状态。删掉容器,没有任何条目要清理——文件随磁盘一起消失。
调度变成仓库里的一个文件后,三类工作就消失了。
当你把 `billing-rollup.sh` 改成 v2,新的调度落在同一个 PR 里。审核者直接在脚本旁边看到 cron 行。回滚一个 commit,调度也跟着回滚。
分支容器是临时的。当你合并或关闭分支时,容器被拆除。crontab 就在容器内,所以调度无需清理工——没有共享 cron 服务器留着过期条目。
`experiment/llm-rollups` 上每小时跑一次的实验任务跑在自己的容器、自己的文件系统里。staging 的 cron 守护进程看不到它;生产的 cron 守护进程也看不到它。任务脚本里不再需要 `if BRANCH_ENV` 分支。
标准的「一份运维管理的 crontab」模式和分支绑定模式在两个方向上失败。同样一份任务,影响半径完全不同。
差别不是某个功能——而是调度存放在哪里。是分支随身携带的一个文件,还是分支借用的共享表里的一行。
每容器的 Cron 是一个真正的 REST 接口——三组端点家族,标准 cron 语法,完整的按用户隔离。数字来自 Cron API 规范,不是凭空的基准。
每个分支容器都有自己按用户的 crontab。整文件 PUT、整文件 GET、原子替换。背后没有共享的调度表。
原始 crontab(GET/PUT)、托管条目(POST/PATCH/DELETE,带 UUID 和 `expires_at`)、按用户列表。部署脚本想用哪个就用哪个。
标准 `min hour day month dow`,再加上宏:`@hourly`、`@daily`、`@weekly`、`@monthly`、`@yearly`。和 `.hoody/crontab` 现在用的语法一样。
依据 Hoody Cron API:每个容器的 cron 服务 URL 上提供 GET/PUT /users/[user]/crontab 与 POST/PATCH/DELETE /users/[user]/entries。
调度就活在它要执行的代码旁边,在同一个容器、同一个分支里。
cron 调度过去藏过的六个地方,没有一个挨着代码。分支绑定的 crontab 让它们全部多余。
别再跨系统同步调度了。把 crontab 签进仓库。让分支带着它走。