☰
Zeek Supervisor 框架进程监督 API 完全指南:NodeConfig、集群编排与远程控制
2026/10/8 7:44:21 网站建设 项目流程
  • 网络安全
  • 网络
  • IDS

【免费下载链接】zeek

Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.

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

本文是 Zeek 内置 Supervisor 框架核心 API 的权威技术指南,内容以 api.zeek 的官方 API 文档(api.zeek.rst)为主体骨架,并深入其脚本层实现与 C++ 底层源码。读完本文,你将掌握:如何使用Supervisor::create拉起持久的 Zeek 子进程、如何通过NodeConfig/ClusterEndpoint编排一个完整集群、如何用事件与钩子接管子进程的标准输出,以及如何借助 Broker 远程控制 Supervisor 进程树。

Supervisor 框架为 Zeek 引入了一种全新的运行模式:由一个"监督者"进程(Supervisor)管理一组应当长期存活的 Zeek 子进程,任何进程意外退出都会被自动复活,整个进程树在 Supervisor 退出时有序关闭。该 API 自 Zeek 3.1.0 引入,并在 4.0.0 起趋于稳定;在此之前它可能在不经通知的情况下发生不兼容变更。本文所有代码示例均来自当前仓库的真实脚本,可直接复制运行。

Supervisor API 概览:命名空间与组成

Supervisor API 全部位于Supervisor命名空间下,共分为四类构件:

类别成员说明
类型(Types)Supervisor::ClusterEndpoint、Supervisor::ClusterRole、Supervisor::NodeConfig、Supervisor::NodeStatus、Supervisor::Status描述被监督节点及其集群角色、当前状态
事件(Events)Supervisor::node_status节点(重新)启动成功时的通知
钩子(Hooks)Supervisor::stdout_hook、Supervisor::stderr_hook接管所有子进程的标准输出/错误流
函数(Functions)Supervisor::create、Supervisor::destroy、Supervisor::is_supervised、Supervisor::is_supervisor、Supervisor::node、Supervisor::restart、Supervisor::status节点生命周期管理与运行环境查询

脚本层的类型与函数声明定义在 scripts/base/frameworks/supervisor/api.zeek,实现则位于 scripts/base/frameworks/supervisor/main.zeek;底层与 C++ 引擎交互的 BIF 桥接层见 src/supervisor/supervisor.bif。

核心类型详解

ClusterRole:节点在集群框架中的角色

Supervisor::ClusterRole是一个枚举,定义了被监督节点在 Zeek Cluster Framework 中将要扮演的角色,取值如下:

枚举值含义
Supervisor::NONE不参与集群,作为独立节点运行
Supervisor::LOGGER集群日志记录节点
Supervisor::MANAGER集群管理节点
Supervisor::PROXY集群代理节点
Supervisor::WORKER集群工作节点(通常负责抓包与分析)

从源码看,该枚举定义于 api.zeek,并同步映射到 BIF 层(supervisor.bif),供 C++ 侧识别角色。

ClusterEndpoint:集群中某个节点的端点描述

Supervisor::ClusterEndpoint是一个 record,描述集群布局中一个被监督节点的端点信息(注意它描述的是节点配置,而非单条连接):

字段类型必填/可选说明
roleClusterRole必填该集群节点扮演的角色
hostaddr必填集群节点运行的主机/IP
pport必填集群节点监听的 TCP 端口
interfacestring&optional节点读取/分析数据包的网络接口名,通常由 worker 节点使用
pcap_filestring&optional节点读取/分析数据包的 PCAP 文件名,通常由 worker 节点使用
metrics_portport&optional节点向 Prometheus 暴露指标的 TCP 端口

NodeConfig:被监督节点的完整配置

Supervisor::NodeConfig是create 调用最核心的参数,完整控制被监督节点的行为:

字段类型默认值/可选说明
namestring必填节点名称,在给定监督进程树内唯一,通常人类可读
interfacestring&optional节点读取/分析数据包的网络接口名
pcap_filestring&optional节点读取/分析数据包的 PCAP 文件名
directorystring&optional节点使用的工作目录
stdout_filestring&optional节点 stdout 重定向到的文件路径
stderr_filestring&optional节点 stderr 重定向到的文件路径
bare_modebool&optional是否以 bare 模式启动节点;省略时继承 Supervisor 自身的 bare-mode 状态
addl_base_scriptsvector of string&default = []在 base 脚本之后、任何用户指定脚本之前加载的附加脚本
addl_user_scriptsvector of string&default = []在用户指定脚本之后加载的附加脚本
envtable[string] of string&default = {}在节点中定义的环境变量
cpu_affinityint&optional节点尝试将自己绑定到的 CPU/核编号
clustertable[string] of ClusterEndpoint&default = {}集群布局定义,键为节点名

其中cluster字段值得特别说明:集群框架中的每个节点都知晓自己所归属的完整、静态的集群拓扑。当 Supervisor 孵化被监督节点时,会自动把这个表翻译成正确的 Cluster Framework 配置——例如同时填充CLUSTER_NODE环境变量和Cluster::nodes表(参见 api.zeek 的注释)。在 C++ 侧,该字段被序列化为 JSON 字符串保存(见 Supervisor.h)。

NodeStatus 与 Status:状态查询结果

  • Supervisor::NodeStatus:单个被监督节点的当前状态,含两个字段——node: NodeConfig(期望的节点配置)与pid: int &optional(节点当前或最后已知的进程 ID,进程尚未启动时可能未初始化)。
  • Supervisor::Status:一组被监督节点的状态,仅含一个字段nodes: table[string] of NodeStatus,以节点名为键。

两者定义于 api.zeek,是Supervisor::status函数的返回类型。

生命周期管理函数

Supervisor命名空间下共 7 个函数,其中create、destroy、restart、status四个只能由 Supervisor 进程调用(从其他进程调用是错误用法)。

create:孵化新的被监督节点

function Supervisor::create(node: NodeConfig): string

传入期望的节点配置,返回空字符串表示成功,否则返回错误/失败描述。官方示例中典型的调用方式:

local sn = Supervisor::NodeConfig($name="foo", $interface="en0"); local res = Supervisor::create(sn); if ( res == "" ) print "supervisor created a new node"; else print "supervisor failed to create node", res;

destroy / restart / status:节点销毁、重启与状态查询

function Supervisor::destroy(node: string &default=""): bool function Supervisor::restart(node: string &default=""): bool function Supervisor::status(node: string &default=""): Status

三个函数都接受节点名作为参数,且都支持**空字符串表示"全部节点"**这一特殊语义:

  • destroy:销毁并移除被监督节点,成功返回true。
  • restart:通过先销毁(kill)再重新创建的方式重启节点,成功返回true。
  • status:返回当前状态——空串时返回所有节点的Status,指定名字时返回该节点的状态。

is_supervisor / is_supervised / node:进程角色判定

这三个函数用于让同一份脚本在 Supervisor 进程与子进程中都正确运行:

  • is_supervisor(): bool—— 当前进程是否为 Supervisor 进程。
  • is_supervised(): bool—— 当前进程是否为被监督节点进程。
  • node(): NodeConfig—— 若当前进程是被监督节点,返回其节点配置;从非被监督进程调用是错误。

值得注意:由Supervisor::create孵化的子进程会继承 Supervisor 进程加载的脚本(命令行参数也基本全部继承,唯一不继承的是-r(读 PCAP)与-i(实时接口)这两个参数)。因此脚本作者常用is_supervisor()与is_supervised()区分角色分支,用node()$name进一步区分多个子进程。

事件与钩子:感知节点状态、接管输出流

node_status 事件

event Supervisor::node_status(node: string, pid: count)

Supervisor 在收到来自 stem 进程的状态消息更新、确认某个节点已(重新)启动时触发。node是通过Supervisor::create创建过的节点名,pid是 stem 上报的进程 ID。该事件在 main.zeek 中实现:收到后立即通过 Broker 发布到SupervisorControl::topic_prefix + "/node_status",实现远程通知。

stdout_hook 与 stderr_hook

hook Supervisor::stdout_hook(node: string, msg: string): bool hook Supervisor::stderr_hook(node: string, msg: string): bool

这两个钩子接管所有子进程(包括内部 stem 进程)的 stdout/stderr 行缓冲输出。关键语义:

  • node:消息来源的节点名(此前通过create创建);空值表示消息来自内部 supervisor stem 进程(这种情况通常不应发生)。
  • msg:子进程 stdout/stderr 的行缓冲内容。
  • 若钩子以break结束,将抑制该行输出到关联流(stdout/stderr)。

例如希望过滤某个节点的调试噪音,可以这样注册:

hook Supervisor::stdout_hook(node: string, msg: string) { if ( node == "noisy-node" && /DEBUG/ in msg ) break; # 抑制该行 }

钩子声明见 api.zeek。

实战一:监督一个抓包节点

官方框架文档(doc/frameworks/supervisor.rst)给出了最简用法:simple-supervisor.zeek:

event zeek_init() { if ( Supervisor::is_supervisor() ) { local sn = Supervisor::NodeConfig($name="foo", $interface="en0"); local res = Supervisor::create(sn); if ( res == "" ) print "supervisor created a new node"; else print "supervisor failed to create node", res; } else print fmt("supervised node '%s' zeek_init()", Supervisor::node()$name); } event zeek_done() { if ( Supervisor::is_supervised() ) print fmt("supervised node '%s' zeek_done()", Supervisor::node()$name); else print "supervisor zeek_done()"; }

运行方式:

$ zeek -j simple-supervisor.zeek

其中-j是开启 Supervisor 模式的关键命令行参数(详见 supervisor.rst)。本地测试时请把en0换成真实可嗅探的接口名;若接口启用了校验和卸载(checksum offloading),可加-C忽略校验和:

$ zeek -j -C simple-supervisor.zeek

可以看到,这份脚本在 Supervisor 进程和子进程中都被加载执行,靠is_supervisor()/is_supervised()分支;子进程侧再用Supervisor::node()$name打印自己的节点名。Supervisor 退出时,整个进程树会有序关闭,子进程的zeek_done()同样会被触发。

实战二:用 Supervisor 编排完整集群

cluster-supervisor.zeek 展示了如何一键拉起 manager、logger、proxy、worker 四类节点:

event zeek_init() { if ( ! Supervisor::is_supervisor() ) return; Broker::listen("127.0.0.1", 9999/tcp); local cluster: table[string] of Supervisor::ClusterEndpoint; cluster["manager"] = [$role=Supervisor::MANAGER, $host=127.0.0.1, $p=10000/tcp]; cluster["logger"] = [$role=Supervisor::LOGGER, $host=127.0.0.1, $p=10001/tcp]; cluster["proxy"] = [$role=Supervisor::PROXY, $host=127.0.0.1, $p=10002/tcp]; cluster["worker"] = [$role=Supervisor::WORKER, $host=127.0.0.1, $p=10003/tcp, $interface="en0"]; for ( n, ep in cluster ) { local sn = Supervisor::NodeConfig($name=n); sn$cluster = cluster; sn$directory = n; if ( ep?$interface ) sn$interface = ep$interface; local res = Supervisor::create(sn); if ( res != "" ) print fmt("supervisor failed to create node '%s': %s", n, res); } }

要点拆解:

  1. 用Supervisor::ClusterEndpoint记录构造cluster表,键为节点名,值为端点信息(角色、地址、端口、接口)。
  2. 每个节点创建独立的NodeConfig:name取自节点名,cluster填入完整拓扑(集群框架要求每个节点都知道完整静态拓扑),directory设为节点名对应的子目录,使各节点工作目录隔离。
  3. worker 节点的接口名从ClusterEndpoint的interface字段传递到NodeConfig。
  4. Broker::listen("127.0.0.1", 9999/tcp)让 Supervisor 监听自己的端口,供外部远程控制(见下文)。
  5. 子进程的 stdout/stderr 输出会自动经由 Supervisor 转发,并加上节点名前缀,便于区分来源(见 supervisor.rst)。

运行:

$ zeek -j cluster-supervisor.zeek

实战三:通过 Broker 远程控制 Supervisor

Supervisor 进程除了提供本地 API,还能作为 Broker 端点接收远程指令。远程控制 API 定义在SupervisorControl命名空间(control.zeek),包含两个可重定义选项:

选项类型默认值说明
SupervisorControl::topic_prefixstring&redef"zeek/supervisor"订阅 Supervisor API 请求、发布响应所用的 Broker topic 前缀
SupervisorControl::enable_listenbool&redefF启用后,Supervisor 将监听配置的Broker::default_listen_address

以及一组成对事件:create_request/create_response、status_request/status_response、restart_request/restart_response、destroy_request/destroy_response、stop_request(无响应,Supervisor 收到即终止自身进程树)、node_status(Supervisor::node_status的远程等价物)。

官方控制端示例 supervisor-control.zeek:

event zeek_init() { Broker::peer("127.0.0.1", 9999/tcp, 1sec); } event Broker::peer_added(endpoint: Broker::EndpointInfo, msg: string) { Broker::publish(SupervisorControl::topic_prefix, SupervisorControl::restart_request, "", ""); } event SupervisorControl::restart_response(reqid: string, result: bool) { print fmt("got result of supervisor restart request: %s", result); terminate(); }

运行:

$ zeek supervisor-control.zeek

它会连接到 9999 端口上的 Supervisor,建立 Broker 对等关系后发布restart_request(reqid与node均为空串,含义是"重启全部节点"——适合改完脚本后热重载整个集群)。请求中的reqid是任意字符串,会被原样回显到响应中,用于关联请求与响应。

在 main.zeek 中可以看到请求分发的机制:zeek_init时若is_supervisor()且enable_listen则Broker::listen(),随后Cluster::subscribe(SupervisorControl::topic_prefix)订阅控制 topic。每个*_request事件处理函数都会调用对应的本地 API,并把结果发布回topic_prefix/<操作>/<reqid>主题。stop_request处理最简单——直接terminate()关闭整个进程树(main.zeek)。

内部架构:Supervisor → Stem → 被监督节点

理解 API 背后的进程模型,有助于合理预估资源占用与故障行为。官方文档(supervisor.rst)明确说明:顶层 Supervisor 进程并不直接管理被监督节点,而是先孵化一个中间进程Stem,由 Stem 负责被监督节点的生命周期。这样设计有两个原因:

  1. 避免对子进程执行exec()——否则每次孵化都依赖文件系统上当时的zeek二进制版本,系统升级期间可能引发兼容性/竞态问题。唯一仍需要exec()的情形是 Stem 进程本身意外死亡(属于罕见场景),此时 Supervisor 依据Supervisor::Config::zeek_exe_path(记录在 Supervisor.h)重新 fork+exec 出 Stem。
  2. Zeek 运行时普遍污染全局状态,尽早fork()出 Stem 相当于保留一份纯净的基线镜像,供所有被监督进程复用。

因此实际存在两级监督:Supervisor 复活 Stem,Stem 复活自己的子节点。此外,Stem 和任何被监督子进程都会自动检测"是否与父进程失联"并自我终止:Stem 每秒钟从其poll()循环醒来检查父 PID 是否变化;被监督节点则通过一个周期性Timer(ParentProcessCheckTimer,见 Supervisor.cc)完成同样检查。除失联检查与配置获取方式外,被监督节点运行期与普通 Zeek 进程并无差异。

节点复活策略:指数退避

supervisor.rst 与 Supervisor.cc 共同确认了节点复活(Node Revival)的确切算法,三个硬编码常量如下:

constexpr auto attempts_before_delay_increase = 3; // 每个延迟档位最多尝试 3 次 constexpr auto delay_increase_factor = 2; // 每档结束后延迟翻倍 constexpr auto reset_revival_state_after = 30; // 节点存活满 30 秒后重置复活状态

行为归纳:

  • 从1 秒延迟开始,Stem 尝试复活节点,最多 3 次;
  • 此后复活延迟翻倍(2 秒、4 秒、8 秒……),每档再试最多 3 次,如此无限继续——Stem永不放弃任何节点,但延迟持续增长;
  • 一旦节点存活超过 30 秒,复活状态清零,下次意外退出时重新从 1 秒延迟开始。

也就是说,频繁崩溃的节点不会被高频轰炸式重启,而是逐渐降低重启频率;稳定的节点则始终获得快速恢复。

BIF 层与 C++ 桥接

脚本 API 最终由 src/supervisor/supervisor.bif 桥接到 C++ 引擎(zeek::supervisor_mgr)。几个值得注意的实现事实:

  • __status/__create/__destroy/__restart在zeek::supervisor_mgr为空时(即未以-j启动 Supervisor 模式)会报错"supervisor mode not enabled",create 此时返回该错误字符串——这与"从非 Supervisor 进程调用是错误"的文档语义一致。
  • __is_supervisor直接判断zeek::supervisor_mgr != nullptr;__is_supervised判断Supervisor::ThisNode().has_value();__node从ThisNode()->config.ToRecord()还原脚本层的NodeConfigrecord。
  • 脚本层的NodeConfig在 C++ 侧对应Supervisor::NodeConfig结构体(Supervisor.h),并通过FromRecord/ToRecord/ToJSON/FromJSON在脚本层 record 与 JSON 之间转换——这为未来进程间传递配置提供了序列化基础。
  • BIF 中还有一个未出现在脚本 API 文档的Supervisor::__stem_pid():Supervisor 进程返回 stem PID,被监督节点返回父 PID,可用于调试进程树关系(supervisor.bif)。

使用限制与注意事项

  1. 调用主体限制:create、destroy、restart、status只能由 Supervisor 进程调用;node只能由被监督节点调用。混用会导致运行期错误。
  2. API 稳定性:该 API 自 3.1.0 引入,在 4.0.0 之前视为不稳定,可能不经弃用警告直接不兼容变更。
  3. 参数继承:子进程继承 Supervisor 的大多数命令行参数,但-r(PCAP)与-i(实时接口)不继承,必须在NodeConfig中显式指定interface/pcap_file。
  4. 命名唯一性:节点名在给定监督进程树内必须唯一;destroy/restart/status的空串参数会作用于全部节点,操作前请确认意图。
  5. 远程控制:若要使用SupervisorControl的远程事件,需在 Supervisor 侧显式Broker::listen(...)(如集群示例中的 9999 端口),否则外部无法连接;enable_listen选项默认关闭。
  6. 输出重定向:stdout_file/stderr_file与stdout_hook/stderr_hook都可用于处理子进程输出——前者重定向到文件,后者在脚本内接管;钩子以break结束可抑制对应行。

延伸阅读

  • API 完整文档:api.zeek.rst | 远程控制文档:control.zeek.rst
  • 框架概念与架构说明:doc/frameworks/supervisor.rst
  • 脚本层实现:main.zeek | control.zeek | api.zeek
  • 引擎层实现:Supervisor.cc | Supervisor.h | supervisor.bif
  • 官方示例:simple-supervisor.zeek | cluster-supervisor.zeek | supervisor-control.zeek
  • 网络安全
  • 网络
  • IDS

【免费下载链接】zeek

Zeek is a powerful network analysis framework that is much different from the typical IDS you may know.

项目地址:https://gitcode.com/gh_mirrors/ze/zeek
点击查看免费下载
上一篇:Android MenuDrawer 开源项目教程
下一篇:Stylebot代码编辑器详解:轻松编写专业级自定义CSS

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

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

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

立即咨询