news 2026/10/10 11:27:03

Moby 仓库中的 go-grpc-prometheus v1.2.0:从 CHANGELOG 读懂 gRPC 服务的 Prometheus 可观测性

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Moby 仓库中的 go-grpc-prometheus v1.2.0:从 CHANGELOG 读懂 gRPC 服务的 Prometheus 可观测性
  • 云原生
  • 容器运行时
  • 虚拟化
  • 容器编排

【免费下载链接】moby

The Moby Project - a collaborative project for the container ecosystem to assemble container-based systems

项目地址:https://gitcode.com/GitHub_Trending/mo/moby
点击查看免费下载

本指南以 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创建了四个核心计数器:

字段指标名标签
serverStartedCountergrpc_server_started_totalgrpc_type、grpc_service、grpc_method
serverHandledCountergrpc_server_handled_total上述三个 +grpc_code
serverStreamMsgReceivedgrpc_server_msg_received_totalgrpc_type、grpc_service、grpc_method
serverStreamMsgSentgrpc_server_msg_sent_totalgrpc_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 展示了完整计数链路:

  1. newServerReporter创建 reporter 时立刻自增grpc_server_started_total{grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"};
  2. 用户逻辑每收到一条消息,ReceivedMessage()自增grpc_server_msg_received_total;
  3. 每向客户端发送一条消息,SentMessage()自增grpc_server_msg_sent_total;
  4. 调用结束时,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

项目地址:https://gitcode.com/GitHub_Trending/mo/moby
点击查看免费下载

相关推荐

上一篇:compose-multiplatform多语言实战:从配置到切换全攻略
下一篇:Aria2 命令行下载工具详解:从基础到高级配置

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 11:25:16

CleanCode AI编程标准代码生成器:从源头治理技术债的工程实践

1. 为什么“生成即规范”是个值得死磕的方向写代码这件事&#xff0c;很多人有个误区&#xff1a;觉得功能跑通了就万事大吉。但真正在项目里摸爬滚打过几年的人都知道&#xff0c;代码写出来只是开始&#xff0c;后面还有无数次的修改、调试、交接、扩展。一个功能今天能跑&am…

作者头像 李华
网站建设 2026/10/10 11:18:50

KDD Cup入侵检测三模型实战:贝叶斯+BP神经网络+KNN

简介&#xff1a;本资源是一套基于Python实现的入侵检测系统实战项目&#xff0c;面向网络安全初学者、机器学习入门者及高校相关课程实践者&#xff0c;聚焦于贝叶斯分类器、神经网络&#xff08;BP&#xff09;与K近邻&#xff08;KNN&#xff09;三大算法在IDS中的建模、训练…

作者头像 李华
网站建设 2026/10/10 11:18:17

基于STM32L073RZ与PCA9422的低功耗电源管理方案设计与实测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/10 11:18:16

PCA9422与STM32F732IE电源管理方案:从硬件设计到软件调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/10 11:16:25

电缆表皮腐蚀检测数据集与YOLOv8实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华