KubeEdge Viaduct 示例实战:基于 QUIC 与 WebSocket 的云边加密通信与双向消息演示
【免费下载链接】kubeedgeKubernetes Native Edge Computing Framework (project under CNCF)项目地址: https://gitcode.com/GitHub_Trending/ku/kubeedge
Viaduct(高架桥)是 KubeEdge 中连接云端(CloudHub)与边缘端(EdgeHub)的通信桥,支持以 QUIC 和 WebSocket 两种底层传输协议承载 beehive 消息。本文以仓库中的 chat 示例 为线索,完整讲解其证书生成、双端启动、命令行参数,并结合 Viaduct 源码 剖析连接建立、消息路由、协议打包等底层原理。读完本文,你将能够独立编译运行 Viaduct 通信示例,理解消息多路分发(mux)与 QUIC 多流模型,并能对照生产环境中的 CloudHub/EdgeHub 实现进一步验证。
一、Viaduct 是什么:云与边之间的"高架桥"
Viaduct 的名字取自"跨谷之桥":山谷是云(Cloud)与边(Edge)之间的网络鸿沟,桥梁就是跨越鸿沟的连接。它利用 protobuf3.0 序列化 beehive 中定义的消息模型,并提供连接(Connection)与消息读写(Message)两类 API,当前支持两种底层传输协议:
- WebSocket:基于 gorilla/websocket,走标准的
wss://加密通道; - QUIC:基于 quic-go,基于 UDP 的多路复用可靠传输。
该定位在 pkg/viaduct/README.md 中有明确说明。在生产环境中,CloudHub 服务端 与 EdgeHub 客户端 正是通过这一层完成云边通道的建立与消息收发。
二、示例概览:chat 与 mirror
examples/chat 目录是理解 Viaduct 的最佳入口,包含:
| 文件 | 作用 |
|---|---|
| config/config.go | 通过 Go 标准库flag解析全部命令行参数 |
| main.go | 程序入口,根据--cmd-type分发到 server 或 client |
| server.go | 服务端:监听连接、注册消息路由、支持 stdin 交互发送 |
| client.go | 客户端:建立连接、注册消息路由、支持 stdin 交互发送 |
| common.go | 证书文件加载与双向 TLS 配置构造(GetTlsConfig) |
此外,同级的 mirror 示例 演示的是原始数据流(stream)模式:客户端把 stdin 数据原样推给服务端,服务端通过io.Copy回显到 stdout,可视为"管道回显器",与 chat 的消息模式形成对照。mirror 复用了 chat 的 config 包 来解析参数。
三、第一步:生成 CA 与证书对
chat 示例的 TLS 场景需要一张 CA 证书和一套证书/私钥对,同一套证书/私钥对可同时用于服务端和客户端。以下是 原文档 提供的完整 openssl 命令,按顺序执行即可:
# 1. 生成 CA 根私钥(4096 位,带 DES3 加密口令) openssl genrsa -des3 -out ca.key 4096 # 2. 生成 CA 根证书(SHA-256 签名,1024 天有效期) openssl req -x509 -new -nodes -key ca.key -sha256 -days 1024 -out ca.crt # 3. 生成通信实体私钥(2048 位) openssl genrsa -out chat.key 2048 # 4. 生成证书签名请求 csr,执行后会交互式询问国家/组织/通用名等,按要求填写即可 openssl req -new -key chat.key -out chat.csr # 5. 使用 CA 对 csr 签发证书(500 天有效期,SHA-256 签名) openssl x509 -req -in chat.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out char.crt -days 500 -sha256命令要点说明:
genrsa -des3会为 CA 私钥设置口令,后续使用ca.key时(第 2、5 步)会提示输入;-CAcreateserial会生成ca.srl序列号文件,用于保证每次签发证书的序列号唯一;-days 1024与-days 500分别为 CA 与实体证书的有效期,可按需调整。
注意:原文档第 5 步输出的文件名写作
char.crt(拼写如此),而后续启动命令引用的是./chat.crt。仓库目录中实际存在的文件是 char.crt。若严格照抄命令执行,第 5 步会生成char.crt;请按你实际生成的文件名在启动命令中保持一致(即把--cert=./chat.crt改为--cert=./char.crt,或把第 5 步输出名改为chat.crt)。
证书在源码中如何被使用
common.go 中的 GetTlsConfig 展示了这三类文件的标准加载方式:读取ca文件加入x509.CertPool作为客户端 CA 池,读取cert/key通过tls.X509KeyPair组装成服务端证书,最终构造出启用tls.RequireAndVerifyClientCert(强制校验客户端证书)的tls.Config——这正是云边双向认证的典型形态。
需要说明的是:从当前源码看,chat 的 server.go 与 client.go 中GetTlsConfig的调用被注释掉,实际运行时分别使用测试用自签名证书(generateTLSConfig)和InsecureSkipVerify: true。因此--key/--cert/--ca三个参数目前仍会被解析并接受,但尚未真正注入 TLS 握手流程,这属于示例代码为了便于快速联调而做的简化,生产环境(CloudHub/EdgeHub)则严格加载证书文件,具体见后文"生产环境对照"。
四、第二步:编译并启动 chat 示例
编译
cd pkg/viaduct/examples/chat go build编译成功后当前目录会生成可执行文件chat。由于 Viaduct 属于仓库内的独立 package,可直接在仓库根目录使用 Go Modules 完成构建(仓库已提供 go.mod 与 go.work)。
命令行参数一览
config.go 定义了全部参数,整理如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
--cmd-type | server | 运行模式,server或client |
--type | quic | 传输协议,quic或websocket |
--addr | 127.0.0.1:9890 | 服务端监听地址 / 客户端连接地址 |
--key | 空 | 私钥文件路径(如./chat.key) |
--cert | 空 | 证书文件路径(如./chat.crt) |
--ca | 空 | CA 证书文件路径(如./ca.crt) |
main.go依据--cmd-type分发:server调用StartServer,其余值走StartClient。
QUIC 模式
先启动服务端:
./chat --cmd-type=server --key=./chat.key --cert=./chat.crt --ca=./ca.crt --type=quic --addr=localhost:9890再启动客户端:
./chat --cmd-type=client --key=./chat.key --cert=./chat.crt --ca=./ca.crt --type=quic --addr=localhost:9890WebSocket 模式
服务端:
./chat --cmd-type=server --key=./chat.key --cert=./chat.crt --ca=./ca.crt --type=websocket --addr=localhost:9890客户端:
./chat --cmd-type=client --key=./chat.key --cert=./chat.crt --ca=./ca.crt --type=websocket --addr=wss://localhost:9890/testWebSocket 模式的关键差异在于客户端地址必须是完整的wss://URL,且路径/test必须与服务端 WSServerOption.Path 中配置的路径一致(示例代码固定为/test),否则握手会失败。
交互方式
两端启动后,任一端在send message:提示符下输入一行文本并回车,消息会通过 Viaduct 通道发送到对端,对端打印receive message: <内容>。服务端构建消息时使用 model.NewMessage 设置路由("server", "", "viaduct_message", "update"),客户端使用("client", "", "viaduct_message", "update");若消息被标记为同步(IsSync()为真),收到方还会回写应答(服务端回success,客户端回ack)。
五、源码深读:chat 示例背后的 Viaduct 原理
1. 服务端:Server 组装与连接管理
server.go 的 StartServer 展示了服务端对象的组装方式:
server := server.Server{ Type: cfg.Type, // "quic" 或 "websocket" Addr: cfg.Addr, TLSConfig: tls, AutoRoute: true, // 收到消息后自动路由到已注册的 mux 处理函数 ConnMgr: connMgr, // 连接管理器,可遍历/查找所有活跃连接 ConnNotify: ConnNotify, // 新连接建立时的回调 ExOpts: exOpts, // 协议扩展参数 }ExOpts按协议类型区分:QUIC 使用api.QuicServerOption{}(可配置MaxIncomingStreams),WebSocket 使用api.WSServerOption{Path: "/test"},见 api/server.go。
服务端在 goroutine 中调用ListenAndServeTLS("", "")启动监听(证书已通过TLSConfig注入,无需再传文件路径)。启动后进入 stdin 循环:每次输入都遍历connMgr中所有连接,通过conn.WriteMessageAsync(message)异步广播给每个已连接的客户端。connMgr底层是sync.Map,cmgr/connmgr.go 支持自定义连接 key 函数,默认以RemoteAddr()作为 key。
2. 客户端:Connect 与消息模式
client.go 的 StartClient 展示了客户端组装方式:
client := client.Client{ Options: client.Options{ Type: cfg.Type, Addr: cfg.Addr, TLSConfig: tls, AutoRoute: true, ConnUse: api.UseTypeMessage, // 连接仅用于消息模式 }, ExOpts: exOpts, }其中ExOpts在 QUIC 下为api.QuicClientOption{Header: header},在 WebSocket 下为api.WSClientOption{Header: header},header 中携带client_id: client1这类自定义请求头,可用于服务端识别客户端身份(生产环境中 CloudHub 正是用node_id、project_id头标识节点)。调用client.Connect()后打印连接状态(ConnectionState),随后进入 stdin 循环发送消息。
client/client.go 的 Connect 内部按Type选择协议客户端(NewQuicClient或NewWSClient),连接建立后以 goroutine 启动protoConn.ServeConn()持续接收对端消息,随后返回连接对象。
3. 消息路由:mux 模式匹配
chat 两端都通过 mux.Entry 注册了通配处理器:
mux.Entry(mux.NewPattern("*").Op("*"), handleServer) // 服务端 mux.Entry(mux.NewPattern("*").Op("*"), handleClient) // 客户端这表示"匹配任意 resource、任意 operation"。收到消息时,MessageMux.dispatch 会遍历注册条目:先用 MessagePattern.Match 校验 resource 与 operation,再调用对应处理函数,处理函数通过ResponseWriter回写应答。除了通配符*,路由还支持{name}与{name:regexp}形式的参数化路径——expr.go 会将其编译为正则表达式,并把捕获组注入MessageContainer.Parameter(name),这一点与 HTTP 路由框架的设计思路一致。若没有任何条目匹配,dispatch会返回错误。
路由分发之前还会经过可选的消息过滤器(filter/filter.go),过滤函数返回错误时消息被丢弃,用于实现消息级准入控制。
4. 消息序列化:protobuf 与翻译器
Viaduct 使用 protobuf3 定义线上消息格式,见 protos/message/message.proto:Message由MessageHeader(ID、ParentID、时间戳、同步标志、类型)与MessageRouter(Source、Group、Operation、Resouce)以及Content三部分组成。运行时由 translator/message.go 负责 beehive 的model.Message与 protobufmessage.Message之间的双向转换:Content支持[]byte、string,其他类型自动json.Marshal后写入。
5. 数据打包:packer 帧格式
QUIC 通道上的消息经过 packer 帧封装,包头共 10 字节:
| 字段 | 长度 | 说明 |
|---|---|---|
| Version | 4 字节 | 主/次/修复版本号,当前为 1.1.1(version.go) |
| PackageType | 1 字节 | 0x01消息包、0x02流包、0x04用户自定义包 |
| Flags | 1 字节 | 标志位,0x80表示压缩 |
| PayloadLen | 4 字节 | 负载长度,上限 32 MiB(MaxPayloadLen) |
packer.go 的 Pack/Unpack 实现了大端序编解码。lane/quic.go 在每条 QUIC stream 上执行"packer 读写 + translator 编解码"的完整链路;而 lane/wsnopack.go 中 WebSocket 模式直接以 JSON 读写消息(ReadJSON/WriteJSON),无需额外打包。
6. QUIC 的并发模型:控制流、消息流与流管理器
QUIC 连接天然支持多路复用。conn/quic.go 的QuicConnection结构体现了三条通路:
- 控制流(CtrlLane):
serveControlLan循环读取控制消息并回ack,负责传递 header、config、ping/pong 等控制信息; - 会话服务(serveSession):
AcceptStream循环接受对端新开的数据流,读取消息分发到 handler 或 FIFO;流关闭或会话异常时回调OnReadTransportErr(nodeID, projectID)通知上层; - 流管理器(StreamManager):smgr/smgr.go 维护消息流与二进制流两个池,
NumStreamsMax = 99限制最大并发流数,支持池化复用与自动释放。
同步消息由 keeper/synckeeper.go 实现:发送同步消息前注册消息 ID 对应的 channel,响应消息到达时按ParentID找到 channel 并投递,从而实现WriteMessageSync的请求-应答语义。异步收到的消息则进入容量 100 的 fifo/msgfifo.go,溢出时丢弃最旧消息并告警。
7. Connection 接口:统一两种协议
conn/conn.go 定义了Connection统一接口,覆盖消息级与原始字节级操作:ReadMessage/WriteMessageAsync/WriteMessageSync用于消息模式;Read/Write用于原始数据流;SetReadDeadline/SetWriteDeadline控制超时;ConnectionState()返回连接状态与对端证书信息。具体实现由 factory.go 按协议类型分派到NewQuicConn或NewWSConn。
六、延伸:mirror 示例与原始数据流模式
若只想快速验证 QUIC/WebSocket 通道本身,可运行 mirror 示例:
cd pkg/viaduct/examples/mirror go build # quic:先服务端后客户端 ./mirror --cmd-type=server --type=quic --addr=localhost:9890 ./mirror --cmd-type=client --type=quic --addr=localhost:9890 # websocket ./mirror --cmd-type=server --type=websocket --addr=localhost:9890 ./mirror --cmd-type=client --type=websocket --addr=wss://localhost:9890/test与 chat 不同,mirror 客户端使用api.UseTypeStream(client.go),把 stdin 通过io.Copy(conn, os.Stdin)原样写入连接;服务端在ConnNotify回调里通过io.Copy(loggerWriter{}, conn)把收到的原始字节流打印到 stdout。这演示了 Viaduct 的第二种连接用途:不解析消息、直接透传字节流,适合日志转发、端口代理等场景。协议类型常量定义在 api/type.go:连接用途分为UseTypeMessage(仅消息)、UseTypeStream(仅流)与UseTypeShare(消息+流共享),其中 WebSocket 模式暂不支持共享类型。
七、生产环境对照:CloudHub 与 EdgeHub 如何使用 Viaduct
chat 示例是 Viaduct API 的最小化演示,生产环境的用法完全一致,只是换上了真实的证书与鉴权头:
- 云端 CloudHub:servers/server.go 分别按配置启动 WebSocket 服务(
Path: "/")与 QUIC 服务(MaxIncomingStreams取自配置),两者都使用createTLSConfig构造强制双向证书校验(tls.RequireAndVerifyClientCert、TLS1.2 及以上、限定 ECDSA 密码套件)的tls.Config,AutoRoute: true并将连接通知回调到messageHandler.HandleConnection; - 边缘端 EdgeHub:quicclient.go 加载 CA 与证书/私钥构造
tls.Config,通过api.QuicClientOption{Header}携带node_id、project_id请求头连接云端,Send使用WriteMessageAsync,Receive使用ReadMessage;WebSocket 客户端 则按wss://URL 连接,并同样在 Header 中注入节点标识。
可见,chat 示例中--key/--cert/--ca三个参数对应的正是生产环境下云边双向 TLS 认证所需的证书材料,示例的通信流程与 CloudHub/EdgeHub 的实际链路一一对应。
八、小结
通过 chat 与 mirror 两个示例,可以完整掌握 KubeEdge Viaduct 通信层的核心用法:
- 证书先行:openssl 生成 CA 与实体证书对,支撑双向 TLS 认证;
- 双协议可选:同一套 API 同时支持 QUIC 与 WebSocket,仅需切换
--type与地址格式; - 统一 Connection 接口:消息模式(
WriteMessageAsync/ReadMessage)与流模式(Read/Write)覆盖云边通信的两种典型负载; - mux 路由 + protobuf 序列化 + packer 封帧:构成 Viaduct 的传输协议栈,为 CloudHub/EdgeHub 生产链路提供了坚实底座。
后续可继续阅读 viaduct 包目录 下的server、client、conn、lane、packer各子包源码,并结合 cloudhub 服务端 与 edgehub 客户端 验证示例与生产实现之间的一一对应关系。
【免费下载链接】kubeedgeKubernetes Native Edge Computing Framework (project under CNCF)项目地址: https://gitcode.com/GitHub_Trending/ku/kubeedge
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考