一、作业执行时机为何总让人抓狂
用过Nomad跑批处理任务的朋友,应该都有过这种体验:你把一个巡检脚本放到Nomad里,心想“明天凌晨三点帮我跑一下”,结果到了第二天一看,它要么没跑,要么跑了三四次,要么卡在排队队列里一动不动。你一度怀疑是自己写错了cron表达式,但又觉得明明照着文档抄的,为什么就像是在碰运气?说到底,不是Nomad不听话,而是我们没搞懂它到底什么时候会去“看”作业。
Nomad本身是个调度器,它的职责是把任务放到合适的机器上跑起来。但它并不是你肚子里的蛔虫,你如果不告诉它“什么时候该跑”,它就只能按你提交时的样子,跑一次就结束了。想让作业像闹钟一样按时执行,就得借助“周期驱动”这个机制。你只需要把作业定义里的周期规则写清楚,剩下的重复触发、安置节点、拉起任务,全都交给Nomad去操心。
二、周期驱动到底是什么
周期驱动在Nomad里叫periodic,它好比一个定时闹钟,挂在你的作业定义上。只要时间一到,Nomad就会根据你写的规则,重新生成一个作业实例,然后调度执行。这个机制特别适合做那些“每隔一段时间就要做一次”的事,比如巡检、备份、清理日志、拉取数据。
2.1 一个最简单的周期任务长什么样
我们先写一个特别简单的例子,只看周期部分怎么生效。下面这个作业计划,每一个小时整点打个招呼。注意看注释,它清楚地标明了每个字段的含义。
# 技术栈:HCL(Nomad Job Spec)
job "hourly-hello" {
# 周期调度配置
periodic {
# cron 表达式:分 时 日 月 周
# 这里表示每天每个小时的第 0 分钟触发一次
cron = "0 * * * *"
}
datacenters = ["dc1"]
group "hello-group" {
count = 1
task "say-hi" {
driver = "raw_exec"
config {
command = "/bin/echo"
# 这里会打印一段话,方便你在日志里确认是否触发了
args = ["你好,周期任务开跑了!"]
}
}
}
}
把这段内容保存成hello.nomad,然后使用nomad job run hello.nomad提交。注意,提交之后它不会立刻跑,而是等下个整点才触发。如果你着急看效果,可以把cron表达式改成"*/2 * * * *",也就是每两分钟跑一次,改完再提交就行。
2.2 如何判断周期任务是否生效
你可以用nomad job status来查看作业的状态,如果作业的Type显示为batch,并且带有周期配置,那么它就会一直处于pending状态,而不是running。别担心,这不是卡住了,而是在等下一个触发点。当触发时间到了,Nomad会生成一个子作业,这个子作业才是真正执行的作业。这个“生成子作业”的动作,就是周期驱动的核心逻辑。
每次触发产生的子作业,可以在Nomad的UI或者命令行里看到,它们有自己的ID,执行完就变成dead。这样你就能很清楚知道哪次是几点几分跑的,结果如何,日志在哪。
三、实战:让Nomad按时去巡检每个节点
理论说再多,不如上手做一遍。下面我们做一个真正能用在生产环境的节点自动化巡检作业。巡检内容包括:当前系统时间、主机名、CPU负载、内存使用率、磁盘空间、以及最近几条登录记录。这些信息足够我们快速判断一台机器是否健康。
3.1 先设计巡检任务的执行逻辑
巡检逻辑其实很简单,就是一段Shell命令。但是如果我们把它硬编码在Nomad作业里,阅读和修改都不方便。更好的做法是,把巡检脚本写到Nomad模板里,让它在每次任务启动时自动生成脚本文件,然后执行。不过为了让大家先理解核心,我们先直接用一条内联命令来做。
3.2 编写一个完整的周期巡检作业
下面这个作业,每天凌晨3点整开始巡检。它会在每个符合条件的节点上执行一组命令,并把所有输出打印到标准输出里,方便我们通过Nomad的日志系统查看。注意,这里使用raw_exec驱动,它可以直接执行宿主机上的命令,不需要把命令包进容器。如果你所在环境不允许用raw_exec,可以换成exec驱动,效果类似。
# 技术栈:HCL(Nomad Job Spec)
job "node-inspect" {
# 指定作业类型为批处理,周期任务必须是 batch
type = "batch"
datacenters = ["dc1"]
# 周期驱动:每天凌晨 3 点整执行
periodic {
cron = "0 3 * * *"
# 防止上次还没跑完,下一次又触发,造成堆积
prohibit_overlap = true
}
# 可以在这里指定要巡检哪些节点
# 默认是全部可调度节点,如果你只想巡检某些标签,可以加 constraint
# constraint {
# attribute = "${node.datacenter}"
# value = "dc1"
# }
group "inspect-group" {
# 我们希望每个节点都跑一次,所以这里设置为每个节点创建一个实例
# 注意,count 在周期任务里通常固定为 1,配合 constraint 或 meta 实现多节点
count = 1
task "collect-metrics" {
driver = "raw_exec"
config {
command = "/bin/sh"
args = ["-c", <<EOT
echo "================ 巡检开始: $(date '+%Y-%m-%d %H:%M:%S') ================"
echo "------ 主机名 ------"
hostname
echo "------ 系统负载:1分钟/5分钟/15分钟 ------"
uptime
echo "------ 内存使用情况(MB) ------"
free -m
echo "------ 根分区磁盘使用率 ------"
df -h /
echo "------ 最近5条登录记录 ------"
last -5 2>/dev/null || echo "无登录记录"
echo "================ 巡检结束 ================"
EOT
]
}
# 给每次运行打上时间标签,方便查看日志
meta {
run_time = "${nomad.job.periodic.time}"
}
}
}
}
这份配置看起来不长,但已经把巡检任务完整地提交给了Nomad。其中prohibit_overlap特别有用,它保证同一时间只跑一个周期任务,不会因为上次任务没跑完又触发新的,导致系统资源被挤爆。meta里记录了这次触发的时间戳,这样你在查看日志时能很快对应上到底是哪次周期触发的结果。
3.3 怎么把巡检结果集中起来
直接打印到日志虽然方便,但管理起来还是有点散。更好的办法是让脚本把结果写到共享存储上,比如NFS、对象存储,或者发送到类似Elasticsearch这样的日志系统。这里我们简单改造一下:让每个节点把巡检结果写到一个以主机名命名的文件里,然后统一汇总。你可以在上面的task配置中再挂载一个宿主目录,或者使用artifact下载一个聚合脚本。为了不把示例搞得太复杂,这里只提一个思路,真正落地时你可以根据自己的基础设施来扩展。
四、应用场景、技术优缺点和注意事项
4.1 应用场景
这种“周期驱动+自动化巡检”的组合,特别适合下面几类场景:
- 服务器日常巡检:定时抓取CPU、内存、磁盘、网络等指标,发现异常及时报警。
- 证书到期检查:每天凌晨扫描各服务的SSL证书剩余天数,提前通知运维更换。
- 数据库一致性和备份校验:在低峰期周期性地跑校验脚本,确认备份数据可恢复。
- 日志与临时文件清理:每天定时删除过期日志、清理/tmp目录,避免磁盘被塞满。
- 定时定期拉取外部数据:比如每隔半小时同步一次价格表、股票行情等。
4.2 技术优缺点
先说说优点。周期驱动最大的好处就是“省心”。你把规则写好,剩下的交给Nomad,它自己会盯着时钟看。相比自己在服务器上写crontab,用Nomad还有这些优势:第一种,作业定义是代码,可以放进Git仓库里版本化;第二种,任务自动下发到集群内的所有节点,不需要一台一台去配置crontab;第三种,任务执行状态、日志、退出码都集中管理,出了问题排查很方便;第四种,支持prohibit_overlap,避免任务堆积。
再说说不足。Nomad的周期驱动本身并不保证“高可用”,如果Nomad服务器集群挂了,那定时任务自然也不会触发,这一点需要你提前做好Nomad集群的容灾部署。另外,周期任务默认执行时间基于Nomad服务器时钟,如果服务器时间不准,所有任务都会跟着偏掉。还有就是,如果你要用cron表达式做很复杂的调度,Nomad对cron的支持比较基础,不支持秒级和复杂的L、W等特殊字符,这个时候你就得用外部调度器配合Nomad API来实现了。
4.3 注意事项
在使用过程中,有几个地方需要特别留意:
- 周期任务类型必须是
batch,如果你不小心写成了service,周期配置可能不生效或者报错。 - 设置合理的
prohibit_overlap参数,否则遇到执行时间超过周期的任务,会堆积成山。 - 确保每个节点都有对应的驱动支持。比如使用
raw_exec驱动,需要提前在客户端配置文件里把它开起来,否则任务会一直卡在“pending”状态。 - 周期性触发的作业会产生大量历史子作业,如果不定期清理,Nomad的作业列表和数据存储可能会变得臃肿。建议定时清理已经
dead的作业。 - 如果你不想让某个作业在每个节点都执行,一定要加
constraint限制节点标签,或者使用datacenters来过滤区域。 - 巡检脚本如果有依赖外部包或环境变量,最好提前在系统里装好,或者把脚本打包成容器镜像,用
docker驱动来跑,这样更可控。
五、总结
Nomad的周期驱动其实并不神秘,它只是把“什么时候该跑”这件事从你脑子里搬到了配置文件中。对于集群节点自动化巡检这类重复性工作,用周期驱动再合适不过。你只需要把巡检内容拆成一段可执行脚本,定义好触发时间,再设置好防重叠和标签约束,剩下的就交给Nomad。相比传统crontab,Nomad让你一发配置就能掌控整集群的任务,还能从统一界面查看所有执行结果。虽然它也有限制,但只要我们把边界搞清楚,完全可以让定时任务成为集群里的“隐形助手”,该干活时绝不掉链子。
下次再遇到“作业为什么没跑”的困惑时,记得先去检查一下作业的周期配置是否正确,时间是否到了,以及有没有被prohibit_overlap拦住。技术很多时候就是这样,弄清楚背后的机制,一切就变得顺理成章了。
评论
围绕“Nomad系统作业执行时机难以把控?结合周期驱动实现集群节点自动化巡检任务”参与讨论