Pixie Stirling 测试 Demo Apps 指南:为 BPF 探针与协议解析构建的可观测性测试靶场
Pixie Stirling 测试 Demo Apps 指南:为 BPF 探针与协议解析构建的可观测性测试靶场
本指南围绕 Pixie 仓库中 src/stirling/testing/demo_apps/README.md 展开,系统介绍 Stirling 数据采集引擎测试所依赖的 Demo 应用集合:这些二进制程序既充当 eBPF 探针的观测目标(probing targets),也作为产生流量的刺激源(stimulus)。读完本文,你将掌握 go_grpc_tls、go_https、hipster-shop 等测试应用的设计意图、源码要点与三种运行方式(skaffold、bazel run、docker run),并理解 Pixie 如何用这些应用验证对 TLS 加密流量、HTTP/HTTP2 混合库、gRPC 反射等场景的协议解析能力。
目录定位:为 Stirling 协议解析打造的可控流量环境
在正式介绍具体应用前,先明确该目录在整个项目中的位置与作用。Pixie 的 Stirling 模块(src/stirling/stirling.cc)负责在节点上通过 eBPF 采集应用协议数据。要验证协议追踪与解析的正确性,测试环境必须具备可预测、可重复、流量形态明确的客户端-服务器应用——这正是 src/stirling/testing/demo_apps/ 目录存在的意义。
从主 README(src/stirling/testing/demo_apps/README.md)可以看到它的明确定位:
This directory contains binaries that serve either as BPF probing targets, or as stimulus.
即目录中的每个二进制要么作为 BPF 探测目标(被 eBPF 探针挂载和观测的服务端进程),要么作为刺激源(主动向目标发起请求、制造可观测流量的客户端进程)。这种"靶场"式设计让测试既能验证探针正确附着到目标进程,又能验证协议解码器正确处理不同类型的应用层流量。
主 README 列出的核心应用包括:
- go_grpc_tls:基于 TLS 的 gRPC 客户端-服务器,使用 Golang 编写;
- go_https:基础的 HTTPS 客户端-服务器,使用 Golang 编写;
- hipster-shop:指向
productcatalogservice的客户端,使用 Golang 编写。
实际目录中(见 src/stirling/testing/demo_apps/)还包含 go_http、py_grpc、leaky_http_server、leaky_http_unix_socket_server、node 等更多测试程序,下面按主题逐一深入。
go_grpc_tls_pl:混合 HTTP/HTTP2 API 的 gRPC over TLS 靶场
设计动机:覆盖 Pixie 自身服务框架的库调用模式
目录 src/stirling/testing/demo_apps/go_grpc_tls_pl/ 中的 _pl 后缀含义在其 README 中有明确说明:Pixie 自己的服务框架混用了 Golang 的 HTTP 与 HTTP2 API,这是一种被广泛使用的库调用模式,Pixie 必须支持这种模式,因此专门构建了该测试应用来覆盖。
更深一层,该应用的客户端和服务端针对不同版本的 Golang 发行版分别构建,以便测试能验证 Stirling 在不同 Go 版本下的探针兼容性。这正是"用测试应用反推实现质量"的典型做法:Go 运行时版本更迭会影响 TLS 实现与内部数据结构布局,而 eBPF 探针依赖对这些结构的偏移推断,因此跨版本测试至关重要。
服务端源码要点:h2c + TLS 双栈监听
服务端实现在 server/server.go:
const port = ":50400"
// 命令行参数
serverCert := flag.String("server_tls_cert", "", "Path to server.crt")
serverKey := flag.String("server_tls_key", "", "Path to server.key")
caCert := flag.String("tls_ca_cert", "", "Path to ca.crt")
// TLS 配置:要求 h2 协议协商
config := &tls.Config{
Certificates: []tls.Certificate{pair},
NextProtos: []string{"h2"},
ClientCAs: certPool,
}
// gRPC server 通过 h2c handler 暴露
muxHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
grpcServer.ServeHTTP(w, r)
})
httpServer := &http.Server{
Addr: port,
Handler: h2c.NewHandler(muxHandler, &http2.Server{}),
TLSConfig: config,
ReadTimeout: 1800 * time.Second,
WriteTimeout: 1800 * time.Second,
MaxHeaderBytes: 1 << 20,
}
lis = tls.NewListener(lis, httpServer.TLSConfig)
关键点在于:服务端同时使用 golang.org/x/net/http2/h2c(支持明文 HTTP/2 升级)与 tls.NewListener 包装的标准 HTTP/2 监听,实现 gRPC(HTTP/2)与普通 HTTP 请求在同一端口上的共存。日志输出 Starting HTTP/2 server 用于测试程序探测服务端就绪状态。注意 SayHello 是 gRPC 服务 Greeter 的唯一 RPC,定义于 server/greetpb/service.proto。
客户端源码要点:证书校验与压测循环
客户端实现在 client/client.go,参数通过 pflag + viper 解析:
| 参数 | 默认值 | 说明 |
|---|---|---|
client_tls_cert |
空 | 客户端证书 .crt 路径 |
client_tls_key |
空 | 客户端私钥 .key 路径 |
tls_ca_cert |
空 | CA 证书路径 |
address |
localhost:50400 |
服务端地址 |
count |
1000 |
发送的请求数量 |
客户端加载双向 TLS(mTLS)所需证书后,以 credentials.NewTLS(config) 建立 gRPC 连接,随后循环调用 client.SayHello,每轮间隔 1 秒:
for j := 0; j < viper.GetInt("count"); j++ {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
name := fmt.Sprintf("%d", j)
resp, err := client.SayHello(ctx, &greetpb.HelloRequest{Name: name})
...
time.Sleep(time.Second)
}
这种"每秒一请求"的节流节奏,配合服务端 1800 秒的超时设置,为 eBPF 探针提供了持续、规律的 gRPC over TLS 流量源。
运行方式:skaffold 一键部署
按照 go_grpc_tls_pl/README.md 的说明,使用 skaffold 运行:
kubectl create ns px-grpc-test
skaffold run -f src/stirling/testing/demo_apps/go_grpc_tls_pl/skaffold.yaml
对应的 skaffold.yaml 中,镜像通过 bazel 构建(golang_1_20_grpc_tls_server.tar / golang_1_20_grpc_tls_client.tar),清单由 kustomize 从 k8s/ 目录渲染,并提供了 aarch64_sysroot profile 用于 ARM 环境交叉构建。其中 server_deployment.yaml 暴露容器端口 50400,与源码中的监听端口一致。
go_https:Go TLS 追踪能力与多 Go 版本回归测试
设计动机:Go 版本退役带来的构建策略
src/stirling/testing/demo_apps/go_https/ 用于测试 Pixie 的 Go TLS 追踪能力。其 server/README.md 揭示了重要的工程决策:随着 Go 版本陆续退出支持,把这些旧版本长期维护在 bazel 构建中会阻碍 Pixie 升级 Go 依赖与自身 Go 版本。因此该应用通过 bazel 构建 + update_ghcr.sh 脚本发布镜像的组合来保持对旧 Go 版本的测试覆盖。
同时 README 还提到,Pixie 即将推出的基于 opentelemetry-go-instrumentation 的 offsetgen 追踪方案,需要直接用 Go 工具链构建二进制(在 bazel-contrib/rules_go 的对应 issue 解决之前),这也是该目录采用独立构建流程的原因。
服务端源码要点:HTTP 与 HTTPS 双端口
server/https_server.go 同时监听两个端口:
const (
httpPort = 50100
httpsPort = 50101
)
func main() {
certPath := flag.String("cert", "", "Path to the .crt file.")
keyPath := flag.String("key", "", "Path to the .key file.")
flag.Parse()
http.HandleFunc("/", basicHandler)
go listenAndServeTLS(httpsPort, *certPath, *keyPath)
listenAndServe(httpPort)
}
根路径 / 返回固定 JSON 响应 {"status":"ok"}。这种双端口设计让测试可以同时验证 HTTP(50100)与 HTTPS(50101)两种明文/加密流量的解析。
客户端源码要点:HTTP/1.1 与 HTTP/2 双通道
client/https_client.go 面向 https://127.0.0.1:50101 发起请求,参数如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
max_procs |
1 | Go runtime 创建的 OS 线程上限 |
iters |
1000 | 迭代轮数 |
sub_iters |
1000 | 同一 TLS 配置下的子迭代数 |
http2 |
true | 使用 HTTP/2 而非 HTTP/1.1 |
客户端在每个外层迭代中新建 http.Client 并配置对应 Transport:
if useHTTP2 {
client.Transport = &http2.Transport{TLSClientConfig: tlsConfig}
} else {
client.Transport = &http.Transport{TLSClientConfig: tlsConfig}
}
tlsConfig 设置 InsecureSkipVerify: true 跳过证书校验(测试环境无需真实证书链)。通过 --http2=false 可以切换为 HTTP/1.1 流量,从而在两种协议形态下分别验证 TLS 解析。
运行方式:bazel + docker 两步走
按照 go_https/README.md 的步骤,先构建两个目标:
bazel run //src/stirling/testing/demo_apps/go_https/server:golang_1_23_https_server -- --norun
bazel run //src/stirling/testing/demo_apps/go_https/client:golang_1_23_https_client -- --norun
然后在两个终端分别启动容器(客户端复用服务端的网络命名空间,直接访问其 HTTPS 端口):
docker run --name=go_https_server bazel/src/stirling/testing/demo_apps/go_https/server:golang_1_23_https_server
docker run --name=go_https_client --network=container:go_https_server bazel/src/stirling/testing/demo_apps/go_https/client:golang_1_23_https_client --iters 3 --sub_iters 3
注意目标名中的 golang_1_23 前缀——这是当前 bazel 构建所绑定的 Go 版本。按 server/README.md 的维护约定:新 Go 版本发布后,应把已退出支持的旧版本从 bazel 移除并加入 update_ghcr.sh 脚本(该脚本把各 Go 版本的镜像推送到 ghcr.io),从而在不拖累 Pixie 主构建的前提下持续保有旧版本测试覆盖。
hipster-shop:针对 online-boutique productcatalogservice 的 gRPC 客户端
设计动机:复用业界标准微服务 demo 的协议形态
hipster_shop 的客户端 productcatalogservice_client/client.go 与 GoogleCloudPlatform 的 online-boutique(microservices-demo)中的 productcatalogservice 通信,用于 Stirling 内部的 gRPC 反射(reflection)测试。相关 proto 定义从 microservices-demo 复制而来(见 hipster_shop/proto/demo.proto 头部注释)。
该 proto 文件完整描述了 ProductCatalogService 的三个 RPC:
service ProductCatalogService {
rpc ListProducts(Empty) returns (ListProductsResponse);
rpc GetProduct(GetProductRequest) returns (Product);
rpc SearchProducts(SearchProductsRequest) returns (SearchProductsResponse);
}
客户端源码要点:三次典型调用
客户端通过非加密 gRPC(insecure.NewCredentials())连接 localhost:3550,依次发起三个请求:
client := pb.NewProductCatalogServiceClient(conn)
_, err = client.ListProducts(ctx, &pb.Empty{})
_, err = client.GetProduct(ctx, &pb.GetProductRequest{Id: "OLJCESPC7Z"})
_, err = client.SearchProducts(ctx, &pb.SearchProductsRequest{Query: "typewriter"})
这三个调用分别覆盖列表、按 ID 单查、按关键词搜索三类典型 gRPC 请求形态,为 gRPC 反射测试提供了明确的调用序列。同目录的 reflection.cc 与 reflection_test.cc 展示了该测试程序在 Stirling gRPC 反射测试中的实际使用方式。
运行方式:先起服务端,再跑客户端
按 productcatalogservice_client/README.md 的步骤,先以 docker 启动 online-boutique 的 productcatalogservice(禁用 profiler 与 tracing,映射端口 3550):
docker run -e DISABLE_PROFILER=1 -e DISABLE_TRACING=1 -p 3550:3550 gcr.io/google-samples/microservices-demo/productcatalogservice:v0.2.0
再以 bazel 运行客户端镜像:
bazel run //src/stirling/testing/demo_apps/hipster_shop/productcatalogservice_client:productcatalogservice_client_image
更多测试靶场:go_http、py_grpc 与边界场景应用
除了主 README 明确列出的三个应用,同一目录还沉淀了多个针对特定协议/语言形态的测试程序:
-
go_http(go_http/README.md):与 go_grpc_tls_pl 相同的 skaffold 运行模式:
kubectl create ns px-http-test skaffold run -f src/stirling/testing/demo_apps/go_http/skaffold.yaml其 go_http_client/main.go 的注释说明了设计意图:用 Go 原生 HTTP 客户端模拟"Go 中调用 RESTful 服务的典型设置"——用
curl无法代表典型 Go 客户端的库调用路径。客户端支持--reqType(get/post/mix)、--reqSize(POST 请求体大小,默认 128KB)、--count(请求数,0 表示无限循环)、--sleep(请求间隔毫秒数)等参数;k8s/client_deployment.yaml 中展示了容器化运行示例(--count=0持续打流)。服务端 go_http_server/main.go 监听动态端口,提供/sayhello与/post两个路由,并把实际端口号打印到标准输出供测试框架读取。 -
py_grpc(py_grpc/README.md):用于测试 Python gRPC 应用的 C 层追踪。由于 Python 的 gRPC 模块构建自官方 gRPC C 代码,Pixie 除了实现 BCC C 代码的指针解引用外,还必须定位 Python 进程对应的
.so库文件。受实现限制,该测试应用固定使用 2018 年发布的 gRPC 1.19.0——更新版本的 gRPC 模块被剥离了符号,没有符号 Pixie 就无法找到正确的地址挂载 eBPF 探针。运行方式为两条 docker 命令(客户端复用服务端网络命名空间):docker run --name=server --rm gcr.io/pixie-oss/pixie-dev-public/python_grpc_1_19_0_helloworld:1.0 python helloworld/greeter_server.py docker run --network=container:server --rm gcr.io/pixie-oss/pixie-dev-public/python_grpc_1_19_0_helloworld:1.0 python helloworld/greeter_client.py -
leaky_http_server / leaky_http_unix_socket_server(leaky_http_server/server.cc、leaky_http_unix_socket_server/server.cc):用于覆盖 TCP 与 Unix Socket 两种传输形态的边界场景。
-
node(node/node.cc):提供非 Go 语言运行时形态的测试目标。
另外,src/stirling/demo_apps/README.md 还维护着一组更基础的 demo(cpp_openssl、go_http、python_tls、wrk_sweeper 等),与 testing 目录形成互补:前者偏重各类语言的原始 TLS/HTTP 场景,后者聚焦 Stirling 协议追踪与解析验证。
小结:如何把 Demo Apps 用进你的测试流程
综上,src/stirling/testing/demo_apps/ 是 Pixie Stirling 协议解析测试体系的"流量供给层",其核心设计可归纳为三点:
- 双角色划分:每个应用要么作为 BPF 探测目标(服务端),要么作为刺激源(客户端),让探针附着与协议解析可以被独立验证;
- 覆盖关键协议形态:gRPC over TLS(go_grpc_tls_pl)、HTTPS 与 HTTP/2(go_https)、Go 原生 HTTP 库调用(go_http)、Python gRPC C 层(py_grpc)、行业标准微服务 gRPC 服务(hipster-shop);
- 多种运行入口:需要 K8s 环境时用 skaffold(
kubectl create ns+skaffold run),单机验证时用 bazel run + docker run,且可通过参数(--count、--iters、--reqType、--http2等)精确控制流量节奏与协议形态。
当你需要验证某个协议追踪逻辑时,可以先在 K8s 集群中通过 skaffold 部署对应 demo 制造流量,再观察 Stirling 采集到的数据是否符合预期;当你需要快速本地复现时,则优先使用 bazel + docker 的组合。理解这些 Demo Apps 的参数语义与源码行为,是高效调试与扩展 Stirling 协议解析能力的第一步。