- 云原生
- 容器运行时
- 虚拟化
- 容器编排
【免费下载链接】moby
The Moby Project - a collaborative project for the container ecosystem to assemble container-based systems
本指南以 Moby 仓库 vendored 目录中的 go-grpc-prometheus CHANGELOG.md 为骨架,逐条拆解 v1.2.0 版本的变更内容,并结合同目录下的 README.md 与完整 Go 源码,深入讲解 gRPC 服务端/客户端如何接入 Prometheus 指标采集。读完本文,你将掌握 go-grpc-prometheus 的拦截器接入方式、指标命名与标签体系、直方图开启方法,以及 v1.2.0 中"指标对象即 Collector""支持非默认 Registry""CounterOpts 可配置"三项新增能力的底层实现原理。
背景:这是什么,它为什么出现在 Moby 中
go-grpc-prometheus 是 gRPC Go 生态中用于 Prometheus 监控的拦截器库,提供服务端(grpc_server_*)与客户端(grpc_client_*)两套镜像语义的指标。Moby(Docker)在构建 daemon 时通过 gRPC 与 containerd、buildkit 等组件通信,因此在 go.mod 中以**间接依赖(// indirect)**方式引入了github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0,并将其源码完整 vendor 到仓库中,版本记录同样体现在 vendor/modules.txt。这意味着:Moby 仓库当前锁定的正是本文讨论的 v1.2.0 版本,分析该版本的 CHANGELOG 与源码,就是分析 Moby 实际运行时依赖的行为。
该库遵循 Keep a Changelog 格式与语义化版本规范,CHANGELOG 自 v1.2.0 起开始记录,更早版本请参考各 GitHub Releases。
v1.2.0(2018-06-04)变更总览
根据 CHANGELOG,v1.2.0 是一次兼具"能力增强"与"弃用清理"的版本,完整变更如下:
Added(新增)
- 提供指标对象作为
prometheus.Collector,支持常规的指标注册方式(prometheus.MustRegister(...)等); - 支持非默认/全局的 Prometheus registry(自定义 Registry 实例);
- 允许使用
prometheus.CounterOpts配置计数器(如添加 ConstLabels)。
Changed(变更)
- 移除对已弃用的
grpc.Code()的使用; - 移除对已弃用的
grpc.Errorf的使用,改为status.Errorf。
下面逐一结合 vendored 源码展开。
新增能力一:指标对象即 prometheus.Collector
v1.2.0 之前,指标只能通过包级默认实例隐式注册;v1.2.0 起,ServerMetrics与ClientMetrics都实现了prometheus.Collector接口,允许使用者用标准的 Collector 注册流程把指标挂到任意 Registry 上。
以服务端为例,server_metrics.go 中:
// Describe 向通道发送所有可能指标描述符的超集 func (m *ServerMetrics) Describe(ch chan<- *prom.Desc) { m.serverStartedCounter.Describe(ch) m.serverHandledCounter.Describe(ch) m.serverStreamMsgReceived.Describe(ch) m.serverStreamMsgSent.Describe(ch) if m.serverHandledHistogramEnabled { m.serverHandledHistogram.Describe(ch) } } // Collect 由 Prometheus registry 在采集时调用 func (m *ServerMetrics) Collect(ch chan<- prom.Metric) { m.serverStartedCounter.Collect(ch) m.serverHandledCounter.Collect(ch) m.serverStreamMsgReceived.Collect(ch) m.serverStreamMsgSent.Collect(ch) if m.serverHandledHistogramEnabled { m.serverHandledHistogram.Collect(ch) } }ClientMetrics在 client_metrics.go 中实现了完全对称的Describe/Collect。这套接口意味着你可以这样注册指标:
prometheus.MustRegister(serverMetrics) // serverMetrics 为 *ServerMetrics新增能力二:支持非默认/全局 Prometheus registry
与此配套,v1.2.0 提供了显式构造指标实例的入口:NewServerMetrics与NewClientMetrics。它们不依赖包级默认实例与init()隐式注册,因此可以配合自定义 registry 精确控制指标归属。
// 自定义 registry,而非使用 prometheus.DefaultRegisterer reg := prometheus.NewRegistry() serverMetrics := grpc_prometheus.NewServerMetrics() reg.MustRegister(serverMetrics)从 server_metrics.go 的构造逻辑可以看到,它内部用prom.NewCounterVec创建了四个核心计数器:
| 字段 | 指标名 | 标签 |
|---|---|---|
serverStartedCounter | grpc_server_started_total | grpc_type、grpc_service、grpc_method |
serverHandledCounter | grpc_server_handled_total | 上述三个 +grpc_code |
serverStreamMsgReceived | grpc_server_msg_received_total | grpc_type、grpc_service、grpc_method |
serverStreamMsgSent | grpc_server_msg_sent_total | grpc_type、grpc_service、grpc_method |
同时保留了面向默认 registry 的包级便捷变量: server.go 中的DefaultServerMetrics、UnaryServerInterceptor、StreamServerInterceptor以及init()中的prom.MustRegister(...),client.go 中的DefaultClientMetrics同理。选择"默认变量"还是"自建实例",取决于你是否需要控制 Registry。
新增能力三:用 prometheus.CounterOpts 配置计数器
v1.2.0 为计数器引入CounterOption机制。在 metric_options.go 中:
// A CounterOption lets you add options to Counter metrics using With* funcs. type CounterOption func(*prom.CounterOpts) type counterOptions []CounterOption func (co counterOptions) apply(o prom.CounterOpts) prom.CounterOpts { for _, f := range co { f(&o) } return o } // WithConstLabels allows you to add ConstLabels to Counter metrics. func WithConstLabels(labels prom.Labels) CounterOption { return func(o *prom.CounterOpts) { o.ConstLabels = labels } }NewServerMetrics与NewClientMetrics均接收可变长CounterOption(见 server_metrics.go 与 client_metrics.go),构造计数器时通过opts.apply(prom.CounterOpts{...})把用户配置合并进去。典型用法是为指标附加环境维度:
serverMetrics := grpc_prometheus.NewServerMetrics( grpc_prometheus.WithConstLabels(prometheus.Labels{ "component": "moby-daemon", }), )直方图也有同构的配置能力:WithHistogramBuckets(自定义桶边界)与WithHistogramConstLabels(自定义 ConstLabels),见 metric_options.go。
变更内容:清理弃用 API
v1.2.0 的 "Changed" 部分聚焦于 gRPC Go 库的 API 演进:
- 移除
grpc.Code():旧式从 error 中提取状态码的写法被淘汰,统一改为从错误构造status对象。当前 vendored 源码中,服务端拦截器在 server_metrics.go 与 server_reporter.go 中通过status.FromError(err)+st.Code()获取状态码,已无任何grpc.Code()调用。 grpc.Errorf→status.Errorf:错误构造迁移到google.golang.org/grpc/status包。这也解释了为什么该库所有文件统一 import"google.golang.org/grpc/status"与"google.golang.org/grpc/codes"(例如 util.go)。
这一清理使该库与 gRPC Go 的新状态码模型完全对齐,也让grpc_code标签的取值来自稳定的codes.Code.String(),保证指标语义不被弃用 API 影响。
指标语义与标签体系
v1.2.0 的指标语义在 vendored 源码中可直接验证,核心标签定义在 util.go:
grpc_type:RPC 类型,取值为unary、client_stream、server_stream、bidi_stream四种(由typeFromMethodInfo根据流的单向/双向属性推断,见 util.go);grpc_service:protobufpackage与 service 名的组合,如mwitkow.testproto.TestService;grpc_method:被调用的方法名;grpc_code(仅完成态指标):gRPC 状态码,如OK、InvalidArgument、Internal等。allCodes枚举了全部 17 个状态码(见 util.go)。
splitMethodName(util.go)负责把/package.Service/Method形式的完整方法名拆成 service 与 method 两个标签。
一次完整 RPC 的指标生命周期
以服务端收到一个server_stream请求为例,server_reporter.go 展示了完整计数链路:
newServerReporter创建 reporter 时立刻自增grpc_server_started_total{grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"};- 用户逻辑每收到一条消息,
ReceivedMessage()自增grpc_server_msg_received_total; - 每向客户端发送一条消息,
SentMessage()自增grpc_server_msg_sent_total; - 调用结束时,
Handled(code)按最终状态码自增grpc_server_handled_total{grpc_code="OK",...}。
流式场景下,monitoredServerStream 包装了grpc.ServerStream,在每次SendMsg/RecvMsg成功后回调 reporter;客户端侧的 monitoredClientStream 行为对称,并在收到io.EOF时以codes.OK结束计数。
延迟直方图(默认关闭)
由于高基数指标对 Prometheus 存储与查询成本较高,延迟直方图默认关闭,需显式开启:
grpc_prometheus.EnableHandlingTimeHistogram() // 服务端,作用于 DefaultServerMetrics grpc_prometheus.EnableClientHandlingTimeHistogram() // 客户端开启后产生grpc_server_handling_seconds(客户端为grpc_client_handling_seconds)三件套:_count(完成次数)、_sum(累计耗时,可用于计算平均处理时间)、_bucket(各延迟桶计数,可估算 SLA)。桶边界默认使用prom.DefBuckets,可通过WithHistogramBuckets自定义(见 server_metrics.go 与 metric_options.go)。开启时 reporter 会在调用开始时启动计时器,结束时Observe(time.Since(r.startTime).Seconds())(server_reporter.go)。
预注册技巧:InitializeMetrics
InitializeMetrics 会遍历server.GetServiceInfo()中所有已注册服务与方法,通过preRegisterMethod(server_metrics.go)预先为每个方法、每个状态码创建零值指标序列——注意它只GetMetricWithLabelValues引用而不自增。这样 Prometheus 中永远不会出现"某个方法从未被调用导致指标缺失"的情况。对应包级便捷函数为grpc_prometheus.Register(myServer),需在所有服务注册完成后调用(server.go)。
拦截器接入方式(服务端与客户端)
v1.2.0 沿用拦截器设计,服务端在创建grpc.Server时挂载(见 README.md):
myServer := grpc.NewServer( grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor), grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor), ) // 注册全部 gRPC 服务实现后,预初始化所有指标 grpc_prometheus.Register(myServer) // 暴露 Prometheus 采集端点 http.Handle("/metrics", promhttp.Handler())客户端在grpc.Dial时挂载:
clientConn, err := grpc.Dial( address, grpc.WithUnaryInterceptor(grpc_prometheus.UnaryClientInterceptor), grpc.WithStreamInterceptor(grpc_prometheus.StreamClientInterceptor), )拦截器的实现本质是包装 handler/invoker:服务端一元拦截器在 server_metrics.go 中先计数"开始"与"收到请求",调用 handler 后用status.FromError(err)提取状态码并记录"完成";客户端一元拦截器在 client_metrics.go 中先计数"发送",再在 invoker 返回后计数"完成"。这类按调用链包装的模式与 Moby 中 daemon 侧 gRPC 拦截器的使用方式(参见 daemon/command/httphandler.go 的grpc.ChainUnaryInterceptor)一脉相承。
在 Moby 中的落地与版本事实
- 当前 Moby 仓库锁定的版本为v1.2.0,声明于 go.mod,并以 vendor 方式固化源码于
vendor/github.com/grpc-ecosystem/go-grpc-prometheus/,校验记录见 vendor/modules.txt; - 该依赖在 Moby 中属于传递性(间接)依赖,即并非 Moby 源码直接调用,而是由 Moby 依赖链上的其他组件(如 containerd 相关模块)引入;
- 该库采用 Apache 2.0 许可,详见 LICENSE。
常用 PromQL 查询示例
基于上述指标命名,可在 Prometheus 中直接构建运维大盘(详见 README.md):
# 按服务聚合的请求入站速率(1 分钟窗口) sum(rate(grpc_server_started_total{job="foo"}[1m])) by (grpc_service) # 一元 RPC 错误率(非 OK 状态码) sum(rate(grpc_server_handled_total{job="foo",grpc_type="unary",grpc_code!="OK"}[1m])) by (grpc_service) # 一元 RPC 99% 分位延迟 histogram_quantile(0.99, sum(rate(grpc_server_handling_seconds_bucket{job="foo",grpc_type="unary"}[5m])) by (grpc_service,le) ) # 慢请求占比(处理时间 > 250ms) 100.0 - ( sum(rate(grpc_server_handling_seconds_bucket{job="foo",grpc_type="unary",le="0.25"}[5m])) by (grpc_service) / sum(rate(grpc_server_handling_seconds_count{job="foo",grpc_type="unary"}[5m])) by (grpc_service) ) * 100.0小结
go-grpc-prometheus v1.2.0 通过"指标对象即 Collector""支持自定义 Registry""CounterOpts 可配置"三项能力,把指标注册从包级默认实例的约束中解放出来;同时通过清理grpc.Code()与grpc.Errorf两个弃用 API,完成了向status包状态码模型的迁移。Moby 仓库 vendored 的正是这一版本,其拦截器模式、标签体系与延迟直方图设计至今仍是 gRPC 服务可观测性接入的参考范式。若需在自定义的 gRPC 服务中复刻这套监控,可直接对照 server_metrics.go 与 client_metrics.go 的公开 API 实现。
- 云原生
- 容器运行时
- 虚拟化
- 容器编排
【免费下载链接】moby
The Moby Project - a collaborative project for the container ecosystem to assemble container-based systems
相关推荐
Moby 仓库中的 go-grpc-prometheus:用 Prometheus 监控 gRPC 服务端与客户端拦截器全指南
Moby 仓库中的 go grpc prometheus:用 Prometheus 监控 gRPC 服务端与客户端拦截器全指南 导读 本文围绕 Moby 仓库中
云原生容器运行时虚拟化容器编排深入解析 go-grpc-prometheus v1.2.0:Cilium 中 gRPC 服务的 Prometheus 监控拦截器
深入解析 go grpc prometheus v1.2.0:Cilium 中 gRPC 服务的 Prometheus 监控拦截器 导读 本篇文章聚焦于 Cil
云原生网络服务网格可观测性网络安全eBPFGo gRPC Middleware与Prometheus集成:构建可观测性微服务的终极指南
Go gRPC Middleware与Prometheus集成:构建可观测性微服务的终极指南 在当今的微服务架构中, 可观测性 已成为确保系统稳定运行的关键要素
后端微服务可观测性
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考