两个表达式看起来都像周三早上,但只有一个表示一次。0 10 * * 3 在周三 10:00 触发;* 10 * * 3 在十点这一小时的每一分钟都触发,启动同一条隧道的机会有六十次。如果命令是不检查现有会话的启动器,第二个版本就不是排程失误,而是会产生多个争夺所有者的循环。要先读分钟字段,再读其他任何东西。

五个字段依次是分钟、小时、日、月、星期,第一个字段里的星号表示每一分钟。

分钟字段决定触发多少次

开头的数字是分钟,所以 0 10 * * 3 是周三第十个小时的第一分钟。把它换成星号,小时和星期都保留,却失去了只执行一次的性质,从而产生六十次匹配。cron 不会去重:每次匹配都会再次启动命令。这不是 cron 的细枝末节,而是字段语义;而且值得去读已安装调度器的参考文档,因为用户级和系统级 crontab 的字段顺序与可选的用户名一列并不相同。

用一个无害命令测试一整天,让它记录自己的时间戳。日志里只有一行,那是排程;一小时内出现六十行,那是一课。

第二次启动会对第一次做什么

有教养的启动器会注意到连接已经建立,于是什么都不做。裸调 openvpn 则不会:它会再创建一个进程,再尝试占用一个设备或一个端口,并再建一套争抢相同目标网段的路由。在 NetworkManager 上,对同一个连接的重复激活请求通常是幂等的,这正是问题一直藏着、直到有人改用另一条命令才暴露的原因。排程应该去问已经在管理这条隧道的所有者。

然后要决定:连接正在建立时,一个请求意味着什么。重复激活和排队等待都不显然正确;选定一种,让启动器去实现,而不是让调度器碰巧做主。

先数命令前的字段

用户级和系统级 crontab 的差别在于命令前是否有一个用户名列。在两者之间复制条目,要么以错误的账户运行,要么把用户名当成命令的第一个词传进去。要查已安装的参考文档而不是教程,并数一数自己那一行的字段。五个字段、命令前还有第六个词的条目属于系统 crontab;同一行放进用户 crontab,就会试图把那个词当成程序来执行。

执行身份会连环境一起带过来。cron 任务不会继承已解锁的钥匙环、交互式密码提示,也不会继承你测试时所在的工作目录。

等待就绪,而不是等待时钟

启动成功不等于隧道可用。任务退出时,客户端可能还在解析端点、认证或安装路由。任何依赖任务都应该等待它真正需要的受保护资源,带上限超时和失败日志,而不是等固定的秒数。正是这一点区分了大多能用的排程和在忙碌清晨静默失败的排程。

如果那个时段需要交互式 MFA,任务可以请求人来处理,但无法提供应答。要把这种情况显式处理,而不是内嵌一个可重复使用的密码或者无限重试。

对于真正一次性的激活,请使用受支持的单次机制。需要记住再去删除的重复规则,就是未来的重复启动。

相关文章