高可用prometheus:常见问题
监控系统的历史悠久,是一个很成熟的方向,而Prometheus作为新生代的开源监控系统,慢慢成为了云原生体系的事实标准,也证明了其设计很受欢迎。本文主要分享在prometheus实践中遇到的一些问题和思考
几点原则
监控是基础设施,目的是为了解决问题,不要只朝着大而全去做,尤其是不必要的指标采集,浪费人力和存储资源(To B商业产品例外)
需要处理的告警才发出来,发出来的告警必须得到处理
简单的架构就是最好的架构,业务系统都挂了,监控也不能挂,Google SRE里面也说避免使用magic系统,例如机器学习报警阈值、自动修复之类。这一点见仁见智吧,感觉很多公司都在搞智能AI运维
prometheus 的局限
prometheus是基于metric的监控,不适用于日志(logs)、事件(event)、调用链(tracing)
prometheus默认是pull模型,合理规划你的网络,尽量不用pushgateway转发
对于集群化、水平扩展,官方和社区都没有银弹,合理选择 federate、cortex、thanos
监控系统一般 可用性>一致性,这个后面说 thanos 的时候会提到
合理选择黄金指标
我们应该关注哪些指标?Google在“SRE Handbook”中提出了“四个黄金信号”:延迟、流量、错误数、饱和度。实际操作中可以使用USE或RED 方法作为指导,USE用于资源,RED用于服务
USE 方法:Utilization、Saturation、Errors
RED 方法:Rate、Errors、Duration
对USE和RED的阐述可以参考容器监控实践—K8S常用指标分析这篇文章
采集组件all in one
prometheus 体系中 exporter 都是独立的,每个组件各司其职,如机器资源用 node-exporter,gpu 有NVIDIA exporter等等,但是 exporter 越多,运维压力越大,尤其是对 agent做资源控制、版本升级。我们尝试对一些exporter进行组合,方案有二:
通过主进程拉起n个exporter 进程,仍然可以跟着社区版本更新
用telegraf来支持各种类型的 input,n 合 1
另外,node-exporter 不支持进程监控,可以加一个process-exporter,也可以用上边提到的telegraf。
k8s 1.16中cadvisor的指标兼容问题
在 k8s 1.16版本,cadvisor的指标去掉了pod_name 和 container_name的label,替换为了pod 和 container。如果你之前用这两个 label 做查询或者 grafana 绘图,得更改下 sql 了。因为我们一直支持多个 k8s 版本,就通过 relabel配置继续保留了原来的**_name
注意要用metric_relabel_configs,不是relabel_configs,采集后做的replace。
prometheus集群内与集群外部署
prometheus 如果部署在k8s集群内采集是很方便的,用官方给的yaml就可以,但我们因为权限和网络需要部署在集群外,二进制运行,专门划了几台高配服务器运行监控组件。
以 pod 方式运行在集群内是不需要证书的(in-cluster 模式),但集群外需要声明 token之类的证书,并替换address。例如:
上面是通过默认配置中通过apiserver proxy 到 let,如果网络能通,其实也可以直接把kubelet的10255作为 target,规模大的时候还减轻了 apiserver 的压力,不过这种方式就要写服务发现来更新 node列表了。
gpu 指标的获取
nvidia-smi可以查看机器上的gpu资源,而cadvisor 其实暴露了metric来表示 容器使用 gpu 情况,
如果要更详细的gpu 数据,可以安装dcgm exporter,不过k8s 1.13 才能支持
更改 prometheus的显示时区
prometheus为避免时区混乱,在所有组件中专门使用Unix time和UTC进行显示。不支持在配置文件中设置时区,也不能读取本机/etc/timezone时区。
其实这个限制是不影响使用的:
如果做可视化,grafana是可以做时区转换的
如果是调接口,拿到了数据中的时间戳,你想怎么处理都可以
如果因为prometheus 自带的 ui不是本地时间,看着不舒服, 2.16 版本的新版 webui已经引入了local timezone 的选项。区别见下图
如果你仍然想改prometheus 代码来适应自己的时区,可以参考这篇文章
关于 timezone 的讨论,可以看这个issue
如何采集 lb 后面的rs 的 metric
假如你有一个负载均衡lb,但网络上prometheus 只能访问到 lb本身,访问不到后面的rs,应该如何采集 rs 暴露的 metric?
rs 的服务加 sidecar proxy,或者本机增加 proxy 组件,保证prometheus能访问到
lb 增加/ backend1和/ backend2请求转发到两个单独的后端,再由prometheus访问 lb 采集
版本
prometheus当前最新版本为 2.16,prometheus还在不断迭代,因此尽量用最新版,1.x版本就不用考虑了。
2.16 版本上有一套实验 UI,可以查看TSDB的状态,包括top 10的label、metric
prometheus 大内存问题
随着规模变大,prometheus需要的cpu和内存都会升高,内存一般先达到瓶颈,这个时候要么加内存,要么集群分片减少单机指标。这里我们先讨论单机版prometheus的内存问题
原因:
prometheus 的内存消耗主要是因为每隔2小时做一个 block 数据落盘,落盘之前所有数据都在内存里面,因此和采集量有关。
加载历史数据时,是从磁盘到内存的,查询范围越大,内存越大。这里面有一定的优化空间
一些不合理的查询条件也会加大内存,如 group、大范围rate
我的指标需要多少内存:
作者给了一个计算器,设置指标量、采集间隔之类的,计算 prometheus 需要的理论内存值:https://www.robustperception.io/how-much-ram-does-prometheus-2-x-need-for-cardinality-and-ingestion
有什么优化方案:
sample 数量超过了 200 万,就不要单实例了,做下分片,然后通过victoriametrics,thanos,trickster等方案合并数据
评估哪些metric 和 label占用较多,去掉没用的指标。2.14 以上可以看 tsdb 状态
查询时尽量避免大范围查询,注意时间范围和 step 的比例,慎用 group
如果需要关联查询,先想想能不能通过 relabel 的方式给原始数据多加个 label,一条sql 能查出来的何必用join,时序数据库不是关系数据库。
prometheus 内存占用分析:
相关 issue:
prometheus 容量规划
容量规划除了上边说的内存,还有磁盘存储规划,这和你的 prometheus 的架构方案有关
如果是单机prometheus,计算本地磁盘使用量
如果是 remote-write,和已有的 tsdb 共用即可。
如果是 thanos 方案,本地磁盘可以忽略(2h),计算对象存储的大小就行。
Prometheus每2小时将已缓冲在内存中的数据压缩到磁盘上的块中。包括chunks, indexes, tombstones 和metadata,这些占用了一部分存储空间。一般情况下,Prometheus中存储的每一个样本大概占用1-2字节大小(1.7byte)。可以通过promql来查看每个样本平均占用多少空间:
如果大致估算本地磁盘大小,可以通过以下公式:
保留时间(retention_time_seconds)和样本大小(bytes_per_sample)不变的情况下,如果想减少本地磁盘的容量需求,只能通过减少每秒获取样本数(ingested_samples_per_second)的方式。
查看当前每秒获取的样本数:
有两种手段,一是减少时间序列的数量,二是增加采集样本的时间间隔。考虑到Prometheus会对时间序列进行压缩,因此减少时间序列的数量效果更明显.
举例说明:
采集频率 30s,机器数量1000,metric种类6000,1000600026024 约 200 亿,30G左右磁盘
只采集需要的指标,如 match[], 或者统计下最常使用的指标,性能最差的指标
以上磁盘容量并没有把 WAL 文件算进去,WAL 文件(raw data)Prometheus 官方文档中说明至少会保存3个 write-ahead log files,每一个最大为128M(实际运行发现数量会更多)
因为我们使用了 thanos 的方案,所以本地磁盘只保留2h 热数据。WAL 每2小时生成一份block文件,block文件每2小时上传对象存储,本地磁盘基本没有压力。
关于prometheus存储机制,可以看这篇
对 apiserver的 性能影响
如果你的 prometheus 使用了kubernetes_sd_config做服务发现,请求一般会经过集群的 apiserver,随着规模的变大,需要评估下对 apiserver性能的影响,尤其是proxy失败的时候,会导致cpu 升高。当然了,如果单k8s集群规模太大,一般都是拆分集群,不过随时监测下 apiserver 的进程变化还是有必要的。
在监控cadvisor、docker、kube-proxy 的 metric 时,我们一开始选择从 apiserver proxy 到节点的对应端口,统一设置比较方便,但后来还是改为了直接拉取节点,apiserver 仅做服务发现。
rate 的计算逻辑
prometheus 中的counter类型主要是为了 rate 而存在的,即计算速率,单纯的counter计数意义不大,因为counter一旦重置,总计数就没有意义了。
rate会自动处理counter重置的问题,counter一般都是一直变大的,例如一个exporter启动,然后崩溃了。本来以每秒大约10的速率递增,但仅运行了半个小时,则速率(x_total [1h])将返回大约每秒5的结果。另外,counter的任何减少也会被视为counter重置。例如,如果时间序列的值为[5,10,4,6],则将其视为[5,10,14,16]。
rate值很少是精确的。由于针对不同目标的抓取发生在不同的时间,因此随着时间的流逝会发生抖动,query_range计算时很少会与抓取时间完美匹配,并且抓取有可能失败。面对这样的挑战,rate的设计必须是健壮的。
rate并非想要捕获每个增量,因为有时候增量会丢失,例如实例在抓取间隔中挂掉。如果counter的变化速度很慢,例如每小时仅增加几次,则可能会导致【假象】。比如出现一个counter时间序列,值为100,rate就不知道这些增量是现在的值,还是目标已经运行了好几年并且才刚刚开始返回。
建议将rate计算的范围向量的时间至少设为抓取间隔的四倍。这将确保即使抓取速度缓慢,且发生了一次抓取故障,您也始终可以使用两个样本。此类问题在实践中经常出现,因此保持这种弹性非常重要。例如,对于1分钟的抓取间隔,您可以使用4分钟的rate 计算,但是通常将其四舍五入为5分钟。
如果 rate 的时间区间内有数据缺失,他会基于趋势进行推测,比如:
详细的内容可以看下这个视频
反直觉的 p95统计
histogram_quantile 是 Prometheus 常用的一个函数,比如经常把某个服务的 P95 响应时间来衡量服务质量。不过它到底是什么意思很难解释得清,特别是面向非技术的同学,会遇到很多“灵魂拷问”。
我们常说 P95(p99,p90都可以) 响应延迟是 100ms,实际上是指对于收集到的所有响应延迟,有 5% 的请求大于 100ms,95% 的请求小于 100ms。Prometheus 里面的 histogram_quantile 函数接收的是 0-1 之间的小数,将这个小数乘以 100 就能很容易得到对应的百分位数,比如 0.95 就对应着 P95,而且还可以高于百分位数的精度,比如 0.9999。
当你用histogram_quantile画出响应时间的趋势图时,可能会被问:为什么p95大于或小于我的平均值?
正如中位数可能比平均数大也可能比平均数小,P99 比平均值小也是完全有可能的。通常情况下 P99 几乎总是比平均值要大的,但是如果数据分布比较极端,最大的 1% 可能大得离谱从而拉高了平均值。一种可能的例子:
服务 X 由顺序的 A,B 两个步骤完成,其中 X 的 P99 耗时 100ms,A 过程 P99 耗时 50ms,那么推测 B 过程的 P99 耗时情况是?
直觉上来看,因为有 X=A+B,所以答案可能是 50ms,或者至少应该要小于 50ms。实际上 B 是可以大于 50ms 的,只要 A 和 B 最大的 1% 不恰好遇到,B 完全可以有很大的 P99:
所以我们从题目唯一能确定的只有 B 的 P99 应该不能超过 100ms,A 的 P99 耗时 50ms 这个条件其实没啥用。
类似的疑问很多,因此对于histogram_quantile函数,可能会产生反直觉的一些结果,最好的处理办法是不断试验调整你的 bucket 的值,保证更多的请求时间落在更细致的区间内,这样的请求时间才有统计意义。
慢查询问题
promql 的基础知识看这篇文章
prometheus 提供了自定义的promql作为查询语句,在 graph上调试的时候,会告诉你这条 sql 的返回时间,如果太慢你就要注意了,可能是你的用法出现了问题。
评估 prometheus 的整体响应时间,可以用这个默认指标:
一般情况下响应过慢都是promql 使用不当导致,或者指标规划有问题,如:
大量使用 join 来组合指标或者增加 label,如将 kube-state-metric 中的一些 meta label和 node-exporter 中的节点属性 label加入到 cadvisor容器 数据里,像统计 pod 内存使用率并按照所属节点的机器类型分类,或按照所属rs 归类。
范围查询时,大的时间范围,step 值却很小,导致查询到的数量量过大。
rate会自动处理counter重置的问题,最好由 promql 完成,不要自己拿出来全部元数据在程序中自己做rate计算。
prometheus 是有基本预测功能的,如
deriv
和predict_linear
(更准确)可以根据已有数据预测未来趋势如果比较复杂且耗时的sql,可以使用record rule减少指标数量,并使查询效率更高,但不要什么指标都加record,一半以上的 metric 其实不太会查询到。同时 label 中的值不要加到record rule 的 name 中。
高基数问题Cardinality
高基数是数据库避不开的一个话题,对于mysql这种db来讲,基数是指特定列或字段中包含的唯一值的数量。基数越低,列中重复的元素越多。对于时序数据库而言,就是tags、label 这种标签值的数量多少。
比如 prometheus 中如果有一个指标 http_request_count{method="get",path="/abc",originIP="1.1.1.1"}
表示访问量,method 表示请求方法,originIP是客户端 IP,method的枚举值是有限的,但originIP却是无限的,加上其他 label 的排列组合就无穷大了,也没有任何关联特征,因此这种高基数不适合作为metric 的 label,真要的提取originIP,应该用日志的方式,而不是 metric 监控
时序数据库会为这些 label建立索引,以提高查询性能,以便您可以快速找到与所有指定标签匹配的值。如果值的数量过多,索引是没有意义的,尤其是做p95 等计算的时候,要扫描大量 series 数据
官方文档中对于label 的建议
如何查看当前的label 分布情况呢,可以使用 prometheus提供的tsdb工具。可以使用命令行查看,也可以在 2.16 版本以上的 prometheus graph 查看
top10 高基数的 metric
高基数的 label
找到最大的 metric 或 job
top10的 metric 数量: 按 metric 名字分
top10的 metric 数量: 按 job 名字分
k8s组件性能指标
除了基础的cadvisor资源监控,还应该对核心组件的 metric 进行采集,包括:
10250:kubelet监听端口,包括/stats/summary、metrics、metrics/cadvisor。10250为认证端口,非认证端口用10255
10251:kube-scheduler的 metric 端口,本地 127 访问不需要认证,如调度延迟,
10252:kube-controller的metric 端口,本地 127 访问不需要认证
6443: apiserver,需要证书认证,直接 curl命令为
curl --cacert /etc/kubernetes/pki/ca.pem --cert /etc/kubernetes/pki/admin.pem --key /etc/kubernetes/pki/admin-key.pem https://ip:6443/metrics -k
2379: etcd的 metric 端口,直接 curl命令为:
curl --cacert /etc/etcd/ssl/ca.pem --cert /etc/etcd/ssl/etcd.pem --key /etc/etcd/ssl/etcd-key.pem https://localhost:2379/metrics -k
docker 指标暴露:
如果要开放 docker进程指标,需要开启实验特性,文件/etc/docker/daemon.json
kube-proxy 指标:
端口为10249,默认 127开放,可以修改为 hostname 开放,--metrics-bind-address=机器 ip
prometheus 重启慢
prometheus重启的时候需要把 wal 中的内容 load 到内存里,保留时间越久、wal 文件越大,重启的实际越长,这个是prometheus的机制,没得办法,因此能 reload 的,就不要重启,重启一定会导致短时间的不可用,而这个时候prometheus高可用就很重要了。
但prometheus 也曾经对启动时间做过优化,在 2.6 版本中对于WAL的 load速度就做过速度的优化,希望重启的时间不超过 1 分钟
你的应用应该暴露多少指标
当你开发自己的服务的时候,你可能会把一些数据暴露 metric出去,比如特定请求数、goroutine 数等,指标数量多少合适呢?
虽然指标数量和你的应用规模相关,但也有一些建议(Brian Brazil),
比如简单的服务如缓存等,类似 pushgateway,大约 120 个指标,prometheus 本身暴露了 700 左右的指标,如果你的应用很大,也尽量不要超过 10000 个指标,需要合理控制你的 label。
relabel_configs 与metric_relabel_configs
relabel_config发生在采集之前,metric_relabel_configs发生在采集之后,合理搭配可以满足场景的配置
如
Prometheus 的预测能力
场景1:你的磁盘剩余空间一直在减少,并且降低的速度比较均匀,你希望知道大概多久之后达到阈值,并希望在某一个时刻报警出来。
场景2:你的pod内存使用率一直升高,你希望知道大概多久之后会到达 limit 值,并在一定时刻报警出来,在被杀掉之前上去排查。
prometheus的deriv和predict_linear方法可以满足这类需求, promtheus 提供了基础的预测能力,基于当前的变化速度,推测一段时间后的值。
以mem_free 为例,最近一小时的 free值一直在下降。
deriv函数可以显示指标在一段时间的变化速度
predict_linear方法是预测基于这种速度,最后可以达到的值
你可以基于设置合理的报警规则,如小于 10 时报警
predict_linear与deriv的关系,含义上约等于,predict_linear稍微准确一些。
如果你要基于 metric做模型预测,可以参考下forecast-prometheus
错误的高可用设计
应用程序将metric 推到到消息队列如 kafaka,然后经过 exposer消费中转,再被 prometheus 拉取。产生这种方案的原因一般是有历史包袱、复用现有组件、想通过 mq 来提高扩展性。
这种方案有几个问题:
增加了 queue 组件,多了一层依赖,如果 app与 queue 之间连接失败,难道要在app本地缓存监控数据?
抓取时间可能会不同步,延迟的数据将会被标记为陈旧数据,当然你可以通过添加时间戳来标识,但就失去了对陈旧数据的处理逻辑
扩展性问题:prometheus 适合大量小目标,而不是一个大目标,如果你把所有数据都放在了 exposer 中,那么 prometheus 的单个 job拉取就会成为cpu 瓶颈。这个和 pushgateway 有些类似,没有特别必要的场景,都不是官方建议的方式。
缺少了服务发现和拉取控制,prom只知道一个exposer,不知道具体是哪些 target,不知道他们的 up 时间,无法使用scrape_*等指标做查询,也无法用scrape_limit做限制。
如果你的架构和 prometheus 的设计理念相悖,可能要重新设计一下方案了,否则扩展性和可靠性反而会降低。
高可用方案
prometheus 高可用有几种方案:
基本 HA:即两套 prometheus 采集完全一样的数据,外边挂负载均衡
HA + 远程存储:除了基础的多副本prometheus,还通过Remote write 写入到远程存储,解决存储持久化问题
联邦集群:即federation,按照功能进行分区,不同的 shard 采集不同的数据,由Global节点来统一存放,解决监控数据规模的问题。
使用thanos 或者victoriametrics,来解决全局查询、多副本数据 join 问题。
就算使用官方建议的多副本 + 联邦,仍然会遇到一些问题:
本质原因是,prometheus的本地存储没有数据同步能力,要在保证可用性的前提下,再保持数据一致性是比较困难的,基础的 HA proxy 满足不了要求,比如:
集群的后端有 A 和 B 两个实例,A 和 B 之间没有数据同步。A 宕机一段时间,丢失了一部分数据,如果负载均衡正常轮询,请求打到A 上时,数据就会异常。
如果 A 和 B 的启动时间不同,时钟不同,那么采集同样的数据时间戳也不同,就不是多副本同样数据的概念了
就算用了远程存储,A 和 B 不能推送到同一个 tsdb,如果每人推送自己的 tsdb,数据查询走哪边就是问题了。
因此解决方案是在存储、查询两个角度上保证数据的一致:
存储角度:如果使用 remote write 远程存储, A 和 B后面可以都加一个 adapter,adapter做选主逻辑,只有一份数据能推送到 tsdb,这样可以保证一个异常,另一个也能推送成功,数据不丢,同时远程存储只有一份,是共享数据。方案可以参考这篇文章
查询角度:上边的方案实现很复杂且有一定风险,因此现在的大多数方案在查询层面做文章,比如thanos 或者victoriametrics,仍然是两份数据,但是查询时做数据去重和join。只是 thanos是通过 sidecar 把数据放在对象存储,victoriametrics是把数据remote write 到自己的 server 实例,但查询层 thanos-query 和victor的 promxy的逻辑基本一致。
我们采用了thanos来支持多地域监控数据,具体方案可以看这篇文章
关于日志
k8s 中的日志一般指是容器标准输出 + 容器内日志,方案基本是采用 fluentd/fluent-bit/filebeat等采集推送到 es,但还有一种是日志转 metric,如解析特定字符串出现次数,nginx 日志得到qps指标 等,这里可以采用 grok或者 mtail,以 exporter的形式提供 metric 给 prometheus
参考
本文为容器监控实践系列文章,完整内容见:container-monitor-book
Last updated