Cilium Hubble Relay 节点状态协议解析:relay.proto 中的 NodeStatusEvent 与 NodeState
【免费下载链接】ciliumeBPF-based Networking, Security, and Observability项目地址: https://gitcode.com/GitHub_Trending/ci/cilium
导读
本文深入解析 Cilium 仓库中 Hubble Relay 的节点状态协议(api/v1/relay/relay.proto 及其生成的文档 api/v1/relay/README.md)。Hubble Relay 是集群范围可观测性的聚合网关,它通过 gRPC 流式代理各节点的 Hubble 服务,而NodeStatusEvent正是它用来向客户端通告"哪些节点正在提供流、哪些节点不可达"的关键消息。读完本文,你将掌握该协议的消息结构、五种节点状态的完整语义、它在 Observer API 中的嵌入方式,以及 relay 服务端是如何基于底层连接状态生成这些事件的。
协议定位:Hubble Relay 的"节点健康广播"
在 Cilium 的可观测性架构中,hubble-relay 位于客户端与各节点 Hubble 实例之间:客户端(如cilium-dbg hubble、Hubble UI)只与 Relay 建立一条 gRPC 连接,Relay 再把GetFlows请求扇出到集群内所有可达的 Hubble peer,并将各 peer 返回的流按时间戳排序后合并返回。
由于底层 peer 的连接状态是动态变化的(节点滚动升级、网络分区、节点下线),Relay 需要一种机制向客户端说明"当前这份流数据来自哪些节点、缺失了哪些节点"。这就是relay包存在的意义——它定义了一套独立于流量数据本身的节点状态信令协议。该协议被 Observer API 以 oneof 分支的方式嵌入,客户端在解析流式响应时可以通过GetFlowsResponse.NodeStatus分支实时感知节点健康状况。
NodeStatusEvent:节点状态事件消息
NodeStatusEvent是协议的核心消息,定义于 relay.proto:
// NodeStatusEvent is a message sent by hubble-relay to inform clients about // the state of a particular node. message NodeStatusEvent { // state_change contains the new node state NodeState state_change = 1; // node_names is the list of nodes for which the above state changes applies repeated string node_names = 2; // message is an optional message attached to the state change (e.g. an // error message). The message applies to all nodes in node_names. string message = 3; }| Field | Type | Label | Description |
|---|---|---|---|
| state_change | NodeState | 节点的新状态 | |
| node_names | string | repeated | 该状态变更所适用的一组节点名列表 |
| message | string | 可选的消息附件(例如错误信息),作用于 node_names 中的所有节点 |
三个字段的设计意图非常明确:
state_change:一次事件描述一个状态迁移,枚举值见下文NodeState。node_names:由于一次事件通常影响多个节点(例如整个节点池同时不可达),协议用 repeated 字段批量声明,避免为每个节点单独发送一条事件。message:主要承载错误详情。从源码实现看,服务端构造事件时把底层 gRPC 错误文本放入该字段(见 pkg/hubble/relay/observer/observer.go),客户端可直接展示给用户,无需自行拼接错误。
NodeState 枚举:五种节点状态的完整语义
NodeState定义了节点在 Relay 视角下的全部生命周期状态,语义在 relay.proto 中有逐项注释:
| Name | Number | Description |
|---|---|---|
| UNKNOWN_NODE_STATE | 0 | 节点状态未知 |
| NODE_CONNECTED | 1 | 已与该节点建立连接,客户端可以预期观察到来自该节点的流 |
| NODE_UNAVAILABLE | 2 | 到该节点的连接当前不可用,客户端预期看不到来自该节点的流,直到连接重建或节点消失 |
| NODE_GONE | 3 | 节点已从集群中移除,不再尝试重连 |
| NODE_ERROR | 4 | 节点在处理请求时报告了错误,不再尝试重连 |
对照 relay.pb.go 生成的 Go 类型,NodeState的常量名与 proto 完全一致(如NodeState_NODE_CONNECTED、NodeState_NODE_UNAVAILABLE),Go 代码中通常以relaypb.NodeState_*引用。
五个状态共同刻画了节点的完整生命周期,值得注意的是终态与非终态的区别:
NODE_CONNECTED/NODE_UNAVAILABLE是动态状态,会随连接状态反复切换;NODE_GONE/NODE_ERROR是终态,注释明确声明"不再尝试重连"(No reconnection attempts will be made);UNKNOWN_NODE_STATE是 0 值兜底,用于"尚未确定"的过渡期。
协议嵌入:NodeStatusEvent 如何融入 Observer API
relay包并非独立服务,而是被 api/v1/observer/observer.proto 以类型引用的方式嵌入。共有三处引用点:
GetFlowsResponse.node_status:GetFlowsResponse的 oneof 分支ResponseTypes中包含node_status字段(observer.proto),即流式拉取GetFlows的过程中可以随时插入节点状态事件;ExportEvent.node_status:流式导出接口ExportEvent同样内嵌node_status字段(observer.proto),保证导出流中也能携带节点状态;Node.state:GetNodes返回的节点信息Node消息中,state字段直接复用relay.NodeState类型(observer.proto),用于在非流式场景下一次性描述各节点当前状态。
在生成的 Go 代码中,GetFlowsResponse通过 oneof 接口isGetFlowsResponse_ResponseTypes()区分"流数据"与"节点状态",客户端可用GetNodeStatus()方法安全断言(见 api/v1/observer/observer.pb.go)。
服务端实现:Relay 如何生成节点状态事件
理解了协议结构,再看 Relay 服务端的生成逻辑。核心实现在 pkg/hubble/relay/observer/observer.go 与 pkg/hubble/relay/observer/server.go。
事件构造器
nodeStatusEvent与nodeStatusError两个函数负责构造事件负载。前者生成普通状态事件(observer.go):
func nodeStatusEvent(state relaypb.NodeState, nodeNames ...string) *observerpb.GetFlowsResponse { return &observerpb.GetFlowsResponse{ NodeName: nodeTypes.GetAbsoluteNodeName(), Time: timestamppb.New(time.Now()), ResponseTypes: &observerpb.GetFlowsResponse_NodeStatus{ NodeStatus: &relaypb.NodeStatusEvent{ StateChange: state, NodeNames: nodeNames, }, }, } }后者在 peer 拉流失败时构造NODE_ERROR事件,并把 gRPC 错误信息提取后写入Message字段(observer.go)。
连接可用性判定
isAvailable通过底层 gRPC 连接状态判定 peer 是否可用(observer.go):
func isAvailable(conn poolTypes.ClientConn) bool { if conn == nil { return false } state := conn.GetState() return state != connectivity.TransientFailure && state != connectivity.Shutdown }即连接处于TransientFailure(瞬时失败)或Shutdown(已关闭)时视为不可用。
事件发送时序
在GetFlows处理流程中(server.go),Relay在正式转发流数据之前,先向客户端发送两批状态事件:先发送所有已连接节点的NODE_CONNECTED事件,再发送所有不可用节点的NODE_UNAVAILABLE事件,随后才进入sendFlowsResponse流式转发阶段。这保证客户端在收到第一批流之前就知道数据的覆盖范围,避免将"节点缺失"误判为"集群无流量"。
在follow模式下,Relay 还会周期性调用fc.collect重新评估 peer 列表,动态追加新的状态事件(server.go),实现节点状态的热更新。
错误聚合:NODE_ERROR 的合并发送
当多个 peer 同时失败时,若每个错误都单独发送一条事件会淹没客户端。aggregateErrors实现了错误聚合窗口(observer.go):在errorAggregationWindow时间内,具有相同错误消息的NODE_ERROR事件会不断把node_names追加合并,直到窗口超时才一次性发出。非错误事件则直接透传。这一设计与协议中node_names使用 repeated 字段的设计互为呼应。
测试验证:状态事件如何被断言
仓库通过表驱动测试覆盖了状态事件的生成逻辑,见 pkg/hubble/relay/observer/server_test.go:
- 构造
statusEvents []*relaypb.NodeStatusEvent作为期望值,例如StateChange: relaypb.NodeState_NODE_UNAVAILABLE(server_test.go)与StateChange: relaypb.NodeState_NODE_CONNECTED(server_test.go); - 对包含节点连接、节点不可用、节点报错(
NODE_ERROR)的混合场景逐一断言事件序列(server_test.go); - 通过
cmp.Diff比较期望与实际的完整事件流,并IgnoreUnexported(relaypb.NodeStatusEvent{})忽略内部未导出字段(server_test.go)。
阅读这些测试用例,可以快速理解各状态在真实场景中的触发路径,是学习该协议的最佳配套材料。
消费端视角:客户端如何利用节点状态
对 Hubble UI、cilium hubbleCLI 等客户端而言,消费该协议的核心模式是:
- 在
GetFlows流式响应中,用 oneof 分支识别node_status,维护一个"当前可用节点集合"; - 收到
NODE_CONNECTED时把节点加入集合,收到NODE_UNAVAILABLE/NODE_GONE/NODE_ERROR时移除并展示message; - 在 UI 上据此展示"当前流覆盖范围"与缺失节点告警。
非流式场景下,GetNodes返回的每个Node.state直接就是NodeState值,适合用于状态概览面板(实现见 pkg/hubble/relay/observer/server.go,其中NODE_CONNECTED的节点还会附带ServerStatus的版本、运行时长、流数量等附加信息)。
关于协议文档的生成
api/v1/relay/README.md 是标准的 protoc 文档生成产物(与 relay.proto 同目录)。其中除上述协议内容外,还包含一张通用的Scalar Value Types表格,列出 proto3 标量类型(double、float、int32、uint64、sint32、fixed32、bool、string、bytes等)在 C++、Java、Python、Go、C#、PHP、Ruby 各语言中的映射关系。由于NodeStatusEvent仅使用string与NodeState(枚举本质为int32变长编码)两种标量类型,该表格主要供跨语言客户端生成代码时参考,例如 Go 中string映射为string、bytes映射为[]byte、int32映射为int32。
小结
relay协议是 Hubble Relay 与客户端之间关于"数据覆盖范围"的约定:NodeStatusEvent以批量节点名 + 可选错误消息的方式承载状态变更,NodeState的五个枚举值完整刻画了节点从连接到消亡的生命周期。理解它,你就能正确解析 Hubble 流式 API 中的非流量事件,构建出能感知集群节点健康状况的可观测性客户端。若要进一步研究,建议从 relay.proto、observer.proto 的引用关系,以及 pkg/hubble/relay/observer 的完整实现三处入手。
【免费下载链接】ciliumeBPF-based Networking, Security, and Observability项目地址: https://gitcode.com/GitHub_Trending/ci/cilium
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考