Salt 异步批处理任务管理实战:salt.runners.batch Runner 的 status / list_active / stop 深入解析
2026/9/24 14:21:59 网站建设 项目流程
  • 运维
  • 配置管理
  • 后端

【免费下载链接】salt

Software to automate the management and configuration of infrastructure and applications at scale.

项目地址:https://gitcode.com/gh_mirrors/sa/salt
点击查看免费下载

salt.runners.batch是 Salt 主控端(master)上用于管理与巡检异步批处理(async batch)任务的 Runner 模块,它围绕statuslist_activestop三个命令,让运维人员能够在salt -b批处理模式脱离前台之后,仍然掌握每个批量任务的状态、清单与停止手段。本文以该模块的官方文档为骨架,结合仓库内salt/utils/batch_state.pysalt/utils/batch_manager.pysalt/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 20240610120000000000

2.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_active

2.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,将参数jidtgt_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=True

2.4 summary 返回结构:一个扁平、稳定的字段契约

statuslist_active返回的每个 summary 都由_summary()统一构造(见 salt/runners/batch.py)。源码注释明确说明:刻意设计为扁平映射,方便salt-run --out=json消费,并且跨小版本保持稳定。字段含义如下:

字段类型含义
jidstr批处理任务 JID
funstr批处理执行的执行模块函数,如test.ping
tgtstr目标表达式
tgt_typestr目标匹配类型,如glob
totalint全部目标 minion 数(all_minions长度)
completedint已完成(done)minion 数
activeint在途(active)minion 数
pendingint待调度(pending)minion 数
failedint失败(failed)minion 数
batch_sizeint每批次大小
haltedbool是否已中止(异常终止)
halted_reasonstr/None中止原因(如failhardstop
driverstr驱动方式:cli(同步 CLI 驱动)或master(异步 BatchManager 驱动)
userstr发起 batch 的用户
createdfloat创建时间戳
last_progressfloat最近一次推进时间戳
age_secondsfloat/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_minionspendingactivedonefailedwaitbatch_sizefailhardbatch_waittimeoutgather_job_timeouthaltedhalted_reasondriveruser等键。

progress_batch()是推进核心:消费一批新返回后返回一个Actionnamedtuple,携带publish(下一步要发布的 minion 列表)、finished_minionstimed_out_minionshaltedhalted_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>/recoverMaintenance 发现陈旧 batch 时触发恢复
salt/batch/<jid>/stopbatch.stopRunner 发出的停止请求

四、深入:stop 的两种模式在源码中的真实路径

batch.stop的完整链路值得展开,因为它同时演示了"事件投递"与"kill 发布"两条路径:

  1. 前置检查read_batch_state(jid, __opts__)读不到状态(日志batch.stop: no batch state found for jid ...)或state["halted"]为真时直接返回False,且不会发出任何事件。
  2. 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接受显式数量或百分比(如1025%),--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(m1m2,不含 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返回Nonestop返回False,这是设计行为而非错误。
  • 权限与属主:Runner 只读状态、只发事件,不写 mastercachedir;实际的.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.

项目地址:https://gitcode.com/gh_mirrors/sa/salt
点击查看免费下载

相关推荐

上一篇:Windows AirPlay 2投屏终极指南:5步实现iOS设备无线投屏到Windows电脑
下一篇:CANN/opbase算子执行器预留接口

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询