- 运维
- 配置管理
- 后端
【免费下载链接】salt
Software to automate the management and configuration of infrastructure and applications at scale.
salt.runners.batch是 Salt 主控端(master)上用于管理与巡检异步批处理(async batch)任务的 Runner 模块,它围绕status、list_active、stop三个命令,让运维人员能够在salt -b批处理模式脱离前台之后,仍然掌握每个批量任务的状态、清单与停止手段。本文以该模块的官方文档为骨架,结合仓库内salt/utils/batch_state.py、salt/utils/batch_manager.py、salt/utils/batch_output.py等实现源码与单元测试,讲清三个命令的用法、返回值、底层状态机与事件驱动原理,帮助你把它直接用于生产环境的批量任务管控。
一、Runner 定位:异步批处理任务的"控制台"
Salt 的批处理(batch)模式通过salt -b(即--batch-size)将目标 minion 按固定窗口分批执行,避免一次性打满全网。传统上批处理由 CLI 前台驱动(同步模式),但从 RFC-0002 引入异步批处理支持(见 changelog/60269.added.md)后,批量任务的编排可以被卸载到 master 的事件循环上,由 master 侧的BatchManager进程驱动。
salt.runners.batch(源码见 salt/runners/batch.py)就是面向这个异步体系暴露给运维人员的 Runner 接口。它的模块 docstring 直接给出了四个典型用法:
# 查询某个异步 batch 任务的状态 salt-run batch.status 20240610120000000000 # 列出所有活跃的异步 batch 任务 salt-run batch.list_active # 停止一个正在运行的异步 batch 任务(优雅排空) salt-run batch.stop 20240610120000000000 # 停止并同时终止在途 minion 上的任务 salt-run batch.stop 20240610120000000000 kill=True模块通过__virtualname__ = "batch"声明虚拟名,因此所有函数都以batch.<function>形式被 Runner 装载调用。Runner 本身不做任何文件写入——状态查询走salt.utils.batch_state的只读接口,停止请求则通过 master 事件总线投递给BatchManager,由后者统一完成持久化与推进,职责边界非常清晰。
二、三个核心命令的完整说明
2.1 batch.status:查询单个 batch 任务状态
def status(jid): """ Return the current status of an async batch job. ... :param str jid: The batch JID to query. :returns: Summary dict or ``None``. :rtype: dict or None """- 参数:
jid为批处理任务统一的任务 ID(JID,例如20240610120000000000)。 - 行为:从该 JID 对应的作业缓存目录中读取
.batch.p状态文件,返回一个扁平化的 summary 字典。 - 返回值:若 JID 不存在,或该 JID 从未是异步 batch(没有
.batch.p),或缓存已被清理,则返回None。
调用示例:
salt-run batch.status 202406101200000000002.2 batch.list_active:列出全部活跃 batch
def list_active(): """ Return a list of all active (non-halted) async batch jobs. ... :returns: List of batch summary dicts. :rtype: list """- 读取
<cachedir>/batch_active.p活跃索引,逐个读取每个 JID 的.batch.p并生成 summary 列表。 - 容错设计:对于
.batch.p缺失或不可读的条目会静默丢弃——文档与源码明确指出,Maintenance(master 维护进程)会在下一轮收敛清理这些脏索引,因此 Runner 无需自行删改。 - JID 按字典序排序输出,便于阅读与对比。
调用示例:
salt-run batch.list_active2.3 batch.stop:停止运行中的 batch
def stop(jid, kill=False): """ Stop a running async batch job. ... :param str jid: The batch JID to stop. :param bool kill: When ``True``, also terminate in-flight minion jobs via ``saltutil.kill_job``. :rtype: bool """- 默认(
kill=False)是"优雅排空":只停止后续子批次的发布(halt 进一步调度),但保留在途 minion 任务的运行,这些任务的返回仍会通过正常的salt/job/<jid>/ret/<minion>路径写入作业缓存,进度不会丢失。 kill=True为强制终止:在触发 halt 事件之前,先向该 batch 当前处于 active(在途)状态的 minion 发布saltutil.kill_job,将参数jid以tgt_type="list"形式异步下发。对已经完成任务的 minion 是无害的 no-op(对应源码_kill_active_minions只取state["active"]中的 minion)。- 返回值:JID 未知或该 batch 已经处于
halted状态时返回False,否则返回True。
两种调用方式:
# 优雅排空:后续批次不再发布,在途任务跑完 salt-run batch.stop 20240610120000000000 # 强制终止:先 kill 在途 minion 任务,再停止调度 salt-run batch.stop 20240610120000000000 kill=True2.4 summary 返回结构:一个扁平、稳定的字段契约
status与list_active返回的每个 summary 都由_summary()统一构造(见 salt/runners/batch.py)。源码注释明确说明:刻意设计为扁平映射,方便salt-run --out=json消费,并且跨小版本保持稳定。字段含义如下:
| 字段 | 类型 | 含义 |
|---|---|---|
jid | str | 批处理任务 JID |
fun | str | 批处理执行的执行模块函数,如test.ping |
tgt | str | 目标表达式 |
tgt_type | str | 目标匹配类型,如glob |
total | int | 全部目标 minion 数(all_minions长度) |
completed | int | 已完成(done)minion 数 |
active | int | 在途(active)minion 数 |
pending | int | 待调度(pending)minion 数 |
failed | int | 失败(failed)minion 数 |
batch_size | int | 每批次大小 |
halted | bool | 是否已中止(异常终止) |
halted_reason | str/None | 中止原因(如failhard、stop) |
driver | str | 驱动方式:cli(同步 CLI 驱动)或master(异步 BatchManager 驱动) |
user | str | 发起 batch 的用户 |
created | float | 创建时间戳 |
last_progress | float | 最近一次推进时间戳 |
age_seconds | float/None | 距最近推进的秒数(now - last_progress),无进度记录时为None |
实际输出效果(JSON 模式下约 18 个字段):
salt-run --out=json batch.status 20240610120000000000{ "jid": "20240610120000000000", "fun": "state.apply", "tgt": "web*", "tgt_type": "glob", "total": 200, "completed": 80, "active": 20, "pending": 100, "failed": 0, "batch_size": 20, "halted": false, "halted_reason": null, "driver": "master", "user": "root", "created": 1718107200.0, "last_progress": 1718107350.0, "age_seconds": 30.0 }三、底层原理:BatchState 状态机与两级持久化
Runner 之所以"轻",是因为所有状态推进都落在salt.utils.batch_state(见 salt/utils/batch_state.py)这个与 I/O 完全解耦的纯状态机上。BatchState是一个普通 dict,包含all_minions、pending、active、done、failed、wait、batch_size、failhard、batch_wait、timeout、gather_job_timeout、halted、halted_reason、driver、user等键。
progress_batch()是推进核心:消费一批新返回后返回一个Actionnamedtuple,携带publish(下一步要发布的 minion 列表)、finished_minions、timed_out_minions、halted、halted_reason。其内部按固定顺序完成:处理返回 → 判定 failhard → 处理驱动上报超时 → 内部超时清扫 → 剪除过期wait记录 → 按batch_size - len(active) - len(wait)的空闲槽位从pending弹出新 minion 组成下一子批次。
3.1 .batch.p 与 batch_active.p
状态通过两级文件持久化(均由salt.utils.batch_state提供读写接口):
<cachedir>/jobs/<jhash[:2]>/<jhash[2:]>/<jid>/.batch.p:单个 batch 的完整状态快照。目录结构由salt.utils.jid.jid_dir()按 JID 的哈希分片生成(见 salt/utils/jid.py)。写入使用salt.utils.atomicfile.atomic_open()原子写,读者永远不会读到半截数据;读取失败(文件损坏)时返回None,由调用方按"不可恢复"处理,Maintenance 最终会清理。<cachedir>/batch_active.p:当前活跃 batch JID 集合的索引,同样原子写入。add_to_active_index/remove_from_active_index是"读-改-写"模式,正常运行时BatchManager是唯一写者,竞态窗口可忽略。
3.2 事件驱动的 Runner 与 BatchManager 协作
batch.stop之所以能生效,靠的是 master 事件总线。Runner 通过salt.utils.event.get_master_event()取得事件句柄,投递salt/batch/<jid>/stop事件(携带{"jid": jid, "reason": "stop"}),随后由BatchManager._handle_stop()完成真正的状态修改:置halted=True、写入.batch.p、发出salt/batch/<jid>/halted、并从活跃索引中退役该 JID(见 salt/utils/batch_manager.py)。
BatchManager是 masterProcessManager启动的SignalHandlingProcess,与 Maintenance、Reactor 引擎并列(见 salt/master.py)。它空闲时阻塞在get_event(wait=loop_interval)上,loop_interval由 master 配置batch_manager_loop_interval控制,默认 5 秒;每次醒来无论有无事件都会执行一次_tick()巡检,推进超时检测与batch_wait过期。
3.3 事件词汇表
salt.utils.batch_output(见 salt/utils/batch_output.py)集中定义了全部salt/batch/*事件标签与载荷构造,代码库中其它模块不应手工拼装这些标签:
| 事件标签 | 含义 |
|---|---|
salt/batch/<jid>/new | 新异步 batch 注册 |
salt/batch/<jid>/progress | 状态推进(含空闲心跳) |
salt/batch/<jid>/complete | 全部 minion 结清且未中止 |
salt/batch/<jid>/halted | 异常终止(failhard / stop / 损坏 / 陈旧) |
salt/batch/<jid>/recover | Maintenance 发现陈旧 batch 时触发恢复 |
salt/batch/<jid>/stop | batch.stopRunner 发出的停止请求 |
四、深入:stop 的两种模式在源码中的真实路径
batch.stop的完整链路值得展开,因为它同时演示了"事件投递"与"kill 发布"两条路径:
- 前置检查:
read_batch_state(jid, __opts__)读不到状态(日志batch.stop: no batch state found for jid ...)或state["halted"]为真时直接返回False,且不会发出任何事件。 - kill=True 分支:
_kill_active_minions(state)取sorted(state["active"].keys()),为空则直接跳过;否则用salt.client.get_local_client()异步下发:
local.cmd_async( list(minion_ids), # 在途 minion 列表 "saltutil.kill_job", # 复用现有按 minion 取消原语 arg=[state["jid"]], # 要杀掉的 batch JID tgt_type="list", )该原语的执行端是salt/modules/saltutil.py中的kill_job,因此不新增任何取消管道,完全复用既有能力。异常会被捕获并记录日志,不会中断后续 halt 流程。 3.halt 投递:以listen=False打开 master 事件句柄,firesalt/batch/<jid>/stop,随后BatchManager负责原子 halt 写入、发salt/batch/<jid>/halted并退役索引,Runner 返回True。
值得注意:同步 CLI 驱动的 batch(driver="cli")同样能被这三个 Runner 看到。3008.2 的发布说明(见 doc/topics/releases/3008.2.md)记录了 issue #69418 的修复:sync CLI 不再直接写 master 的cachedir(否则以 root 运行的 CLI 会以错误属主预建 JID 目录,导致 master 侧local_cache.prep_jid触发PermissionError),而是把每次状态迁移作为salt/batch/<jid>/{new,progress,complete,halted}事件"托运"给 master 侧的BatchManager代写。因此非 root master + root CLI 的部署形态下,batch.status/batch.list_active/batch.stop对同步 batch 同样生效;事件总线不可用时批处理仍能完成,只是 Runner 无可见性——优雅降级。
五、纵深:Maintenance 安全网与陈旧任务回收
Runner 的查询语义里多次出现"Maintenance 会在下一轮收敛",其实现位于 master 的Maintenance.handle_batch_jobs()(见 salt/master.py):
- 读取
batch_active.p索引,对每个活跃 JID 判断age = now - last_progress是否超过阈值timeout + gather_job_timeout + stale_buffer,其中stale_buffer = batch_manager_loop_interval * 6(默认 5s × 6 = 30s)。 - 超阈值的陈旧 batch 触发
salt/batch/<jid>/recover事件,BatchManager._handle_recover()重新从磁盘读取状态、重新收养进内存活跃集合并立即强制推进一次,让空余槽位尽快补发(见 salt/utils/batch_manager.py)。 .batch.p缺失/损坏或已halted的索引条目由 Maintenance 直接清理。
这解释了为什么batch.list_active可以"静默丢弃"坏条目而不自行修复:回收是后台进程的职责,Runner 只做只读快照。
六、实战:从 CLI 到 Runner 的完整管控闭环
一个典型的多批次滚动更新场景:
# 1. 前台发起异步 batch(示意:10 台一批,最多同时 10 台) salt -b 10 --batch-wait 5 'web*' state.apply nginx # 2. 另开终端巡检当前所有活跃 batch salt-run batch.list_active # 3. 单独盯一个批次,观察 completed/pending 变化 salt-run batch.status 20240610120000000000 # 4. 发现异常,先优雅排空(不再发新批次) salt-run batch.stop 20240610120000000000 # 5. 若仍需立刻止血,连在途任务一起终止 salt-run batch.stop 20240610120000000000 kill=True配套的 CLI 参数定义在 doc/ref/cli/salt.rst:-b/--batch-size接受显式数量或百分比(如10或25%),--batch-wait控制每个任务完成后、槽位让出给下一个之前的等待秒数,--batch-safe-limit/--batch-safe-size提供达到一定目标规模才自动转批处理的保护。批大小解析的严谨实现见salt.utils.batch_state.get_batch_size():支持"10"与"25%"两种写法,空 minion 列表时返回 1 以避免调用方到处做零值防护,格式非法时抛出salt.exceptions.SaltInvocationError。
Runner 层的单元测试覆盖了本文全部关键行为(见 tests/pytests/unit/runners/test_batch.py):
status对存在 batch 返回正确 summary(total/pending/halted/driver字段断言),对缺失 JID 返回None;list_active按索引列出、自动丢弃.batch.p缺失的"幽灵"条目、空索引返回空列表;stop优雅模式断言只 fire 一次salt/batch/JID1/stop事件且reason == "stop",缺失 JID 与已 halted 均返回False且不触碰事件总线;stop(kill=True)断言cmd_async只针对active中的 minion(m1、m2,不含 pending 的m3)、函数名为saltutil.kill_job、参数为 batch JID;无在途 minion 时跳过发布但 halt 事件照常发出。
集成层面,tests/pytests/integration/cli/test_batch.py 还覆盖了批大小数字/百分比、grains 目标、exit code、failhard 提前终止、批量 retcode 归并等端到端行为。
七、注意事项与版本背景
- 适用前提:
batch.status/batch.list_active/batch.stop面向异步批处理体系(含被BatchManager代管的同步 batch 可见性);若某个 JID 从未以 batch 形态运行或缓存被清理,status返回None,stop返回False,这是设计行为而非错误。 - 权限与属主:Runner 只读状态、只发事件,不写 master
cachedir;实际的.batch.p/batch_active.p写入统一由以 master 守护进程属主运行的BatchManager完成,规避了 3008.2 修复的 root CLI 与 salt master 属主冲突问题。 - 事件总线可用性:所有事件操作均为 best-effort——总线不可达时批处理照常完成,只是 Runner 命令失去可见性(对应 salt/cli/batch.py 中的
_fire_event/_subscribe_to_halt等事件胶水方法,全部自吞异常)。 - kill 的边界:
kill=True只针对active状态的在途 minion;已入pending(未下发)与已done的 minion 不受影响,这是"先杀在途、再停调度"语义的精确落点。
综上,salt.runners.batch是异步批处理体系的"运维控制面":status给你精确到每个 minion 状态的快照,list_active给你全局清单,stop给你从优雅排空到强制止血的完整处置能力,而其背后是BatchState状态机、BatchManager事件驱动进程与 Maintenance 安全网三者构成的健壮闭环。
- 运维
- 配置管理
- 后端
【免费下载链接】salt
Software to automate the management and configuration of infrastructure and applications at scale.
相关推荐
darktable 上手实践:RAW 照片编辑与开源摄影工作流的完整路径
darktable 上手实践:RAW 照片编辑与开源摄影工作流的完整路径 相机存储卡里的一批 RAW 文件,往往是摄影师最头疼的部分:商业软件按年收费,处理流程
运维配置管理后端EasySwoole 任务管理器实战:异步任务处理与性能优化
EasySwoole 任务管理器实战:异步任务处理与性能优化 EasySwoole 是一款基于 Swoole Server 开发的高性能 PHP 框架,其内置的
后端AntdUI任务处理:ITask与ITaskOpacity的任务管理与异步处理
AntdUI任务处理:ITask与ITaskOpacity的任务管理与异步处理 在现代WinForm应用开发中,流畅的用户体验和高效的异步处理是至关重要的。An
UI组件桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考