Prometheus 监控系统:30个常见问题(下)

2020年5月14日 274点热度 0人点赞 0条评论

上篇:Prometheus 监控系统:30个常见问题(上)

十六、Prometheus 重启慢与热加载

Prometheus 重启的时候需要把 Wal 中的内容 Load 到内存里,保留时间越久、Wal 文件越大,重启的实际越长,这个是 Prometheus 的机制,没得办法,因此能 Reload 的就不要重启,重启一定会导致短时间的不可用,而这个时候Prometheus高可用就很重要了。

Prometheus 也曾经对启动时间做过优化,在 2.6 版本中对于Wal的 Load 速度就做过速度的优化,希望重启的时间不超过 1 分钟

Prometheus 提供了热加载能力,不过需要开启web.enable-lifecycle配置,更改完配置后,curl 下 reload 接口即可。prometheus-operator 中更改了配置会默认触发 reload,如果你没有使用operator,又希望可以监听 configmap 配置变化来 reload 服务,可以试下这个简单的脚本

#!/bin/shFILE=$1URL=$2HASH=$(md5sum $(readlink -f $FILE))while true; do   NEW_HASH=$(md5sum $(readlink -f $FILE))   if [ "$HASH" != "$NEW_HASH" ]; then     HASH="$NEW_HASH"     echo "[$(date +%s)] Trigger refresh"     curl -sSL -X POST "$2" > /dev/null   fi   sleep 5done

使用时和 prometheus 挂载同一个 configmap,传入如下参数即可:

args:    - /etc/prometheus/prometheus.yml    - http://prometheus.kube-system.svc.cluster.local:9090/-/reloadargs:    - /etc/alertmanager/alertmanager.yml    - http://prometheus.kube-system.svc.cluster.local:9093/-/reload

十七、你的应用需要暴露多少指标

当你开发自己的服务的时候,你可能会把一些数据暴露 Metric出去,比如特定请求数、Goroutine 数等,指标数量多少合适呢?

虽然指标数量和你的应用规模相关,但也有一些建议(Brian Brazil),

比如简单的服务如缓存等,类似 Pushgateway,大约 120 个指标,Prometheus 本身暴露了 700 左右的指标,如果你的应用很大,也尽量不要超过 10000 个指标,需要合理控制你的 Label。

十八、node-exporter 的问题

  • node-exporter 不支持进程监控,这个前面已经提到了。

  • node-exporter 只支持 unix 系统,windows机器 请使用 wmi_exporter。因此以 yaml 形式不是 node-exporter 的时候,node-selector 要表明os类型。

  • 因为node_exporter是比较老的组件,有一些最佳实践并没有merge进去,比如符合Prometheus命名规范,因此建议使用较新的0.16和0.17版本。

一些指标名字的变化:

* node_cpu ->  node_cpu_seconds_total* node_memory_MemTotal -> node_memory_MemTotal_bytes* node_memory_MemFree -> node_memory_MemFree_bytes* node_filesystem_avail -> node_filesystem_avail_bytes* node_filesystem_size -> node_filesystem_size_bytes* node_disk_io_time_ms -> node_disk_io_time_seconds_total* node_disk_reads_completed -> node_disk_reads_completed_total* node_disk_sectors_written -> node_disk_written_bytes_total* node_time -> node_time_seconds* node_boot_time -> node_boot_time_seconds* node_intr -> node_intr_total

如果你之前用的旧版本 exporter,在绘制 grafana 的时候指标名称就会有差别,解决方法有两种:

  • 一是在机器上启动两个版本的node-exporter,都让prometheus去采集。

  • 二是使用指标转换器,他会将旧指标名称转换为新指标

十九、kube-state-metric 的问题

kube-state-metric 的使用和原理可以先看下这篇

除了文章中提到的作用,kube-state-metric还有一个很重要的使用场景,就是和 cadvisor 指标组合,原始的 cadvisor 中只有 pod 信息,不知道属于哪个 deployment 或者 sts,但是和kube-state-metric 中的 kube_pod_info 做 join 查询之后就可以显示出来,kube-state-metric的元数据指标,在扩展 cadvisor 的 label 中起到了很多作用,prometheus-operator 的很多 record rule 就使用了 kube-state-metric 做组合查询。

kube-state-metric 中也可以展示 pod 的 label 信息,可以在拿到 cadvisor 数据后更方便地做 group by,如按照 pod 的运行环境分类。但是 kube-state-metric 不暴露 pod 的 annotation,原因是下面会提到的高基数问题,即 annotation 的内容太多,不适合作为指标暴露。

二十、relabel_configs 与 metric_relabel_configs

relabel_config 发生在采集之前,metric_relabel_configs 发生在采集之后,合理搭配可以满足很多场景的配置。

如:

metric_relabel_configs:  - separator: ;    regex: instance    replacement: $1    action: labeldrop
- source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_endpoints_name,      __meta_kubernetes_service_annotation_prometheus_io_port]    separator: ;    regex: (.+);(.+);(.*)    target_label: __metrics_path__    replacement: /api/v1/namespaces/${1}/services/${2}:${3}/proxy/metrics    action: replace

二十一、找到最大的 metric 或 job

top10的 metric 数量:按 metric 名字分

topk(10, count by (__name__)({__name__=~".+"}))
apiserver_request_latencies_bucket{} 62544apiserver_response_sizes_bucket{} 44600

top10的 metric 数量:按 job 名字分

topk(10, count by (__name__, job)({__name__=~".+"}))
{job="master-scrape"} 525667{job="xxx-kubernetes-cadvisor"} 50817{job="yyy-kubernetes-cadvisor"} 44261

二十二、反直觉的 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% 可能大得离谱从而拉高了平均值。一种可能的例子:

1, 1, ... 1, 901 // 共 100 条数据,平均值=10,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:

A = 1, 1, ... 1,  1,  1,  50,  50 // 共 100 条数据,P99=50B = 1, 1, ... 1,  1,  1,  99,  99 // 共 100 条数据,P99=99X = 2, 2, ... 1, 51, 51, 100, 100 // 共 100 条数据,P99=100
如果让 A 过程最大的 1% 接近 100Ms,我们也能构造出 P99 很小的 B:A = 50, 50, ... 50,  50,  99 // 共 100 条数据,P99=50B =  1,  1, ...  1,   1,  50 // 共 100 条数据,P99=1X = 51, 51, ... 51, 100, 100 // 共 100 条数据,P99=100

所以我们从题目唯一能确定的只有 B 的 P99 应该不能超过 100ms,A 的 P99 耗时 50Ms 这个条件其实没啥用。

类似的疑问很多,因此对于 histogram_quantile 函数,可能会产生反直觉的一些结果,最好的处理办法是不断试验调整你的 Bucket 的值,保证更多的请求时间落在更细致的区间内,这样的请求时间才有统计意义。

二十三、慢查询问题

Promql 的基础知识看这篇文章

Prometheus 提供了自定义的 Promql 作为查询语句,在 Graph 上调试的时候,会告诉你这条 Sql 的返回时间,如果太慢你就要注意了,可能是你的用法出现了问题。

评估 Prometheus 的整体响应时间,可以用这个默认指标:

prometheus_engine_query_duration_seconds{}

一般情况下响应过慢都是Promql 使用不当导致,或者指标规划有问题,如:

  • 大量使用 join 来组合指标或者增加 label,如将 kube-state-metric 中的一些 meta label和 node-exporter 中的节点属性 label加入到 cadvisor容器数据里,像统计 pod 内存使用率并按照所属节点的机器类型分类,或按照所属 rs 归类。

  • 范围查询时,大的时间范围 step 值却很小,导致查询到的数量过大。

  • rate 会自动处理 counter 重置的问题,最好由 promql 完成,不要自己拿出来全部元数据在程序中自己做 rate 计算。

  • 在使用 rate 时,range duration要大于等于step,否则会丢失部分数据

  • prometheus 是有基本预测功能的,如derivpredict_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 的建议

CAUTION: Remember that every unique combination of key-value label pairs represents a new time series, which can dramatically increase the amount of data stored. Do not use labels to store dimensions with high cardinality (many different label values), such as user IDs, email addresses, or other unbounded sets of values.

如何查看当前的Label 分布情况呢,可以使用 Prometheus提供的Tsdb工具。可以使用命令行查看,也可以在 2.16 版本以上的 Prometheus Graph 查看

[work@xxx bin]$ ./tsdb analyze ../data/prometheus/Block ID: 01E41588AJNGM31SPGHYA3XSXGDuration: 2h0m0sSeries: 955372Label names: 301Postings (unique label pairs): 30757Postings entries (total label pairs): 10842822....

图片

图片

top10 高基数的 metric

Highest cardinality metric names:87176 apiserver_request_latencies_bucket59968 apiserver_response_sizes_bucket39862 apiserver_request_duration_seconds_bucket37555 container_tasks_state....

高基数的 label

Highest cardinality labels:4271 resource_version3670 id3414 name1857 container_id1824 __name__1297 uid1276 pod...

二十五、Prometheus 的预测能力

场景1:你的磁盘剩余空间一直在减少,并且降低的速度比较均匀,你希望知道大概多久之后达到阈值,并希望在某一个时刻报警出来。

场景2:你的 Pod 内存使用率一直升高,你希望知道大概多久之后会到达 Limit 值,并在一定时刻报警出来,在被杀掉之前上去排查。

Prometheus 的 Deriv 和 Predict_Linear 方法可以满足这类需求, Promtheus 提供了基础的预测能力,基于当前的变化速度,推测一段时间后的值。

以 mem_free 为例,最近一小时的 free 值一直在下降。

mem_free仅为举例,实际内存可用以mem_available为准

图片

deriv函数可以显示指标在一段时间的变化速度:

图片

predict_linear方法是预测基于这种速度,最后可以达到的值:

predict_linear(mem_free{instanceIP="100.75.155.55"}[1h], 2*3600)/1024/1024

图片

你可以基于设置合理的报警规则,如小于 10 时报警:

rule: predict_linear(mem_free{instanceIP="100.75.155.55"}[1h], 2*3600)/1024/1024 <10 

predict_linear 与 deriv 的关系: 含义上约等于如下表达式,不过 predict_linear 稍微准确一些。

  deriv(mem_free{instanceIP="100.75.155.55"}[1h]) * 2 * 3600+  mem_free{instanceIP="100.75.155.55"}[1h]

如果你要基于 Metric做模型预测,可以参考下forecast-prometheus

二十六、alertmanager 的上层封装

Prometheus 部署之后很少会改动,尤其是做了服务发现,就不需要频繁新增 target。但报警的配置是很频繁的,如修改阈值、修改报警人等。alertmanager 拥有丰富的报警能力如分组、抑制等,但如果你要想把他给业务部门使用,就要做一层封装了,也就是报警配置台。用户喜欢表单操作,而非晦涩的 yaml,同时他们也并不愿意去理解 promql。而且大多数公司内已经有现成的监控平台,也只有一份短信或邮件网关,所以最好能使用 webhook 直接集成。

例如: 机器磁盘使用量超过 90% 就报警,rule 应该写为:disk_used/disk_total > 0.9

如果不加 label 筛选,这条报警会对所有机器生效,但如果你想去掉其中几台机器,就得在disk_used和disk_total后面加上{instance != “”}。这些操作在 promql 中是很简单的,但是如果放在表单里操作,就得和内部的 cmdb 做联动筛选了。

  • 对于一些简单的需求,我们使用了 Grafana 的报警能力,所见即所得,直接在图表下面配置告警即可,报警阈值和状态很清晰。不过 Grafana 的报警能力很弱,只是实验功能,可以作为调试使用。

  • 对于常见的 pod 或应用监控,我们做了一些表单化,如下图所示:提取了 CPU、内存、磁盘 IO 等常见的指标作为选择项,方便配置。

图片

图片

  • 使用 webhook 扩展报警能力,改造 alertmanager, 在 send message 时做加密和认证,对接内部已有报警能力,并联动用户体系,做限流和权限控制。

  • 调用 alertmanager api 查询报警事件,进行展示和统计。

对于用户来说,封装 alertmanager yaml 会变的易用,但也会限制其能力,在增加报警配置时,研发和运维需要有一定的配合。如新写了一份自定义的 exporter,要将需要的指标供用户选择,并调整好展示和报警用的 promql。还有报警模板、原生 promql 暴露、用户分组等,需要视用户需求做权衡。

二十七、错误的高可用设计

有些人提出过这种类型的方案,想提高其扩展性和可用性。
图片

应用程序将 Metric 推到到消息队列如 Kafaka,然后经过 Exposer 消费中转,再被 Prometheus 拉取。产生这种方案的原因一般是有历史包袱、复用现有组件、想通过 Mq 来提高扩展性。

这种方案有几个问题:

  • 增加了 Queue 组件,多了一层依赖,如果 App与 Queue 之间连接失败,难道要在 App 本地缓存监控数据?

  • 抓取时间可能会不同步,延迟的数据将会被标记为陈旧数据,当然你可以通过添加时间戳来标识,但就失去了对陈旧数据的处理逻辑

  • 扩展性问题:Prometheus 适合大量小目标,而不是一个大目标,如果你把所有数据都放在了 Exposer 中,那么 Prometheus 的单个 Job 拉取就会成为 CPU 瓶颈。这个和 Pushgateway 有些类似,没有特别必要的场景,都不是官方建议的方式。

  • 缺少了服务发现和拉取控制,Prom 只知道一个 Exposer,不知道具体是哪些 Target,不知道他们的 UP 时间,无法使用 Scrape_* 等指标做查询,也无法用scrape_limit做限制。

如果你的架构和 Prometheus 的设计理念相悖,可能要重新设计一下方案了,否则扩展性和可靠性反而会降低。

二十八、prometheus-operator 的场景

如果你是在 K8S 集群内部署 Prometheus,那大概率会用到 prometheus-operator,他对 Prometheus 的配置做了 CRD 封装,让用户更方便的扩展 Prometheus实例,同时 prometheus-operator 还提供了丰富的 Grafana 模板,包括上面提到的 master 组件监控的 Grafana 视图,operator 启动之后就可以直接使用,免去了配置面板的烦恼。

operator 的优点很多,就不一一列举了,只提一下 operator 的局限:

  • 因为是 operator,所以依赖 K8S 集群,如果你需要二进制部署你的 Prometheus,如集群外部署,就很难用上prometheus-operator了,如多集群场景。当然你也可以在 K8S 集群中部署 operator 去监控其他的 K8S 集群,但这里面坑不少,需要修改一些配置。

  • operator 屏蔽了太多细节,这个对用户是好事,但对于理解 Prometheus 架构就有些 gap 了,比如碰到一些用户一键安装了operator,但 Grafana 图表异常后完全不知道如何排查,record rule 和 服务发现还不了解的情况下就直接配置,建议在使用 operator 之前,最好熟悉 prometheus 的基础用法。

  • operator 方便了 Prometheus 的扩展和配置,对于 alertmanager 和 exporter 可以很方便的做到多实例高可用,但是没有解决 Prometheus 的高可用问题,因为无法处理数据不一致,operator目前的定位也还不是这个方向,和 Thanos、Cortex 等方案的定位是不同的,下面会详细解释。

二十九、高可用方案

Prometheus 高可用有几种方案:

  1. 基本 HA:即两套 Prometheus 采集完全一样的数据,外边挂负载均衡

  2. HA + 远程存储:除了基础的多副本 Prometheus,还通过 Remote Write 写入到远程存储,解决存储持久化问题

  3. 联邦集群:即 Federation,按照功能进行分区,不同的 Shard 采集不同的数据,由Global节点来统一存放,解决监控数据规模的问题。

  4. 使用 Thanos 或者 Victoriametrics,来解决全局查询、多副本数据 Join 问题。

就算使用官方建议的多副本 + 联邦,仍然会遇到一些问题:

  1. 官方建议数据做 Shard,然后通过Federation来实现高可用,

  2. 但是边缘节点和Global节点依然是单点,需要自行决定是否每一层都要使用双节点重复采集进行保活。
    也就是仍然会有单机瓶颈。

  3. 另外部分敏感报警尽量不要通过Global节点触发,毕竟从Shard节点到Global节点传输链路的稳定性会影响数据到达的效率,进而导致报警实效降低。

  4. 例如服务Updown状态,Api请求异常这类报警我们都放在Shard节点进行报警。

本质原因是,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 来支持多地域监控数据,具体方案可以看这篇文章

三十、容器日志与事件

本文主要是 Prometheus 监控内容, 这里只简单介绍下 K8S 中的日志、事件处理方案,以及和 Prometheus 的搭配。

日志处理:

  • 日志采集与推送:一般是Fluentd/Fluent-Bit/Filebeat等采集推送到 ES、对象存储、kafaka,日志就该交给专业的 EFK 来做,分为容器标准输出、容器内日志。

  • 日志解析转 metric:可以提取一些日志转为 Prometheus 格式的指标,如解析特定字符串出现次数,解析 Nginx 日志得到 QPS 、请求延迟等。常用方案是 mtail 或者 grok

日志采集方案:

  • sidecar 方式:和业务容器共享日志目录,由 sidecar 完成日志推送,一般用于多租户场景。

  • daemonset 方式:机器上运行采集进程,统一推送出去。

需要注意的点:对于容器标准输出,默认日志路径是/var/lib/docker/containers/xxx, kubelet 会将改日志软链到/var/log/pods,同时还有一份/var/log/containers 是对/var/log/pods的软链。不过不同的 K8S 版本,日志的目录格式有所变化,采集时根据版本做区分:

  • 1.15 及以下:/var/log/pods/{pod_uid}/

  • 1.15 以上:var/log/pods/{pod_name+namespace+rs+uuid}/

事件:在这里特指 K8S Events,Events 在排查集群问题时也很关键,不过默认情况下只保留 1h,因此需要对 Events 做持久化。一般 Events 处理方式有两种:

  • 使用 kube-eventer 之类的组件采集 Events 并推送到 ES

  • 使用 event_exporter 之类的组件将Events 转化为 Prometheus Metric,同类型的还有谷歌云的 stackdriver 下的 event-exporter

- END -

图片推荐阅

《31天Kubernetes集训营,拿下CKA全球认证!》

Prometheus 监控系统:30个常见问题(上)

GitLab 内置了一个强大的 CI/CD 系统

Kubernetes 数据库 Etcd 日常运维及技巧

全网最详细的 K8s Service 不能访问排查流程

企业 MySQL 优化实施方案

Jenkins 多环境 CI/CD 架构设计

Linux服务器被入侵后的处理过程

疫情之下,人人都需要一技傍身,是时候拿下K8s了

图片

年轻时偷的懒,迟早是要还的。点亮图片

62650Prometheus 监控系统:30个常见问题(下)

这个人很懒,什么都没留下

文章评论