logo
0
0
WeChat Login
docs: 更新文档并优化代码结构

Supercronic

Supercronic 有一篇发布公告博客文章

Supercronic 是一个与 crontab 兼容的任务运行器,专门设计用于在容器中运行。

为什么选择 Supercronic?

Crontab 是任务调度的通用语言,但典型的服务器 cron 实现不适合容器环境:

  • 它们在启动任务之前会清除环境变量。这在多用户系统中是一个重要的安全特性,但它破坏了容器的基本配置机制。
  • 它们捕获所运行任务的输出,并且通常要么想通过电子邮件发送此输出,要么直接丢弃它。在容器化环境中,将任务输出和错误记录到 stdout / stderr 通常更容易处理。
  • 它们通常不能优雅地响应 SIGINT / SIGTERM / SIGQUIT,并且在收到信号时可能会使正在运行的任务变成孤儿。这在服务器环境中是有意义的,因为 init 会处理孤儿任务,而且 Cron 也不经常重启,但在容器环境中这是不合适的,因为当容器退出时会导致任务被强制终止(即 SIGKILL)。
  • 它们通常尝试将日志发送到 syslog。当运行 syslog 服务器时,这很方便地提供了集中式日志记录,但对于容器,更倾向于简单地记录到 stdoutstderr

最后,它们通常是静默的,使这些问题难以理解和调试!

Supercronic 的目标是完全按照您期望在容器中运行的 cron 的行为方式运行:

  • 您的环境变量在作业中可用
  • 作业输出记录到 stdout / stderr
  • SIGTERM 触发优雅关闭(SIGINT 也是如此,当以交互方式使用时,您可以通过 CTRL+C 发送)
  • 作业返回代码和时间表记录到 stdout / stderr
  • SIGUSR2 触发优雅关闭并重新加载 crontab 配置
  • SIGQUIT 触发优雅关闭

它是如何工作的?

  • 安装 Supercronic(见下文)
  • 将其指向 crontab:supercronic CRONTAB
  • 完成!

它适用于谁?

我们(Aptible)最初创建 Supercronic 是为了让我们的无基础设施平台的客户能够轻松地在他们的应用中加入定期任务,但它更广泛地适用于任何在容器中运行 cron 作业的人

安装

下载

安装 Supercronic 最简单的方法是下载预构建的二进制文件。

导航到发布页面,获取适合您系统的构建版本。发布版本包括安装 Supercronic 的示例 Dockerfile 语句,您可以轻松地将它们包含在您自己的 Dockerfile 中或根据需要进行调整。

注意:如果您不确定哪个二进制文件适合您,请尝试 supercronic-linux-amd64

构建

您也可以从源代码构建 Supercronic。

运行以下命令来获取 Supercronic,安装其依赖项,并安装它:

go get -d cnb.cool/svn/supercronic cd "${GOPATH}/src/cnb.cool/svn/supercronic" go mod vendor go install

Crontab 格式

总的来说,Supercronic 尝试像 Vixie cron 一样处理 crontab。在大多数情况下,它应该与您现有的 crontab 兼容。

然而,有几个例外:

  • 首先,Supercronic 支持秒级时间表:在底层,Supercronic 使用 cronexpr,所以请参考其文档以了解您可以做的确切操作。
  • 其次,Supercronic 不支持在运行任务时更改用户。在您的 crontab 中设置 USER 将不会产生效果。在容器环境中,更改用户通常通过其他方式完成,例如,向您的 Dockerfile 添加 USER 指令。

支持的表达式格式

7 字段格式(秒级 + 年份)

# 语法:秒 分 时 日 月 星期 年 * * * * * * * # 每秒执行 */10 * * * * * * # 每10秒执行 0 30 */2 * * * * # 每2小时30分0秒执行 0 0 12 * * 1-5 * # 工作日中午12点执行

6 字段格式(分钟级 + 年份)

# 语法:分 时 日 月 星期 年 * * * * * 2026 # 2026 年内每分钟执行 0 0 * * * 2026 # 2026 年内每天午夜执行 30 8 * * 1-5 2026 # 2026 年工作日 08:30 执行

5 字段格式(传统分钟级)

# 语法:分 时 日 月 星期 * * * * * # 每分钟执行(自动添加0秒) */1 * * * * # 每分钟执行 0 */2 * * * # 每2小时执行 0 0 * * * # 每天午夜执行

特殊字符说明

  • * : 匹配任意值
  • */n : 每隔 n 个单位
  • x-y : 范围,从 x 到 y
  • x,y,z : 列表,匹配多个值
  • L : 最后(如每月最后一天 L,最后一个星期五 5L
  • W : 最近的工作日
  • # : 第几个星期(如 2#3 表示第 3 个星期二)

预定义表达式

@reboot # 启动时执行(不支持) @yearly # 每年1月1日午夜 等同于 0 0 0 1 1 * * @annually # 每年1月1日午夜 等同于 0 0 0 1 1 * * @monthly # 每月1日午夜 等同于 0 0 0 1 * * * @weekly # 每周日午夜 等同于 0 0 0 * * 0 * @daily # 每天午夜 等同于 0 0 0 * * * * @hourly # 每小时开始 等同于 0 0 * * * * *

示例 crontab

# 传统5字段格式 * * * * * echo "每分钟执行一次" */5 * * * * echo "每5分钟执行一次" 0 2 * * * echo "每天凌晨2点执行" # 6字段格式(添加年份) * * * * * 2026 echo "2026年内每分钟执行一次" 0 2 * * * 2026 echo "2026年每天凌晨2点执行" # 7字段格式(秒级+年份) */2 * * * * * 2024 echo "2024年每2秒执行" 0 0 12 1 * * 2024 echo "2024年每月1日中午12点执行" # 复杂表达式 0 9 * * 1-5 echo "工作日上午9点" 0 0 1 * * echo "每月1日午夜" 0 0 L * * echo "每月最后一天午夜" 5 0 * * 6 echo "每周六凌晨0点5分"

注意事项

  • 如果使用 5 字段或 6 字段格式,系统会在内部补全为 7 字段格式
  • 5 字段格式:自动添加 0 秒和 * 年份
  • 6 字段格式:解释为“分钟级 + 年份”
  • 需要秒级精度时,请显式使用 7 字段格式
  • 秒级任务请谨慎使用频率,避免过度消耗系统资源

环境变量

与常规 cron 一样,Supercronic 允许您在 crontab 中使用 KEY=VALUE 语法指定环境变量。

然而,这只是为了与现有 crontab 兼容,在使用 Supercronic 时通常不推荐使用此功能。

确实,Supercronic 不会在运行作业之前清除您的环境,因此如果您需要在作业运行时环境变量可用,只需在启动 Supercronic 本身之前设置它们,您的作业将继承它们。

例如,如果您使用 Docker,Supercronic 启动的作业将继承使用您 Dockerfile 中的 ENV 指令定义的环境变量,以及运行容器时传递的变量(例如,通过 docker run -e SOME_VARIABLE=SOME_VALUE)。

除非您以前使用过 cron,否则这正是您期望环境变量的工作方式!

时区

Supercronic 使用来自 /etc/localtime 的当前时区来调度作业。您也可以通过设置环境变量 TZ(例如 TZ=Europe/Berlin)在运行 Supercronic 时覆盖时区。您可能需要安装 tzdata 以便 Supercronic 能够找到提供的时区。

您可以覆盖 TZ 以使用不同的时区,但如果您需要让 cron 作业在时区 A 中调度并在时区 B 中运行,您可以在 /etc/localtimeTZ 设置为 B 的情况下运行,并向您的 crontab 添加一行 CRON_TZ=A

如果您不确定 Supercronic 正在使用哪个时区,可以使用 -debug 标志运行它以确认。

Prometheus 指标配置

  • --prometheus-listen-address: 绑定 Prometheus HTTP 端口,不设置则不启动。
  • --prometheus-include-command: 是否在指标中包含 command 标签,默认关闭以避免高基数;可通过环境变量 SUPERCRONIC_PROMETHEUS_INCLUDE_COMMAND=true 开启(可能导致动态 crontab 场景下指标维度爆炸、内存上升)。

v0.3.0 重要变更

  • 安全改进:默认关闭 command 标签以避免敏感信息泄露和高基数问题
  • 稳定性提升:修复了 SIGUSR2 重载时的并发竞态和优雅关闭超时问题
  • 资源优化:修复了 inotify watcher 的 goroutine 泄漏

详细变更请参考 CHANGELOG.md迁移指南

日志记录

Supercronic 提供丰富的日志记录,并将告诉您确切是什么命令触发了给定的消息。这是一个示例:

$ cat ./my-crontab */5 * * * * * * echo "hello from Supercronic" $ ./supercronic ./my-crontab INFO[2017-07-10T19:40:44+02:00] read crontab: ./my-crontab INFO[2017-07-10T19:40:50+02:00] starting iteration=0 job.command="echo "hello from Supercronic"" job.position=0 job.schedule="*/5 * * * * * *" INFO[2017-07-10T19:40:50+02:00] hello from Supercronic channel=stdout iteration=0 job.command="echo "hello from Supercronic"" job.position=0 job.schedule="*/5 * * * * * *" INFO[2017-07-10T19:40:50+02:00] job succeeded iteration=0 job.command="echo "hello from Supercronic"" job.position=0 job.schedule="*/5 * * * * * *" INFO[2017-07-10T19:40:55+02:00] starting iteration=1 job.command="echo "hello from Supercronic"" job.position=0 job.schedule="*/5 * * * * * *" INFO[2017-07-10T19:40:55+02:00] hello from Supercronic channel=stdout iteration=1 job.command="echo "hello from Supercronic"" job.position=0 job.schedule="*/5 * * * * * *" INFO[2017-07-10T19:40:55+02:00] job succeeded iteration=1 job.command="echo "hello from Supercronic"" job.position=0 job.schedule="*/5 * * * * * *"

日志文件轮转

Supercronic 支持自动日志文件轮转和压缩功能,无需外部工具即可管理日志文件。

基本用法

使用 --log-file 参数将日志输出到文件:

$ ./supercronic --log-file=/var/log/supercronic.log ./my-crontab

轮转配置选项

参数默认值说明
--log-file(空)日志文件路径,未设置时输出到 stdout
--log-max-size100单个日志文件最大大小(MB)
--log-max-age30保留日志文件的最大天数
--log-max-backups30保留的旧日志文件最大数量
--log-compresstrue是否压缩轮转后的日志文件为 .gz 格式

轮转示例

完整配置示例:

$ ./supercronic \ --log-file=/var/log/supercronic.log \ --log-max-size=50 \ --log-max-age=7 \ --log-max-backups=10 \ --log-compress=true \ ./my-crontab

当日志文件达到 50MB 时,会自动轮转:

  • 当前日志重命名为 supercronic-2024-12-15T14-30-25.123456789.log
  • 压缩为 supercronic-2024-12-15T14-30-25.123456789.log.gz
  • 创建新的 supercronic.log 继续记录
  • 超过 7 天或超过 10 个备份的旧文件会自动删除

Docker 容器中使用

在 Dockerfile 中配置日志轮转:

# 创建日志目录 RUN mkdir -p /var/log/app # 使用日志轮转运行 CMD ["/usr/local/bin/supercronic", \ "--log-file=/var/log/app/cron.log", \ "--log-max-size=100", \ "--log-max-age=30", \ "/etc/crontab"]

或通过 docker-compose.yml 配置:

services: cron: image: your-app command: - supercronic - --log-file=/var/log/cron.log - --log-max-size=50 - --log-max-age=7 - /etc/crontab volumes: - ./logs:/var/log # 挂载日志目录到宿主机

注意事项

  1. 日志目录权限:确保 Supercronic 进程对日志目录有写权限
  2. 磁盘空间:设置合理的 max-agemax-backups 避免占用过多磁盘
  3. 性能影响:日志轮转过程非常快速,对性能影响可忽略
  4. 时区设置:轮转后的文件名使用本地时区时间戳

调试

如果您的作业没有运行,或者您只是想再次检查您的 crontab 语法,请传递 -debug 标志以获取更详细的日志记录:

$ ./supercronic -debug ./my-crontab INFO[2017-07-10T19:43:51+02:00] read crontab: ./my-crontab DEBU[2017-07-10T19:43:51+02:00] try parse(7): */5 * * * * * * echo "hello from Supercronic"[0:15] = */5 * * * * * * DEBU[2017-07-10T19:43:51+02:00] job will run next at 2017-07-10 19:44:00 +0200 CEST job.command="echo "hello from Supercronic"" job.position=0 job.schedule="*/5 * * * * * *"

重复作业

Supercronic 会等待给定作业完成后再次调度该作业(某些 cron 实现这样做,其他则不)。如果作业落后于计划(即花费太长时间完成),Supercronic 会警告您。

这是一个示例:

$ cat ./my-crontab # 每秒睡眠2秒。这将花费太长时间。 * * * * * * * sleep 2 $ ./supercronic ./my-crontab INFO[2017-07-11T12:24:25+02:00] read crontab: ./my-crontab INFO[2017-07-11T12:24:27+02:00] starting iteration=0 job.command="sleep 2" job.position=0 job.schedule="* * * * * * *" INFO[2017-07-11T12:24:29+02:00] job succeeded iteration=0 job.command="sleep 2" job.position=0 job.schedule="* * * * * * *" WARN[2017-07-11T12:24:29+02:00] job took too long to run: it should have started 1.009438854s ago job.command="sleep 2" job.position=0 job.schedule="* * * * * * *" INFO[2017-07-11T12:24:30+02:00] starting iteration=1 job.command="sleep 2" job.position=0 job.schedule="* * * * * * *" INFO[2017-07-11T12:24:32+02:00] job succeeded iteration=1 job.command="sleep 2" job.position=0 job.schedule="* * * * * * *" WARN[2017-07-11T12:24:32+02:00] job took too long to run: it should have started 1.014474099s ago job.command="sleep 2" job.position=0 job.schedule="* * * * * * *"

您可以选择性地禁用此行为,并通过向 Supercronic 传递 -overlapping 标志来允许作业的重叠实例。Supercronic 仍会警告作业落后,但会运行它们的重复实例。

重新加载 crontab

向 Supercronic 发送 SIGUSR2 以重新加载 crontab:

# docker 环境(Supercronic 需要是容器中的 PID 1) docker kill --signal=USR2 <container id> # shell kill -USR2 <pid>

如果您在发送 SIGUSR2 比较麻烦的环境中运行 Supercronic,或者您期望频繁更新您的 crontab 文件,您可以选择使用 -inotify 标志运行 Supercronic。这将在 crontab 文件上启动监视,在更改时重新加载它。一个示例用例是一个运行 Supercronic 的 kubernetes pod,它从 configMap 挂载其 crontab 文件。使用 -inotify 标志,对此 configmap 的任何更新(前提是它不是不可变的)都将触发 Supercronic 中的重新加载,而无需您找出向 pod 发送 SIGUSR2 信号的机制。对 crontab 文件的监视在 WriteRemove 事件上触发,后者确保检测 kubernetes 的原子写入。

$ ./supercronic -inotify ./my-crontab ... time="2024-09-11T09:23:18+02:00" level=debug msg="event: CHMOD \"./my-crontab\", watch-list: []" time="2024-09-11T09:23:18+02:00" level=debug msg="event: REMOVE \"./my-crontab\", watch-list: []" time="2024-09-11T09:23:18+02:00" level=debug msg="watched file changed" time="2024-09-11T09:23:18+02:00" level=info msg="received user defined signal 2, reloading crontab" time="2024-09-11T09:23:18+02:00" level=info msg="waiting for jobs to finish" time="2024-09-11T09:23:18+02:00" level=debug msg="shutting down" job.command="sleep 2" job.position=0 job.schedule="* * * * *" time="2024-09-11T09:23:18+02:00" level=info msg="read crontab: ./my-crontab" time="2024-09-11T09:23:18+02:00" level=debug msg="try parse (7 fields): '* * * * * sleep 5'" time="2024-09-11T09:23:18+02:00" level=debug msg="failed to parse (7 fields): '* * * * * sleep 5': failed: syntax error in day-of-week field: 'sleep'" time="2024-09-11T09:23:18+02:00" level=debug msg="try parse (6 fields): '* * * * * sleep'" time="2024-09-11T09:23:18+02:00" level=debug msg="failed to parse (6 fields): '* * * * * sleep': failed: syntax error in year field: 'sleep'" time="2024-09-11T09:23:18+02:00" level=debug msg="try parse (5 fields): '* * * * *'" time="2024-09-11T09:23:18+02:00" level=debug msg="job will run next at 2024-09-11 09:24:00 +0200 CEST" job.command="sleep 5" job.position=0 job.schedule="* * * * *"

测试您的 crontab

使用 -test 标志提示 Supercronic 验证您的 crontab,但不执行它。这对于作为构建过程的一部分来验证 crontab 的语法很有用。

基于级别的日志记录

默认情况下,Supersonic 将所有日志路由到 stderr。如果您希望将此行为更改为基于级别的日志记录,请传递 -split-logs 标志以将调试和信息级别日志路由到 stdout

$ ./supercronic -split-logs ./my-crontab 1>./stdout.log $ cat ./stdout.log time="2019-01-12T19:34:57+09:00" level=info msg="read crontab: ./my-crontab" time="2019-01-12T19:35:00+09:00" level=info msg=starting iteration=0 job.command="echo \"hello from Supercronic\"" job.position=0 job.schedule="*/5 * * * * * *" time="2019-01-12T19:35:00+09:00" level=info msg="hello from Supercronic" channel=stdout iteration=0 job.command="echo \"hello from Supercronic\"" job.position=0 job.schedule="*/5 * * * * * *" time="2019-01-12T19:35:00+09:00" level=info msg="job succeeded" iteration=0 job.command="echo \"hello from Supercronic\"" job.position=0 job.schedule="*/5 * * * * * *"

集成

Sentry

Supercronic 提供与 Sentry 的集成,用于实时错误跟踪和报告。此功能有助于识别、分类和修复 cron 作业中的崩溃。

启用 Sentry

要启用 Sentry 报告,请配置 Sentry 数据源名称 (DSN),例如在启动 Supercronic 时使用 -sentry-dsn 参数

$ ./supercronic -sentry-dsn DSN

您也可以通过 SENTRY_DSN 环境变量指定 DSN。 当同时通过环境变量和命令行参数指定 DSN 时, 参数的 DSN 具有优先权。

其他 Sentry 配置

您还可以为 Sentry 指定环境和发布版本,以便为错误报告提供更多上下文:

环境:使用 -sentry-environment 标志或 SENTRY_ENVIRONMENT 环境变量在 Sentry 中设置环境标签。

$ ./supercronic -sentry-dsn YOUR_SENTRY_DSN -sentry-environment YOUR_ENVIRONMENT

发布版本:使用 -sentry-release 标志或 SENTRY_RELEASE 环境变量在 Sentry 中设置发布版本标签。

$ ./supercronic -sentry-dsn YOUR_SENTRY_DSN -sentry-release YOUR_RELEASE

问题和支持

如果您对 Supercronic 有任何问题,请随时在此存储库中打开 issue!

请注意,如果您尝试在 Aptible App 上使用 Supercronic,我们有专门的支持文章

贡献

PR 总是受欢迎的!在进行重大更改之前,考虑打开一个 issue 进行一些讨论。

许可证

参见 LICENSE.md

版权

版权所有 (c) 2019 Aptible。保留所有权利。