作者 | 朱瑜坚 腾讯云后台开发工程师
Prometheus 是一个开源的监控解决方案,部署简单易使用,难点在于如何设计符合特定需求的 Metrics 去全面高效地反映系统实时状态,以助力故障问题的发现与定位。本文即基于最佳实践的 Metrics 设计方法,结合具体的场景实例——TKE 的网络组件 IPAMD 的内部监控,以个人实践经验谈一谈如何设计和实现适合的、能够更好反映系统实时状态的监控指标(Metrics)。该篇内容适于 Prometheus 或相关监控系统的初学者(可无任何基础了解),以及近期有 Prometheus 监控方案搭建和维护需求的系统开发管理者。通过这篇文章,可以加深对 Prometheus Metrics 的理解,并能针对实际的监控场景提出更好的指标(Metrics)设计。
目录
1 引言
Prometheus 是一个开源的监控解决方案,它能够提供监控指标数据的采集、存储、查询以及监控告警等功能。作为云原生基金会(CNCF)的毕业项目,Prometheus 已经在云原生领域得到了大范围的应用,并逐渐成为了业界最流行的监控解决方案之一。
Prometheus 的部署和使用可以说是简单易上手,但是如何针对实际的问题和需求设计适宜的 Metrics 却并不是那么直接可行,反而需要优先解决暴露出来的诸多不确定问题,比如何时选用 Vector,如何设计适宜的 buckets,Summary 和 Histogram 指标类型的取舍等。然而,要想有效助力故障及问题的发现与定位,必须要有一个合理有效的 Metrics 去全面高效地反映系统实时状态。
本文将介绍基于最佳实践的 Metrics 设计方法,并结合具体的场景实例——TKE 的网络组件 IPAMD 的内部监控,以个人实践经验谈一谈如何设计和实现适合的、能够更好反映系统实时状态的监控指标(Metrics)。
本文之后的第 2 节将对 Prometheus 的 Metrics 做简单的介绍,对此已有了解的读者可跳过。之后第 3 节将介绍 Metrics 设计的最佳实践。第 4 节将结合具体的实例应用相关设计方法。第 5 节将介绍 Golang 上指标收集的实现方案。
2 Prometheus Metrics Type 简介
Prometheus Metrics 是整个监控系统的核心,所有的监控指标数据都由其记录。Prometheus 中,所有 Metrics 皆为时序数据,并以名字作区分,即每个指标收集到的样本数据包含至少三个维度的信息:名字、时刻和数值。
而 Prometheus Metrics 有四种基本的 type:
此外,Prometheus Metrics 中有一种将样本数据以标签(Label)为维度作切分的数据类型,称为向量(Vector)。四种基本类型也都有其 Vector 类型:
Vector 相当于一组同名同类型的 Metrics,以 Label 做区分。Label 可以有多个,Prometheus 实际会为每个 Label 组合创建一个 Metric。Vector 类型记录数据时需先打 Label 才能调用 Metrics 的方法记录数据。
如对于 HTTP 请求延迟这一指标,由于 HTTP 请求可在多个地域的服务器处理,且具有不同的方法,于是,可定义名为 http_request_latency_seconds
的 SummaryVec,标签有region
和method
,以此表示不同地域服务器的不同请求方法的请求延迟。
以下将对每个类型做详细的介绍。
>>>>
2.1 Counter
方法:
type Counter interface {
Metric
Collector
// 自增1
Inc()
// 把给定值加入到计数器中. 若值小于 0 会 panic
Add(float64)
}
>>>>
2.2 Gauge
方法:
type Gauge interface {
Metric
Collector
Set(float64) // 直接设置成给定值
Inc() // 自增1
Dec() // 自减1
Add(float64) // 增加给定值,可为负
Sub(float64) // 减少给定值,可为负
// SetToCurrentTime 将 Gauge 设置成当前的 Unix 时间戳
SetToCurrentTime()
}
>>>>
2.3 Histogram
方法:
type Histogram interface {
Metric
Collector
// Observe 将一个观测到的样本数据加入 Histogram 中,并更新相关信息
Observe(float64)
}
>>>>
2.4 Summary
方法:
type Summary interface {
Metric
Collector
// Observe 将一个观测到的样本数据加入 Summary 中,并更新相关信息
Observe(float64)
}
实际分位数值可根据需求制定,且是对每一个 Label 组合做聚合。
>>>>
2.5 Histogram 和 Summary 简单对比
可以看出,Histogram 和 Summary 类型测量的对象是比较接近的,但根据其实现方式和其本身的特点,在性能耗费、适用场景等方面具有一定差别,本文总结如下:
类型 | Histogram | Summary |
---|---|---|
客户端性能耗费 | 较低,只需增加counter | 较高,需聚合计算百分位数 |
服务端性能耗费 | 较高,需要聚合计算 | 较低,无需再聚合计算 |
时间序列数据 | 每个bucket一个 | 每个百分位数一个 |
百分位数计算误差 | 依赖于桶区间粒度和数据分布,受限于桶的数量 | 受限于百分位数值本身 |
聚合 | 查询时可以灵活聚合数据 | 查询时不建议做聚合,百分位数无法做聚合,只能做均值和加和的聚合 |
数据的时间范围 | 可在查询时灵活定制 | 活动窗口内,窗口大小在声明 Metrics 后不可更改,即查询时也不可更改 |
适用场景 | 客户端监控,组件在系统中较多,不太关心精确的百分位数值 | 服务端监控,组件在系统中唯一或只有个位数,需要知道较准确的百分位数值(如性能优化场景) |
3 Metrics 设计的最佳实践
>>>>
3.1 如何确定需要测量的对象
在具体设计 Metrics 之前,首先需要明确需要测量的对象。需要测量的对象应该依据具体的问题背景、需求和需监控的系统本身来确定。
Google 针对大量分布式监控的经验总结出四个监控的黄金指标,这四个指标对于一般性的监控测量对象都具有较好的参考意义。这四个指标分别为:
而笔者认为,以上四种指标,其实是为了满足四个监控需求:
除了以上常规需求,还可根据具体的问题场景,为了排除和发现以前出现过或可能出现的问题,确定相应的测量对象。比如,系统需要经常调用的一个库的接口可能耗时较长,或偶有失败,可制定 Metrics 以测量这个接口的时延和失败数。
另一方面,为了满足相应的需求,不同系统需要观测的测量对象也是不同的。在 官方文档 的最佳实践中,将需要监控的应用分为了三类:
对于每一类应用其通常情况下测量的对象是不太一样的。其总结如下:
除了系统本身,有时还需监控子系统:
最后的测量对象的确定应结合以上两点思路确定。
>>>>
3.2 如何选用 Vector
选用 Vec 的原则:
例子:
此外,官方文档 中建议,对于一个资源对象的不同操作,如 Read/Write、Send/Receive, 应采用不同的 Metric 去记录,而不要放在一个 Metric 里。原因是监控时一般不会对这两者做聚合,而是分别去观测。
不过对于 request 的测量,通常是以 Label 做区分不同的 action。
>>>>
3.1 如何确定 Label
根据3.2,常见 Label 的选择有:
确定 Label 的一个重要原则是:同一维度 Label 的数据是可平均和可加和的,也即单位要统一。如风扇的风速和电压就不能放在一个 Label 里。
此外,不建议下列做法:
my_metric{label=a} 1
my_metric{label=b} 6
my_metric{label=total} 7
即在 Label 中同时统计了分和总的数据,建议采用 PromQL 在服务器端聚合得到总和的结果。或者用另外的 Metric 去测量总的数据。
>>>>
3.4 如何命名 Metrics 和 Label
好的命名能够见名知义,因此命名也是良好设计的一环。
Metric 的命名:
Label 的命名:
>>>>
3.5 如何设计适宜的 Buckets
根据前述 histogram 的统计原理可知,适宜的 buckets 能使 histogram 的百分位数计算更加准确。
理想情况下,桶会使得数据分布呈阶梯状,即各桶区间内数据个数大致相同。如图1所示,是本人在实际场景下配置的buckets 数据直方图,y 轴为 buckets 内的数据个数,x 轴是各 buckets,可以看出其近似成阶梯状。这种情况下,当前桶个数下对数据的分辨率最大,各百分位数计算的准确率较高。
图1 较为理想的桶数据分布
而根据笔者实践经验,为了达成以上目标,buckets 的设计可遵从如下经验:
4 实例:TKE-ENI-IPAMD Metrics 设计与规划
>>>>
4.1 组件简介
该组件用于支持腾讯云 TKE 的策略路由网络方案。在这一网络方案中,每个 pod 的 IP 都是 VPC 子网的一个IP,且绑定到了所在节点的弹性网卡上,通过策略路由连通网络,并且使得容器可以支持腾讯云的 VPC 的所有特性。
其中,在 2.0.0 版本以前,tke-eni-ipamd 组件是一个 IP 分配管理的 GRPC Server,其主要职责为:
其工作原理和流程如图 2 所示:
图2 tke-eni-ipamd(v2.0.0-) 工作原理和流程
>>>>
4.2 IPAMD 的使用场景和我们的要求
背景:
需求:
我们的场景:
>>>>
4.3 总体设计
因此,需要以下几类 Metric:
>>>>
4.4 Histogram vs. Summary
时延可选择 Histogram 或 Summary 进行测量,如何选择?
基于 2.5 节的两者对比,有如下分析:
Summary:
Histogram:
Summary 的缺点过于致命,难以回避。Histogram 的缺点可以通过增加工作量(即通过测试环境中的实验来确定各 Metrics 的大致分布)和增加 Metrics(不用 Label 区分)来较好解决。
所以倾向于使用 Histogram。
>>>>
4.5 Metrics 规划示例
详细的 Metrics 规划内容较多,这里选取了一些代表性的样例,列举如下:
Metrics Name | Metrics描述 | Metrics Type | Metrics Label | Metrics Buckets |
---|---|---|---|---|
ipamd_request_latency_seconds | 分配/释放 pod ip 请求的总时延 | HistogramVec | ["action"] (value for action:["add","del"]) | exponential_bucket(1,1.25,15)(s) |
ipamd_vpc_assign_ip_latency_seconds | 向vpc或subnet申请绑定 ip 时延 | Histogram | -- | exponential_bucket(1,1.25,15)(s) |
ipamd_current_requests_count | 请求分配ip的并发请求数 | GaugeVec | ["action"] (value for action:["add","del"]) | -- |
ipamd_vpc_eni_alloc_specific_ip_latency_seconds | vpc_eni 接口处理的时延 | Histogram | -- | exponential_bucket(1,1.25,15)(s) |
ipamd_vpc_eni_alloc_specific_ip_total | vpc_eni 接口总调用数 | Counter | -- | -- |
ipamd_vpc_eni_alloc_specific_ip_errors_total | vpc_eni 接口失败调用数 | Counter | -- | -- |
ipamd_sync_latency_seconds | ipamd 的 controller sync 时延 | HistogramVec | ["resource"]: (value for resource: ["node","statefulset,"statefulsetplus"]) | DefBuckets |
ipamd_invalid_resources_count | ipamd 非法资源(脏数据)计数 | GaugeVec | ["type"] (value for type:["eni","uip","eni-ip"]) | -- |
注1:DefBuckets指默认桶 ({.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10})。
注2:以上 buckets 持续微调中。
5 指标收集的 Golang 实现方案
>>>>
5.1 总体实现思路
>>>>
5.2 Metrics 收集方案
方案1:非侵入式装饰器模式
样例: kubelet/kuberuntime/instrumented_services.go
type instrumentedRuntimeService struct {
service internalapi.RuntimeService
}
func recordOperation(operation string, start time.Time) {
metrics.RuntimeOperations.WithLabelValues(operation).Inc()
metrics.DeprecatedRuntimeOperations.WithLabelValues(operation).Inc()
metrics.RuntimeOperationsDuration.WithLabelValues(operation).Observe(metrics.SinceInSeconds(start))
metrics.DeprecatedRuntimeOperationsLatency.WithLabelValues(operation).Observe(metrics.SinceInMicroseconds(start))
}
func (in instrumentedRuntimeService) Status() (*runtimeapi.RuntimeStatus, error) {
const operation = "status"
defer recordOperation(operation, time.Now())
out, err := in.service.Status()
recordError(operation, err)
return out, err
}
优点:
缺点:
方案2:defer 函数收集
样例:
func test() (retErr error){
defer func(){
metrics.LatencySeconds.Observe(...)
}()
...
func body
...
}
优点:
缺点:
>>>>
5.3 目前 IPAMD 的指标收集实现方案
6 总结
本文介绍了 Prometheus Metrics 及最佳实践的 Metrics 设计和收集实现方法,并在具体的监控场景—— TKE 的网络组件 IPAMD 的内部监控中应用了相关方法。
具体而言,本文基于最佳实践,回答了 Prometheus Metrics 设计过程中的若干问题:
此外,Metrics 设计并不是一蹴而就的,需依据具体的需求的变化进行反复迭代。比如需新增 Metrics 去发现定位可能出现的新问题和故障,再比如 Buckets 的设计也需要变化来适应测量数据分布发生的变化,从而获得更精确的百分位数测量值。
参考资料
Prometheus 官方文档 :https://prometheus.io/docs/introduction/overview/
Prometheus Go client library:https://github.com/prometheus/client_golang