Salt 调度执行模块 salt.modules.schedule 完全指南:管理与运维 minion 定时任务
【免费下载链接】saltSoftware to automate the management and configuration of infrastructure and applications at scale.项目地址: https://gitcode.com/gh_mirrors/sa/salt
导读
本文围绕 Salt(gh_mirrors/sa/salt)中的salt.modules.schedule执行模块展开,系统讲解如何通过saltCLI 在 minion 上查看、新增、修改、删除、启停定时任务,并结合底层调度器实现说明其工作原理。读者学完后可以熟练使用schedule.*系列命令管理单台或集群中的计划任务,并理解调度任务的持久化机制、pillar/opts 两种来源以及事件驱动的工作方式。
说明:关联文档 doc/ref/modules/all/salt.modules.schedule.rst 是 Sphinx 的
automodule自动文档入口,其全部技术内容实际来自模块源码 salt/modules/schedule.py 的 docstring 与实现。本文以该模块为主线,结合源码与测试展开。
一、模块概览与适用前提
salt.modules.schedule用于"管理 minion 上的 Salt 调度任务"(Module for managing the Salt schedule on a minion),自 Salt 2014.7.0 版本引入。它本质上是管理 Salt 内置调度器(scheduler)的用户接口:真正的调度循环实现在 salt/utils/schedule.py 的Schedule类中,而本模块通过向 minion 事件总线(event bus)发射manage_schedule事件来驱动调度器的增删改查。
依赖要求
- 模块 docstring 明确指出要求 minion 上安装 python-dateutil,否则
when(按指定时间执行)与range(时间段)相关能力不可用。 - 源码中通过 try/except 检测依赖:salt/modules/schedule.py:
try: import dateutil.parser as dateutil_parser _WHEN_SUPPORTED = True _RANGE_SUPPORTED = True except ImportError: _WHEN_SUPPORTED = False _RANGE_SUPPORTED = False- 若要在调度任务中使用
cron表达式,底层调度器还需要croniter库(见 salt/utils/schedule.py 的导入逻辑),该依赖缺失时cron选项会报错。
代理 minion 支持
模块顶部声明__proxyenabled__ = ["*"],意味着该模块可以被所有类型的 proxy minion 加载使用(如网络设备代理 minion),这是通过代理方式纳管网络设备时同样可以管理定时任务的依据。
二、调度任务的两种来源:opts 与 pillar
调度任务可以来自两个位置,理解这一点是正确使用本模块的前提:
- opts(配置)来源:来自 minion 配置文件或
minion.d/*.conf中的schedule段,以及通过模块命令新增后被持久化的任务。命令执行后默认会持久化到 minion 配置目录下 minion.d 中的_schedule.conf文件(该文件由 Salt 自动创建,文件名以下划线开头,属于 Salt 内部使用文件,详见 minion 配置文档)。 - pillar 来源:来自 master 下发的 pillar 数据中的
schedule段。来自 pillar 的任务不会被持久化到本地_schedule.conf,因为其生命周期由 pillar 管理。
这一点可以从源码中得到印证:模块的delete、modify、enable_job、disable_job、postpone_job、skip_job、move、copy等函数在操作时都会先判断任务属于where="opts"还是where="pillar",针对 pillar 中的任务会明确设置persist: False(例如 salt/modules/schedule.py),避免把 pillar 任务写入本地配置文件造成冲突。
三、任务字段白名单与通用选项
模块顶部定义了调度任务的字段白名单SCHEDULE_CONF(salt/modules/schedule.py),list输出任务时会只保留这些字段,其余字段被过滤。完整字段如下:
| 字段 | 含义 |
|---|---|
name | 任务名(调度器内部用于识别任务) |
function | 要执行的执行模块函数,如test.ping、state.apply |
args/kwargs | 传给 function 的位置参数列表与关键字参数字典 |
seconds/minutes/hours/days | 间隔周期(可组合使用,_seconds为内部归一化后的秒数) |
when | 指定具体时间点执行(依赖 dateutil,支持hh:mm或 ISO 格式列表) |
once/once_fmt | 只执行一次,once_fmt指定时间格式(默认%Y-%m-%dT%H:%M:%S) |
cron | cron 表达式(依赖 croniter) |
range | 仅在指定时间段内执行(依赖 dateutil) |
splay | 随机延时间隔,可为整数或{start: n, end: m}范围字典 |
maxrunning | 最大并行运行实例数,默认 1 |
enabled | 任务是否启用,默认 True |
jid_include | 是否把任务执行记录到 minion 的 job cache,默认 True |
returner | 结果返回器 |
return_config/return_kwargs | 返回器的配置选项与参数 |
metadata | 随任务结果一起返回的元数据 |
run_on_start | minion 启动/调度器重启时是否立即执行一次 |
until/after | 任务生效的截止/起始时间 |
skip_during_range/run_after_skip_range | 在指定时间段内跳过执行,以及跳过结束后是否补跑 |
return_job | 任务结束后是否发 job 返回事件 |
注意:模块别名映射为
__func_alias__ = {"list_": "list", "reload_": "reload"},因此 CLI 上使用schedule.list与schedule.reload即可。
四、任务生命周期管理:增删改查与启停
以下命令全部通过salt远程执行,目标可以是单个 minion 或一组 minion。
1. 查看任务:schedule.list
列出 minion 上当前所有调度任务:
# 列出全部任务(默认隐藏内部任务、显示禁用任务) salt '*' schedule.list # 显示所有任务(含 Salt 内部以 __ 开头的任务) salt '*' schedule.list show_all=True # 隐藏被禁用的任务 salt '*' schedule.list show_disabled=False # 仅查看 opts(配置)来源的任务 salt '*' schedule.list where=opts # 仅查看 pillar 来源的任务 salt '*' schedule.list where=pillar实现要点(salt/modules/schedule.py):
- 默认输出 YAML 格式,可通过
return_yaml=False改为返回 Python 字典; - 以
__开头的任务属于 Salt 内部自动添加(如 mine 更新),默认隐藏,show_all=True时显示; - 任务未显式声明
enabled时默认视为启用; - 每个任务会附加
saved: True/False字段,标识该任务是否已持久化到 minion 配置(即存在于_schedule.conf); - 支持
offline=True离线模式:不经过事件总线,直接从_schedule.conf文件读取(minion 未运行时也能查看)。
2. 新增任务:schedule.add
# 每 3600 秒执行一次 test.ping salt '*' schedule.add job1 function='test.ping' seconds=3600 # 带参数执行 cmd.run salt '*' schedule.add job2 function='cmd.run' job_args="['date >> /tmp/date.log']" seconds=60 # 离线模式(minion 未运行时直接把任务写入 _schedule.conf) salt '*' schedule.add job1 function='test.ping' seconds=3600 offline=True常用参数组合示例:
# 每 5 分钟执行,带 1~10 秒随机抖动 salt '*' schedule.add check_disk function='disk.usage' minutes=5 splay='{start: 1, end: 10}' # 每天 02:30 执行,基于 when(需 dateutil) salt '*' schedule.add nightly function='state.apply' when='02:30am' # 按 cron 表达式每周一凌晨执行(需 croniter) salt '*' schedule.add weekly function='pkg.upgrade' cron='0 3 * * 1' # 通过 pillar 下发任务(不持久化到本地) salt '*' schedule.add pillar_job function='test.ping' seconds=60 where=pillar关键行为(源码 salt/modules/schedule.py):
- 时间参数冲突校验:
seconds/minutes/hours/days不能与when或cron混用;when与cron也不能同时使用,违反时返回错误信息。 job_args必须是 list,job_kwargs必须是 dict,否则校验失败。- 若任务已存在则直接报错"already exists"。
- 支持
test=True试运行模式,只提示"would be added"而不真正添加。 - 默认
persist=True,任务会持久化到_schedule.conf;设置为persist=False则只对当前运行中的调度器生效。
3. 修改任务:schedule.modify
# 修改 job1 的执行周期 salt '*' schedule.modify job1 function='test.ping' seconds=7200 # 离线修改 salt '*' schedule.modify job1 function='test.ping' seconds=7200 offline=True实现要点(salt/modules/schedule.py):
- 目标任务不存在时报错
Job X does not exist in schedule.; - 未指定的字段沿用原任务值(
function缺省时自动取原值); - 比较新旧任务内容,若完全一致则返回
Job X in correct state(幂等); - 变更时会先在返回结果的
changes中记录old与new两份任务定义; - 支持
test=True试运行,以及offline=True离线写文件。
4. 删除任务:schedule.delete 与 schedule.purge
# 删除单个任务 salt '*' schedule.delete job1 # 删除全部用户任务(跳过以 __ 开头的内部任务与全局 enabled 标志) salt '*' schedule.purge # 离线删除 salt '*' schedule.delete job1 offline=Truedelete只针对单个任务,来自 pillar 的任务删除时不会持久化(salt/modules/schedule.py);purge遍历所有非内部任务逐个删除,同样支持test与offline模式(salt/modules/schedule.py);- 离线模式下删除操作在全部完成后一次性把新的调度配置写回
_schedule.conf。
5. 启停任务:enable / disable / enable_job / disable_job
# 启用/禁用某个任务 salt '*' schedule.enable_job job1 salt '*' schedule.disable_job job1 # 启用/禁用 minion 上全部任务(全局开关,对应 schedule 中的 enabled 标志) salt '*' schedule.enable salt '*' schedule.disableenable_job/disable_job针对单个任务,操作后校验任务状态是否符合预期(salt/modules/schedule.py);enable/disable操作的是全局enabled开关,disable后整个调度器暂停(salt/modules/schedule.py);- 均有
test=True试运行与persist持久化参数。
6. 立即执行任务:schedule.run_job
# 立即触发一次 job1 salt '*' schedule.run_job job1 # 任务被禁用时强制触发 salt '*' schedule.run_job job1 force=True实现要点(salt/modules/schedule.py):若任务存在且未禁用,则通过事件总线发射run_job指令让调度器立即执行一次;禁用状态下不带force=True会返回Job X is disabled.。
7. 持久化与重载:schedule.save 与 schedule.reload
# 把当前调度任务(非 pillar 部分)保存到 _schedule.conf salt '*' schedule.save # 从 _schedule.conf 与 pillar 重新加载调度任务 salt '*' schedule.reloadsave通过事件总线请求调度器把非 pillar 任务落盘(salt/modules/schedule.py);reload会先触发pillar_refresh刷新 pillar 中的 schedule,再读取<config_dir>/minion.d/schedule.conf(注意:这里读取的是schedule.conf,与模块命令持久化使用的_schedule.conf是不同文件,前者是用户在 minion.d 下手写的配置),将其中内容重新加载到运行中的调度器(salt/modules/schedule.py)。
五、跨 minion 分发:schedule.move 与 schedule.copy
# 把 job1 迁移到 web1 minion(原 minion 上删除) salt '*' schedule.move job1 web1 # 把 job1 复制到 web1 minion(原任务保留) salt '*' schedule.copy job1 web1 # 支持复合目标 salt '*' schedule.copy job1 'web*'实现要点(salt/modules/schedule.py):这两个函数读取任务定义后,把每个字段拼装为key=value形式的参数列表,通过__salt__"publish.publish"在目标 minion 上重新执行schedule.add。move在全部目标成功返回后才在原 minion 上delete;若有 minion 返回 False,则返回失败的 minion 列表并中止。返回结果中minions字段给出实际应答的 minion 列表。
六、时间控制:postpone_job / skip_job / show_next_fire_time / job_status
这三个功能自 Salt 2018.3.0 加入,用于对一次性/周期性任务做精确时间控制。
postpone_job:推迟任务
# 把原计划在 2026-09-24T03:00:00 触发的任务推迟到 03:30:00 salt '*' schedule.postpone_job job1 '2026-09-24T03:00:00' '2026-09-24T03:30:00' # 自定义时间格式 salt '*' schedule.postpone_job job1 '2026-09-24 03:00:00' '2026-09-24 03:30:00' time_fmt='%Y-%m-%d %H:%M:%S'实现要点(salt/modules/schedule.py):current_time与new_time均需符合time_fmt(默认%Y-%m-%dT%H:%M:%S),源码用datetime.datetime.strptime严格校验格式,解析失败返回Date string could not be parsed.。
skip_job:跳过任务
# 跳过 job1 在 2026-09-24T03:00:00 的这次触发 salt '*' schedule.skip_job job1 '2026-09-24T03:00:00'实现要点(salt/modules/schedule.py):与postpone_job类似,在指定时间点跳过任务执行,同样支持time_fmt自定义格式。
show_next_fire_time:查看下次触发时间
salt '*' schedule.show_next_fire_time job1实现要点(salt/modules/schedule.py):通过事件总线向调度器请求get_next_fire_time,返回next_fire_time字段,方便排查任务是否按预期排期。
job_status:查看任务运行信息
salt '*' schedule.job_status job1实现要点(salt/modules/schedule.py):请求调度器返回指定任务最近一次运行的状态信息(含data字段),源码会把其中的datetime对象统一格式化为字符串再返回,便于直接展示。
is_enabled:查询启用状态
# 查询单个任务是否启用 salt '*' schedule.is_enabled name=job1 # 不带 name 时返回调度器全局 enabled 状态(自 2015.5.3 加入) salt '*' schedule.is_enabled七、底层原理:事件总线驱动调度器
理解salt.modules.schedule的机制,关键在于它并不是直接操作调度数据结构,而是通过minion 事件总线与调度器通信:
- 模块函数(如
add)调用__salt__"event.fire"在本地事件总线上发射manage_schedule事件,payload 中携带func(add/delete/modify/enable/disable/run_job/save_schedule/reload 等)与任务数据; - minion 的调度器监听该事件并执行对应操作;
- 操作完成后,调度器在事件总线上回发对应的完成事件(如
minion_schedule_add_complete、minion_schedule_delete_complete、minion_schedule_list_complete等); - 模块函数用
event_bus.get_event(tag=..., wait=30)等待完成事件并校验complete字段,据此组装result/comment/changes返回给调用方。
例如list的实现(salt/modules/schedule.py)先发{"func": "list", "where": where},再等待minion_schedule_list_complete事件取回完整调度表。若事件模块不可用(如某些极简环境),函数会捕获KeyError并返回"Event module not available"的提示而非崩溃。
真正的调度循环位于 salt/utils/schedule.py 的Schedule类(约 1979 行),它负责:按seconds/minutes/hours/days、when、cron、once等规则计算下次触发时间、在skip_during_range时段跳过、应用splay随机延迟、受maxrunning限制并发、并把执行结果按returner配置返回。模块中的SCHEDULE_CONF白名单与调度器的字段约定保持一致,确保经模块管理的任务能被调度器正确解析。
八、离线模式与持久化文件
模块中多数写操作(add/delete/modify/purge)支持offline=True,用于minion 未运行时直接编辑调度配置文件:
- 配置文件路径由
_get_schedule_config_file()计算(salt/modules/schedule.py):取__opts__["conf_dir"](无则取 minion 配置所在目录),拼上default_include的目录(默认minion.d),最终指向minion.d/_schedule.conf; - 离线模式下跳过事件总线,直接以 YAML 形式读写该文件(写入内容形如
{"schedule": {...}}),写入失败会记录错误日志但不会中断(捕获OSError); - 正常在线模式下,
persist=True时调度器自身也会在完成事件后把非 pillar 任务写入同一文件。
因此查看任务时返回的saved字段正是以"该任务是否出现在_schedule.conf中"为依据(salt/modules/schedule.py)。minion 配置文档也明确提示:minion.d目录中以下划线开头的文件(典型如_schedule.conf)是 Salt 自行创建的内部文件,见 minion 配置文档。
九、测试与验证
仓库中为schedule模块提供了较完整的单元测试(tests/pytests/unit/modules/test_schedule.py),覆盖了:
test_add、test_delete、test_modify、test_purge:增删改与清空的参数解析与事件交互;test_build_schedule_item、test_build_schedule_item_invalid_when、test_build_schedule_item_invalid_jobs_args、test_build_schedule_item_jid_include:任务字段构建及非法when、非 list 的job_args等校验逻辑;test_enable_job、test_disable_job、test_enable、test_disable:任务级与全局启停;test_move、test_copy:跨 minion 分发的 publish 交互;test_is_enabled、test_job_status、test_list及全局 enabled 相关用例。
此外,状态模块 salt/states/schedule.py 提供了声明式的schedule.present/schedule.absent状态,可在 state 文件(SLS)中描述任务期望状态,由状态系统自动调用本执行模块完成收敛,适合把定时任务纳入版本管理;相关单测见 tests/pytests/unit/states/test_schedule.py。
十、最佳实践小结
- 任务尽量来自 pillar 或 state:用
schedule.present状态或 pillar 统一管理任务,避免在多台 minion 上手工schedule.add造成配置漂移;手工添加的任务需确认persist=True才能跨重启保留。 - 善用测试模式:
schedule.add/modify/delete/purge均支持test=True,先试运行确认变更内容再真正执行。 - 留意依赖:
when/range需要 python-dateutil,cron需要 croniter,缺依赖时任务会直接校验失败并给出明确报错。 - 区分全局与任务级启停:
schedule.disable会暂停整个调度器,schedule.disable_job只停单个任务;排障时先用schedule.list show_all=True观察enabled与saved字段。 - 跨机分发先小范围验证:
move会在目标全部成功后删除源任务,建议先在测试目标上copy验证,再执行move。
【免费下载链接】saltSoftware to automate the management and configuration of infrastructure and applications at scale.项目地址: https://gitcode.com/gh_mirrors/sa/salt
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考