gVisor tracereplay:将 runsc trace 会话落盘、回放与复用的完整实践
【免费下载链接】gvisorApplication Kernel for Containers项目地址: https://gitcode.com/GitHub_Trending/gv/gvisor
tracereplay是 gVisor 仓库内置的一个命令行工具,用于把通过remotesink 发出的runsc trace会话消息保存到本地文件,并在之后反复回放完全相同的消息序列。它解决的核心问题是:调试和测试 trace 消息时,往往需要配置好 runsc、配置 trace 会话并运行特定工作负载,成本很高。读完本文,你将掌握tracereplay save/tracereplay replay两个子命令的完整用法、trace 文件的二进制格式,以及如何结合examples/seccheck中的示例服务端查看回放的具体消息内容。
核心定位:为 trace 消息测试摆脱环境依赖
工具自身的包注释说明得很直接:它实现的正是“save and replay messages issued from remote.Remote”(tracereplay.go)。也就是说,它的工作对象是 gVisor 的seccheck 事件子系统中remote类型 sink 发出的消息流。
典型工作流分两段:
- 录制(save):
tracereplay save启动一个监听 runsc 连接的服务器,为每个连上来的 runsc 实例创建一个 trace 文件,把该实例触发的所有 trace 消息写入其中。 - 回放(replay):
tracereplay replay连接到一个远端端点(例如某个检查器或服务端),把 trace 文件里的消息按原始顺序逐条发出去——此时完全不需要 runsc、不需要配置 trace 会话、也不需要运行原始工作负载。
这使得依赖特定 trace 消息的测试可以脱离完整运行时环境独立运行。
save:把 trace 会话写进文件
tracereplay save启动一个监听服务器,接受 runsc 发起的新连接,并为每个 runsc 实例单独创建一个 trace 文件。下面的示例在/tmp/gvisor_events.sock上监听,并把 trace 文件写入/tmp/trace目录:
$ tracereplay save --endpoint=/tmp/gvisor_events.sock --out=/tmp/trace完整的命令行参数(定义在 main.go)如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
--endpoint | (必填) | 服务器监听的 Unix 域套接字路径。runsc 的remotesink 需要配置为连接同一个路径。启动前会先删除同名残留 socket 文件 |
--out | ./replay | trace 文件的保存目录,不存在时会自动创建 |
--prefix | client- | 每个 trace 文件的文件名前缀 |
服务器启动后会打印Listening on "…". Press ctrl-C to stop...,之后每来一个 runsc 连接,就会打印一行New client connected, writing to: "…",并在该客户端断开时打印写入的消息总数(见 save.go 中NewClient与Close的日志逻辑)。
让 runsc 连接到这个服务器
关键配置在 runsc 的 pod-init 配置里:使用trace_session,在其中声明要采集的 trace 点,并把 sink 配置为remote类型、endpoint指向tracereplay save监听的套接字路径。完整示例如下:
$ cat > /tmp/pod_init.json <<EOL { "trace_session": { "name": "Default", "points": [ { "name": "container/start" } ], "sinks": [ { "name": "remote", "config": { "endpoint": "/tmp/gvisor_events.sock" } } ] } } EOL $ runsc --rootless --network=none --pod-init-config=/tmp/pod_init.json do /bin/true配置说明:
points指定要采集的 trace 点,上例只采集container/start一个点;实际使用中可按需追加(trace 点的定义见 seccheck 包);sinks中name为remote,config.endpoint必须与tracereplay save --endpoint的路径完全一致。
执行成功后,tracereplay save一侧应看到:
New client connected, writing to: "/tmp/trace/client-0001" Closing client, wrote 1 messages to "/tmp/trace/client-0001"这表示 runsc 连上了服务器、触发了 1 条 trace 消息(即container/start点),并且连接已正常关闭。
trace 文件二进制格式
理解文件格式有助于排查问题,也能解释为什么replay能“按原样”复现消息。从 save.go 中NewClient的注释与实现看,文件结构为:
signature <size>Config JSON [<size>message]*- 文件开头是固定字符串签名
tracereplay file(tracereplay.go),用于快速识别这是否是一个合法的 trace 文件; - 签名后是一段 JSON 配置,包含
Config.Version字段,即录制时的 wire 协议版本(见 Config 定义); - 之后是消息序列,每条消息前面都有一个8 字节小端 uint64 长度前缀。
两个健壮性细节值得注意:
readSize对读到的长度做了上限保护,超过 1 MiB 直接报错,防止损坏或恶意文件导致内存耗尽(tracereplay.go);- 文件名按客户端序号递增生成(
<prefix>0001、<prefix>0002……),序号来自原子计数器(save.go),因此多个 runsc 实例先后连接同一个 save 服务器时互不覆盖。
replay:反复回放同一份消息
拿到 trace 文件后,tracereplay replay可以在任何时间、任意多次地回放完全相同的消息序列。参数(main.go):
| 参数 | 默认值 | 说明 |
|---|---|---|
--endpoint | (必填) | 回放目标端点的 Unix 域套接字路径,例如正在运行的检查服务器 |
--in | (必填) | 要回放的 trace 文件路径 |
使用上文录制出的文件回放:
$ tracereplay replay --endpoint=/tmp/gvisor_events.sock --in=/tmp/trace/client-0001 Handshake completed Replaying message: 1 Done从 replay.go 的Execute可以看到回放的完整流程:
- 以
AF_UNIX+SOCK_SEQPACKET创建套接字并连接--endpoint(与 seccheck remote 协议保持一致的传输方式); - 校验文件签名,读入带长度前缀的 JSON 配置,取出录制时的协议版本;
- 与对端完成Handshake 版本交换:发送
pb.Handshake{Version: …}并等待对端回应。replay 侧对服务器回传的任意版本都会接受(代码注释明确说明“只验证消息可反序列化,接受任何版本,然后尝试回放看结果”);而服务器侧则会严格校验客户端版本必须等于wire.CurrentVersion(当前为 1,见 server.go 与 wire.go); - 循环读取后续所有带长度前缀的消息,逐条写回套接字,直到文件末尾,打印
Done。
每条 trace 消息内部还带有 wire 头(HeaderSize/MessageType/DroppedCount,见 wire.Header),save只保存未解析的原始字节,因此回放时服务端能像处理 runsc 实时消息一样处理这些消息。
用示例服务端查看消息内容
如果想直观看到 trace 文件里存的是什么,可以用仓库自带的示例服务端 examples/seccheck/server.cc(构建目标examples/seccheck:server_cc)。它的监听地址正好也是/tmp/gvisor_events.sock,启动后再执行上面的 replay 命令:
$ bazel run examples/seccheck:server_cc Socket address /tmp/gvisor_events.sock Connection accepted Start => id: "runsc-865139" cwd: "/home/fvoznika" args: "/bin/true" Connection closed输出的Start => id: … cwd: … args: …正是被录制下来的container/starttrace 点事件,字段与当时 runsc 实例的元数据一致。
测试如何验证录制/回放的正确性
仓库自带了一个往返测试 tracereplay_test.go:TestBasic先启动一个NewSave服务器,然后用Replay把预生成的样例文件 testdata/client-0001 回放进该服务器,最后断言“重新保存出的文件与原始文件逐字节相同”。也就是说,整个工具链被验证为无损的replay original | save new ⇒ original == new过程。这个测试也给出了一个可复制的用法:把预录制的tools/tracereplay/testdata/client-0001当作现成的回放输入即可。
小结与路径索引
- 工具入口与子命令:tools/tracereplay/main/main.go、原始文档 tools/tracereplay/README.md
- 文件格式与读写实现:tools/tracereplay/tracereplay.go、tools/tracereplay/save.go、tools/tracereplay/replay.go
- 通用远端服务器(连接管理、handshake、消息分发):pkg/sentry/seccheck/sinks/remote/server/server.go
- wire 协议版本与消息头:pkg/sentry/seccheck/sinks/remote/wire/wire.go
- 示例查看服务端与配套示例配置:examples/seccheck/server.cc、examples/seccheck/pod_init.json
- 往返正确性测试与预录制样例:tools/tracereplay/tracereplay_test.go、tools/tracereplay/testdata/client-0001
适用前提:tracereplay走的是 seccheckremotesink 的 Unix 域套接字通道,因此 trace 会话必须配置remote类型 sink 且endpoint与 save 服务器的--endpoint一致;回放端需要有一个实现了相同 wire 协议的服务端在监听。基于SOCK_SEQPACKET的特性,该协议适用于本机套接字场景,跨主机传输需要额外手段。
【免费下载链接】gvisorApplication Kernel for Containers项目地址: https://gitcode.com/GitHub_Trending/gv/gvisor
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考