Pixie Stirling 测试客户端解析:基于 online-boutique 的 productcatalogservice gRPC 客户端
Pixie Stirling 测试客户端解析:基于 online-boutique 的 productcatalogservice gRPC 客户端
导读
本文深入解析 Pixie 仓库中位于 src/stirling/testing/demo_apps/hipster_shop/productcatalogservice_client 的 gRPC 测试客户端:它调用 Google Cloud microservices-demo(online-boutique)中的 productcatalogservice,被 Stirling 用作 BPF 探针的流量刺激源与 gRPC/HTTP2 解析验证目标。读完本文,你将掌握该客户端的源码结构、服务契约、独立运行方式,以及它如何被 Stirling 的 socket tracer 端到端测试复用,为自行编写可观测性测试用 gRPC 客户端提供可直接照搬的范本。
目录与定位:Stirling 测试基础设施中的"刺激源"客户端
该客户端位于 src/stirling/testing/demo_apps/hipster_shop/productcatalogservice_client/,目录下仅包含三个文件:
- README.md:使用说明;
- client.go:Go 编写的 gRPC 客户端主程序;
- BUILD.bazel:Bazel 构建与镜像打包规则。
从上层目录的 demo_apps/README.md 可以看出整个 demo apps 体系的定位:这些二进制要么作为 BPF 探测目标(probing targets),要么作为流量刺激源(stimulus)。本客户端属于后者——它主动发起对 productcatalogservice 的 gRPC 调用,制造可被 Stirling socket tracer 捕获的 HTTP/2 流量,用于验证 gRPC 协议解析的正确性。
客户端源码解析:三个 RPC 调用构成最小观测负载
client.go 的核心逻辑非常精简,共分为四步:
1. 建立 gRPC 连接
ctx := context.Background()
addr := "localhost:3550"
conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
- 目标地址硬编码为
localhost:3550,端口 3550 与 online-boutique 中productcatalogservice的监听端口一致(见下文 K8s 清单); - 使用
insecure.NewCredentials()建立**明文(非 TLS)**连接——这正是 Stirling 测试所需要的:BPF 探针能直接读到未加密的 HTTP/2 帧,无需额外的 TLS 解密配置。
2. 依次发起三个 RPC 调用
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"})
三个调用分别对应 ProductCatalogService 的三种典型方法形态:无参数请求(ListProducts)、单字段请求(GetProduct)、带查询字符串的请求(SearchProducts),覆盖了 gRPC 请求体解析的常见场景。测试数据同样精心挑选:OLJCESPC7Z 是 online-boutique 商品目录中的"Vintage Typewriter"商品 ID,typewriter 是能命中该商品的搜索词。
3. 打印终止日志
log.Info("Client terminated")
客户端是无状态的一次性程序:启动后立即发完三个请求便退出,便于测试框架以 Wait() 同步等待执行完成。
服务契约:demo.proto 中的 ProductCatalogService
客户端依赖的 protobuf 定义位于同目录的 demo.proto。文件头部注释明确说明其来源:从 GoogleCloudPlatform/microservices-demo 仓库复制,用于 "prototyping Stirling's gRPC reflection"(为 Stirling 的 gRPC 反射能力做原型验证)。
其中与本文客户端直接相关的服务定义为:
service ProductCatalogService {
rpc ListProducts(Empty) returns (ListProductsResponse);
rpc GetProduct(GetProductRequest) returns (Product);
rpc SearchProducts(SearchProductsRequest) returns (SearchProductsResponse);
}
三个 RPC 的请求/响应消息同样定义在该文件内:
Product:包含id、name、description、picture、price_usd(Money类型:currency_code/units/nanos三段式金额表示)以及可选的categories分类标签;GetProductRequest:单字段id;SearchProductsRequest:单字段query;ListProductsResponse:repeated Product products;SearchProductsResponse:repeated Product results。
此外,该文件还完整保留了 online-boutique 的其余服务契约(CartService、RecommendationService、ShippingService、CurrencyService、PaymentService、EmailService、CheckoutService、AdService 及各自的 message 定义),说明该 proto 是整个 Stirling 测试用 demo 契约的公共底座。编译产物见 demo.pb.go,其 Bazel 规则在 proto/BUILD.bazel 中同时产出 Go 与 C++ 两个语言的 proto 库,分别供客户端(Go)与反射工具(C++)使用。
构建与独立运行:README 中的两条命令
README.md 给出的完整运行流程分为服务端与客户端两步:
第一步:启动 productcatalogservice 服务端(Docker)
docker run -e DISABLE_PROFILER=1 -e DISABLE_TRACING=1 -p 3550:3550 gcr.io/google-samples/microservices-demo/productcatalogservice:v0.2.0
-p 3550:3550:将容器内 3550 端口映射到宿主机,客户端以localhost:3550访问;-e DISABLE_PROFILER=1与-e DISABLE_TRACING=1:关闭服务端的 profiler 与 tracing 上报,避免测试流量被无关依赖干扰;- 镜像
productcatalogservice:v0.2.0是 microservices-demo 的官方发布版本,Pixie 测试亦将其打包为本地 tar 镜像(见下文容器封装章节)。
第二步:以 Bazel 运行客户端
bazel run //src/stirling/testing/demo_apps/hipster_shop/productcatalogservice_client:productcatalogservice_client_image
该 target 由 BUILD.bazel 定义:
go_library(
name = "productcatalogservice_client_lib",
srcs = ["client.go"],
deps = [
"//src/stirling/testing/demo_apps/hipster_shop/proto:demo_pl_go_proto",
"@com_github_sirupsen_logrus//:logrus",
"@org_golang_google_grpc//:grpc",
"@org_golang_google_grpc//credentials/insecure",
],
)
pl_go_binary(name = "productcatalogservice_client", embed = [":productcatalogservice_client_lib"])
pl_go_image(name = "productcatalogservice_client_image", binary = ":productcatalogservice_client", ...)
pl_go_image 是 Pixie 封装的 Go 容器镜像规则(定义见 bazel/go_container.bzl),它把二进制打包成可运行的 OCI 镜像。运行该 target 后,客户端容器启动、向 localhost:3550 依次发出三个 gRPC 请求,随后打印 Client terminated 退出。
运行前提说明:bazel run 需要已配置 Pixie 的 Bazel 工作区(依赖 WORKSPACE 与 workspace.bzl 中声明的规则);客户端容器默认以 bridge 网络访问宿主机端口,若在测试中需要直连服务端容器,可加 --network=container:<server_container> 参数(这正是下方测试所采用的方式)。
端到端验证:客户端如何驱动 Stirling 的 HTTP/2 追踪测试
该客户端并非仅供手工演示——它是 Stirling socket tracer 端到端测试的正式组成部分。在 http2_trace_bpf_test.cc 的 ProductCatalogServiceTraceTest 中可以看到完整闭环:
1. 容器封装
测试通过两个 ContainerRunner 子类拉起服务端与客户端:
- product_catalog_service_container.h:加载镜像
src/stirling/source_connectors/socket_tracer/testing/containers/productcatalogservice_v0_2_0.tar,容器名前缀pcs,就绪信号为日志输出starting grpc server; - product_catalog_client_container.h:加载镜像
src/stirling/testing/demo_apps/hipster_shop/productcatalogservice_client/productcatalogservice_client_image.tar,容器名前缀pcc。
2. 网络联通与流量捕获
PX_CHECK_OK(this->client_.Run(
std::chrono::seconds{10},
{absl::Substitute("--network=container:$0", this->server_.container_name())}));
this->client_.Wait();
客户端以 --network=container:<server> 加入服务端容器的网络命名空间,使其与 localhost:3550 的硬编码地址天然匹配;Run 指定 10 秒超时,Wait 阻塞等待客户端完成三次 RPC 调用。
3. 断言验证解析结果
测试从 Stirling 的 SocketTraceConnector::kHTTPTableNum 表中拉取记录,按服务端 PID 过滤后断言恰好捕获 3 条 HTTP 记录,且请求路径分别为:
/hipstershop.ProductCatalogService/ListProducts
/hipstershop.ProductCatalogService/GetProduct
/hipstershop.ProductCatalogService/SearchProducts
并进一步校验请求体与响应体的字节级内容。例如 GetProduct 的请求体应为 1: "OLJCESPC7Z",响应体应为完整的 "Vintage Typewriter" 商品信息(含 USD、units=67、nanos=990000000 的价格字段);测试还通过 req_body_sizes/resp_body_sizes 断言(期望 5, 17, 17 与 147, 150, 1439)验证了 Stirling 对 protobuf 消息的截断策略。这些断言说明该客户端产生的流量是 Stirling gRPC 解析正确性的黄金标准数据。
与 gRPC 反射原型的配合
在 hipster_shop 目录(BUILD.bazel)中还包含一组 C++ 反射工具:reflection.cc 通过 hipstershop::CartItem::descriptor()->file()->CopyTo(...) 从任意消息描述符构造 FileDescriptorSet,用于原型化 Stirling 的 gRPC reflection 解析;reflection_test.cc 验证生成的描述符集合包含正确的 syntax、name 与 package 信息。这意味着 demo.proto 一份契约同时服务两个 Stirling 功能验证:客户端产生真实流量验证 HTTP/2 解析,反射工具验证描述符读取。
在 K8s 中的对应部署:online-boutique 演示清单
若要在 Kubernetes 中运行完整 online-boutique 环境,可参考 demos/online-boutique/online-boutique.yaml(其头部注释标明自 microservices-demo 的 kubernetes-manifests.yaml 复制)。其中 productcatalogservice 的 Deployment/Service 定义与本文客户端高度呼应:
- 容器端口
3550,环境变量PORT=3550; - 默认关闭
DISABLE_STATS、DISABLE_TRACING、DISABLE_PROFILER(与 README 中 docker run 的参数同源); - 就绪/存活探针使用
grpc_health_probe -addr=:3550; - Service 为
ClusterIP类型,暴露grpc端口 3550。
这也解释了为什么客户端地址硬编码为 3550:从 Docker 单机运行到 K8s 集群,productcatalogservice 的端口约定始终一致,测试与演示代码得以复用同一契约。
小结
productcatalogservice_client 是理解 Pixie Stirling 测试方法论的绝佳切片:它用不足 60 行的 Go 代码,配合一份复刻自 online-boutique 的 proto 契约,同时支撑了手工演示(README 两步运行)、端到端 BPF 验证(http2_trace_bpf_test)与gRPC 反射原型(hipster_shop 反射工具)三类场景。对希望为可观测性项目编写协议解析测试的开发者而言,这套"最小客户端 + 固定端口 + 确定请求序列 + 字节级断言"的组合模式可以直接借鉴。
延伸阅读:demo_apps/README.md(demo apps 目录总览)、online-boutique/README.md(演示应用说明)、demos/online-boutique/online-boutique.yaml(完整 K8s 清单)。