Flink从入门到精通系列文章

监控系统的历史悠久,是一个很成熟的方向,而Prometheus作为新生代的开源监控系统,慢慢成为了云原生体系的事实标准,也证明了其设计很受欢迎。本文主要分享在Prometheus实践中遇到的一些问题和思考,如果你对Kubernetes监控体系或Prometheus的设计还不太了解,可以先看下容器监控系列[1]。
几点原则
监控是基础设施,目的是为了解决问题,不要只朝着大而全去做,尤其是不必要的指标采集,浪费人力和存储资源(ToB商业产品例外)。
需要处理的告警才发出来,发出来的告警必须得到处理。
简单的架构就是最好的架构,业务系统都挂了,监控也不能挂。GoogleSRE里面也说避免使用Magic系统,例如机器学习报警阈值、自动修复之类。这一点见仁见智吧,感觉很多公司都在搞智能AI运维。
Prometheus的局限
Prometheus是基于Metric的监控,不适用于日志(Logs)、事件(Event)、调用链(Tracing)。
Prometheus默认是Pull模型,合理规划你的网络,尽量不要转发。
对于集群化和水平扩展,官方和社区都没有银弹,需要合理选择Federate、Cortex、Thanos等方案。
监控系统一般情况下可用性大于一致性,容忍部分副本数据丢失,保证查询请求成功。这个后面说Thanos去重的时候会提到。
Prometheus不一定保证数据准确,这里的不准确一是指rate、histogram_quantile等函数会做统计和推断,产生一些反直觉的结果,这个后面会详细展开。二来查询范围过长要做降采样,势必会造成数据精度丢失,不过这是时序数据的特点,也是不同于日志系统的地方。

Kubernetes集群中常用的Exporter
Prometheus属于CNCF项目,拥有完整的开源生态,与Zabbix这种传统Agent监控不同,它提供了丰富的Exporter来满足你的各种需求。你可以在这里[2]看到官方、非官方的Exporter。如果还是没满足你的需求,你还可以自己编写Exporter,简单方便、自由开放,这是优点。
但是过于开放就会带来选型、试错成本。之前只需要在ZabbixAgent里面几行配置就能完成的事,现在你会需要很多Exporter搭配才能完成。还要对所有Exporter维护、监控。尤其是升级Exporter版本时,很痛苦。非官方Exporter还会有不少bug。这是使用上的不足,当然也是Prometheus的设计原则。
Kubernetes生态的组件都会提供/metric接口以提供自监控,这里列下我们正在使用的:
cAdvisor:集成在Kubelet中。
kubelet:10255为非认证端口,10250为认证端口。
apiserver:6443端口,关心请求数、延迟等。
scheduler:10251端口。
controller-manager:10252端口。
etcd:如etcd写入读取延迟、存储容量等。
Docker:需要开启experimental实验特性,配置metrics-addr,如容器创建耗时等指标。
kube-proxy:默认127暴露,10249端口。外部采集时可以修改为0.0.0.0监听,会暴露:写入iptables规则的耗时等指标。
kube-state-metrics:Kubernetes官方项目,采集Pod、Deployment等资源的元信息。
node-exporter:Prometheus官方项目,采集机器指标如CPU、内存、磁盘。
blackbox_exporter:Prometheus官方项目,网络探测,DNS、ping、http监控。
process-exporter:采集进程指标。
NVIDIAExporter:我们有GPU任务,需要GPU数据监控。
node-problem-detector:即NPD,准确的说不是Exporter,但也会监测机器状态,上报节点异常打taint。
应用层Exporter:MySQL、Nginx、MQ等,看业务需求。
还有各种场景下的自定义Exporter,如日志提取,后面会再做介绍。
Kubernetes核心组件监控与Grafana面板




模板可以参考GrafanaDashboardsforKubernetesAdministrators[3],根据运行情况不断调整报警阈值。
这里提一下Grafana虽然支持了templates能力,可以很方便地做多级下拉框选择,但是不支持templates模式下配置报警规则,相关issue[4]。
官方对这个功能解释了一堆,可最新版本仍然没有支持。借用issue的一句话吐槽下:
关于Grafana的基础用法,可以看这个文章[5]。
采集组件AllinOne
Prometheus体系中Exporter都是独立的,每个组件各司其职,如机器资源用Node-Exporter,GPU有NvidiaExporter等等。但是Exporter越多,运维压力越大,尤其是对Agent做资源控制、版本升级。我们尝试对一些Exporter进行组合,方案有二:
通过主进程拉起N个Exporter进程,仍然可以跟着社区版本做更新、bugfix。
用Telegraf来支持各种类型的Input,N合1。
另外,Node-Exporter不支持进程监控,可以加一个Process-Exporter,也可以用上边提到的Telegraf,使用procstat的input来采集进程指标。
合理选择黄金指标
Use方法:Utilization、Saturation、Errors。如Cadvisor数据
Red方法:Rate、Errors、Duration。如Apiserver性能指标
Prometheus采集中常见的服务分三种:
在线服务:如Web服务、数据库等,一般关心请求速率,延迟和错误率即RED方法
对Use和Red的实际示例可以参考《容器监控实践——Kubernetes常用指标分析[6]》这篇文章。
中cAdvisor的指标兼容问题
在版本,cAdvisor的指标去掉了pod_Name和container_name的label,替换为了Pod和Container。如果你之前用这两个label做查询或者Grafana绘图,需要更改下SQL了。因为我们一直支持多个Kubernetes版本,就通过relabel配置继续保留了原来的**_name。
metric_relabel_configs:
-source_labels:[container]
regex:(.+)
target_label:container_name
replacement:$1
action:replace
-source_labels:[pod]
regex:(.+)
target_label:pod_name
replacement:$1
action:replace
注意要用metric_relabel_configs,不是relabel_configs,采集后做的replace。
Prometheus采集外部Kubernetes集群、多集群
Prometheus如果部署在Kubernetes集群内采集是很方便的,用官方给的Yaml就可以,但我们因为权限和网络需要部署在集群外,二进制运行,采集多个Kubernetes集群。
以Pod方式运行在集群内是不需要证书的(In-Cluster模式),但集群外需要声明token之类的证书,并替换__address__,即使用ApiserverProxy采集,以cAdvisor采集为例,Job配置为:
-job_name:cluster-cadvisor
honor_timestamps:true
scrape_interval:30s
scrape_timeout:10s
metrics_path:/metrics
scheme:https
kubernetes_sd_configs:
-api_server:https://xx:6443
role:node
bearer_token_file:token/
tls_config:
insecure_skip_verify:true
bearer_token_file:token/
tls_config:
insecure_skip_verify:true
relabel_configs:
-separator:;
regex:__meta_kubernetes_node_label_(.+)
replacement:$1
action:labelmap
-separator:;
regex:(.*)
target_label:__address__
replacement:xx:6443
action:replace
-source_labels:[__meta_kubernetes_node_name]
separator:;
regex:(.+)
target_label:__metrics_path__
replacement:/api/v1/nodes/${1}/proxy/metrics/cadvisor
action:replace
metric_relabel_configs:
-source_labels:[container]
separator:;
regex:(.+)
target_label:container_name
replacement:$1
action:replace
-source_labels:[pod]
separator:;
regex:(.+)
target_label:pod_name
replacement:$1
action:replace
bearer_token_file需要提前生成,这个参考官方文档即可。记得base64解码。
对于cAdvisor来说,__metrics_path__可以转换为/api/v1/nodes/${1}/proxy/metrics/cadvisor,代表Apiserverproxy到Kubelet,如果网络能通,其实也可以直接把Kubelet的10255作为target,可以直接写为:${1}:10255/metrics/cadvisor,代表直接请求Kubelet,规模大的时候还减轻了Apiserver的压力,即服务发现使用Apiserver,采集不走Apiserver。
因为cAdvisor是暴露主机端口,配置相对简单,如果是kube-state-metric这种Deployment,以point形式暴露,写法应该是:
-job_name:cluster-service-points
honor_timestamps:true
scrape_interval:30s
scrape_timeout:10s
metrics_path:/metrics
scheme:https
kubernetes_sd_configs:
-api_server:https://xxx:6443
role:points
bearer_token_file:token/
tls_config:
insecure_skip_verify:true
bearer_token_file:token/
tls_config:
insecure_skip_verify:true
relabel_configs:
-source_labels:[__meta_kubernetes_service_annotation_prometheus_io_scrape]
separator:;
regex:"true"
replacement:$1
action:keep
-source_labels:[__meta_kubernetes_service_annotation_prometheus_io_scheme]
separator:;
regex:(https?)
target_label:__scheme__
replacement:$1
action:replace
-separator:;
regex:(.*)
target_label:__address__
replacement:xxx:6443
action:replace
-source_labels:[__meta_kubernetes_namespace,__meta_kubernetes_points_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
-separator:;
regex:__meta_kubernetes_service_label_(.+)
replacement:$1
action:labelmap
-source_labels:[__meta_kubernetes_namespace]
separator:;
regex:(.*)
target_label:kubernetes_namespace
replacement:$1
action:replace
-source_labels:[__meta_kubernetes_service_name]
separator:;
regex:(.*)
target_label:kubernetes_name
replacement:$1
action:replace
对于point类型,需要转换__metrics_path__为/api/v1/namespaces/${1}/services/${2}:${3}/proxy/metrics,需要替换namespace、svc名称端口等,这里的写法只适合接口为/metrics的exporter,如果你的exporter不是/metrics接口,需要替换这个路径。或者像我们一样统一约束都使用这个地址。
其他的一些relabel如kubernetes_namespace是为了保留原始信息,方便做PromQL查询时的筛选条件。
如果是多集群,同样的配置多写几遍就可以了,一般一个集群可以配置三类Job:
role:node的,包括cAdvisor、node-exporter、kubelet的summary、kube-proxy、Docker等指标
role:point的,包括kube-state-metric以及其他自定义Exporter
普通采集:包括etcd、Apiserver性能指标、进程指标等。
GPU指标的获取
nvidia-smi可以查看机器上的GPU资源,而cAdvisor其实暴露了Metric来表示容器使用GPU情况。
container_accelerator_duty_cycle
container_accelerator_memory_total_bytes
container_accelerator_memory_used_bytes
如果要更详细的GPU数据,可以安装dcgmexporter,不过才能支持。
更改Prometheus的显示时区
Prometheus为避免时区混乱,在所有组件中专门使用UnixTime和Utc进行显示。不支持在配置文件中设置时区,也不能读取本机/etc/timezone时区。
其实这个限制是不影响使用的:
如果做可视化,Grafana是可以做时区转换的。
如果是调接口,拿到了数据中的时间戳,你想怎么处理都可以。
如果因为Prometheus自带的UI不是本地时间,看着不舒服,2.16版本的新版WebUI已经引入了LocalTimezone的选项,区别见下图。
如果你仍然想改Prometheus代码来适应自己的时区,可以参考这篇文章[7]。


关于timezone的讨论,可以看这个issue[8]。
如何采集LB后面的RS的Metric
假如你有一个负载均衡LB,但网络上Prometheus只能访问到LB本身,访问不到后面的RS,应该如何采集RS暴露的Metric?
RS的服务加SidecarProxy,或者本机增加Proxy组件,保证Prometheus能访问到。
LB增加/back1和/back2请求转发到两个单独的后端,再由Prometheus访问LB采集。
版本的选择
Prometheus当前最新版本为2.16,Prometheus还在不断迭代,因此尽量用最新版,1.X版本就不用考虑了。
2.16版本上有一套实验UI,可以查看TSDB的状态,包括Top10的Label、Metric。

Prometheus大内存问题
随着规模变大,Prometheus需要的CPU和内存都会升高,内存一般先达到瓶颈,这个时候要么加内存,要么集群分片减少单机指标。这里我们先讨论单机版Prometheus的内存问题。
原因:
Prometheus的内存消耗主要是因为每隔2小时做一个Block数据落盘,落盘之前所有数据都在内存里面,因此和采集量有关。
加载历史数据时,是从磁盘到内存的,查询范围越大,内存越大。这里面有一定的优化空间。
一些不合理的查询条件也会加大内存,如Group或大范围Rate。
我的指标需要多少内存:
作者给了一个计算器,设置指标量、采集间隔之类的,计算Prometheus需要的理论内存值:计算公式[9]。
以我们的一个PrometheusServer为例,本地只保留2小时数据,95万Series,大概占用的内存如下:


有什么优化方案:
Sample数量超过了200万,就不要单实例了,做下分片,然后通过Victoriametrics,Thanos,Trickster等方案合并数据。
评估哪些Metric和Label占用较多,去掉没用的指标。2.14以上可以看TSDB状态
查询时尽量避免大范围查询,注意时间范围和Step的比例,慎用Group。
如果需要关联查询,先想想能不能通过Relabel的方式给原始数据多加个Label,一条SQL能查出来的何必用Join,时序数据库不是关系数据库。
Prometheus内存占用分析:
通过pprof分析:
1.X版本的内存:
相关issue:
!searchin/prometheus-users/memory%7Csort:date/prometheus-users/q4oiVGU6Bxo/uifpXVw3CwAJ
Prometheus容量规划
容量规划除了上边说的内存,还有磁盘存储规划,这和你的Prometheus的架构方案有关。
如果是单机Prometheus,计算本地磁盘使用量。
如果是Remote-Write,和已有的TSDB共用即可。
如果是Thanos方案,本地磁盘可以忽略(2H),计算对象存储的大小就行。
Prometheus每2小时将已缓冲在内存中的数据压缩到磁盘上的块中。包括Chunks、Indexes、Tombstones、Metadata,这些占用了一部分存储空间。一般情况下,Prometheus中存储的每一个样本大概占用1-2字节大小(1.7Byte)。可以通过PromQL来查看每个样本平均占用多少空间:
rate(prometheus_tsdb_compaction_chunk_size_bytes_sum[1h])
/
rate(prometheus_tsdb_compaction_chunk_samples_sum[1h])
{instance="0.0.0.0:8890",job="prometheus"}1.252747585939941
如果大致估算本地磁盘大小,可以通过以下公式:
磁盘大小=保留时间*每秒获取样本数*样本大小
保留时间(retention_time_seconds)和样本大小(bytes_per_sample)不变的情况下,如果想减少本地磁盘的容量需求,只能通过减少每秒获取样本数(ingested_samples_per_second)的方式。
查看当前每秒获取的样本数:
rate(prometheus_tsdb_head_samples_apped_total[1h])
有两种手段,一是减少时间序列的数量,二是增加采集样本的时间间隔。考虑到Prometheus会对时间序列进行压缩,因此减少时间序列的数量效果更明显。
举例说明:
采集频率30s,机器数量1000,Metric种类6000,1000600026024约200亿,30G左右磁盘。
只采集需要的指标,如match,或者统计下最常使用的指标,性能最差的指标。
以上磁盘容量并没有把wal文件算进去,wal文件(RawData)在Prometheus官方文档中说明至少会保存3个Write-AheadLogFiles,每一个最大为128M(实际运行发现数量会更多)。
因为我们使用了Thanos的方案,所以本地磁盘只保留2H热数据。Wal每2小时生成一份Block文件,Block文件每2小时上传对象存储,本地磁盘基本没有压力。
关于Prometheus存储机制,可以看这篇[10]。
对Apiserver的性能影响
如果你的Prometheus使用了kubernetes_sd_config做服务发现,请求一般会经过集群的Apiserver,随着规模的变大,需要评估下对Apiserver性能的影响,尤其是Proxy失败的时候,会导致CPU升高。当然了,如果单Kubernetes集群规模太大,一般都是拆分集群,不过随时监测下Apiserver的进程变化还是有必要的。
在监控cAdvisor、Docker、Kube-Proxy的Metric时,我们一开始选择从ApiserverProxy到节点的对应端口,统一设置比较方便,但后来还是改为了直接拉取节点,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%可能大得离谱从而拉高了平均值。一种可能的例子:
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=50
B=1,1,1,1,1,99,99//共100条数据,P99=99
X=2,2,1,51,51,100,100//共100条数据,P99=100
如果让A过程最大的1%接近100ms,我们也能构造出P99很小的B:
A=50,50,50,50,99//共100条数据,P99=50
B=1,1,1,1,50//共100条数据,P99=1
X=51,51,51,100,100//共100条数据,P99=100
所以我们从题目唯一能确定的只有B的P99应该不能超过100ms,A的P99耗时50ms这个条件其实没啥用。
类似的疑问很多,因此对于histogram_quantile函数,可能会产生反直觉的一些结果,最好的处理办法是不断试验调整你的Bucket的值,保证更多的请求时间落在更细致的区间内,这样的请求时间才有统计意义。
慢查询问题
PromQL的基础知识看这篇文章[12]。
Prometheus提供了自定义的PromQL作为查询语句,在Graph上调试的时候,会告诉你这条SQL的返回时间,如果太慢你就要注意了,可能是你的用法出现了问题。
评估Prometheus的整体响应时间,可以用这个默认指标:
prometheus_engine_query_duration_seconds{}
一般情况下响应过慢都是PromQL使用不当导致,或者指标规划有问题,如:
大量使用join来组合指标或者增加label,如将kube-state-metric中的一些metalabel和node-exporter中的节点属性label加入到cAdvisor容器数据里,像统计Pod内存使用率并按照所属节点的机器类型分类,或按照所属RSS归类。
范围查询时,大的时间范围step值却很小,导致查询到的数量过大。
rate会自动处理counter重置的问题,最好由PromQL完成,不要自己拿出来全部元数据在程序中自己做rate计算。
在使用rate时,rangeduration要大于等于step,否则会丢失部分数据。
Prometheus是有基本预测功能的,如deriv和predict_linear(更准确)可以根据已有数据预测未来趋势。
如果比较复杂且耗时的SQL,可以使用recordrule减少指标数量,并使查询效率更高,但不要什么指标都加record,一半以上的metric其实不太会查询到。同时label中的值不要加到recordrule的name中。
高基数问题Cardinality
高基数是数据库避不开的一个话题,对于MySQL这种DB来讲,基数是指特定列或字段中包含的唯一值的数量。基数越低,列中重复的元素越多。对于时序数据库而言,就是tags、label这种标签值的数量多少。
比如Prometheus中如果有一个指标http_request_count{method=quot;getquot;,path=quot;/abcquot;,originIP=quot;1.1.1.1quot;}表示访问量,method表示请求方法,originIP是客户端IP,method的枚举值是有限的,但originIP却是无限的,加上其他label的排列组合就无穷大了,也没有任何关联特征,因此这种高基数不适合作为Metric的label,真要的提取originIP,应该用日志的方式,而不是Metric监控。
时序数据库会为这些Label建立索引,以提高查询性能,以便你可以快速找到与所有指定标签匹配的值。如果值的数量过多,索引是没有意义的,尤其是做P95等计算的时候,要扫描大量Series数据。
官方文档中对于Label的建议:
如何查看当前的Label分布情况呢,可以使用Prometheus提供的TSDB工具。可以使用命令行查看,也可以在2.16版本以上的PrometheusGraph查看。
[work@xxxbin]$./tsdbanalyze../data/prometheus/
BlockID:01E41588AJNGM31SPGHYA3XSXG
Duration:2h0m0s
Series:955372
Labelnames:301
Postings(uniquelabelpairs):30757
Postingsentries(totallabelpairs):10842822
.


top10高基数的metric:
Highestcardinalitymetricnames:
87176apiserver_request_latencies_bucket
59968apiserver_response_sizes_bucket
39862apiserver_request_duration_seconds_bucket
37555container_tasks_state
.
高基数的label:
Highestcardinalitylabels:
4271resource_version
3670id
3414name
1857container_id
1824__name__
1297uid
1276pod
找到最大的Metric或Job
top10的Metric数量:按Metric名字分。
topk(10,countby(__name__)({__name__=~".+"}))
apiserver_request_latencies_bucket{}62544
apiserver_response_sizes_bucket{}44600
top10的Job数量:按Job名字分。
topk(10,countby(__name__,job)({__name__=~".+"}))
{job="master-scrape"}525667
{job="xxx-kubernetes-cadvisor"}50817
{job="yyy-kubernetes-cadvisor"}44261
Prometheus重启慢与热加载
Prometheus重启的时候需要把Wal中的内容Load到内存里,保留时间越久、Wal文件越大,重启的实际越长,这个是Prometheus的机制,没得办法,因此能Reload的就不要重启,重启一定会导致短时间的不可用,而这个时候Prometheus高可用就很重要了。
Prometheus也曾经对启动时间做过优化,在2.6版本中对于Wal的Load速度就做过速度的优化,希望重启的时间不超过1分钟。
Prometheus提供了热加载能力,不过需要开启配置,更改完配置后,curl下reload接口即可。prometheus-operator中更改了配置会默认触发reload,如果你没有使用Operator,又希望可以监听ConfigMap配置变化来reload服务,可以试下这个简单的脚本。
#!/bin/sh
FILE=$1
URL=$2
HASH=$(md5sum$(readlink-f$FILE))
whiletrue;do
NEW_HASH=$(md5sum$(readlink-f$FILE))
if["$HASH"!="$NEW_HASH"];then
HASH="$NEW_HASH"
echo"[$(date+%s)]Triggerrefresh"
curl-sSL-XPOST"$2"/dev/
fi
sleep5
done
使用时和Prometheus挂载同一个ConfigMap,传入如下参数即可:
args:
-/etc/prometheus/
-
args:
-/etc/alertmanager/
-
你的应用需要暴露多少指标
当你开发自己的服务的时候,你可能会把一些数据暴露Metric出去,比如特定请求数、Goroutine数等,指标数量多少合适呢?
虽然指标数量和你的应用规模相关,但也有一些建议(BrianBrazil),比如简单的服务,如缓存等,类似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的使用和原理可以先看下这篇[13]。
除了文章中提到的作用,kube-state-metric还有一个很重要的使用场景,就是和cAdvisor指标组合,原始的cAdvisor中只有Pod信息,不知道属于哪个Deployment或者sts,但是和kube-state-metric中的kube_pod_info做join查询之后就可以显示出来,kube-state-metric的元数据指标,在扩展cAdvisor的label中起到了很多作用,prometheus-operator的很多recordrule就使用了kube-state-metric做组合查询。
kube-state-metric中也可以展示Pod的label信息,可以在拿到cAdvisor数据后更方便地做groupby,如按照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_points_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
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警:
bashrule:predict_linear(mem_free{instanceIP="100.75.155.55"}[1h],2*3600)/1024/102410
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[14]。
alertmanager的上层封装
Prometheus部署之后很少会改动,尤其是做了服务发现,就不需要频繁新增target。但报警的配置是很频繁的,如修改阈值、修改报警人等。alertmanager拥有丰富的报警能力如分组、抑制等,但如果你要想把它给业务部门使用,就要做一层封装了,也就是报警配置台。用户喜欢表单操作,而非晦涩的yaml,同时他们也并不愿意去理解PromQL。而且大多数公司内已经有现成的监控平台,也只有一份短信或邮件网关,所以最好能使用webhook直接集成。
例如:机器磁盘使用量超过90%就报警,rule应该写为:disk_used/disk_。
如果不加label筛选,这条报警会对所有机器生效,但如果你想去掉其中几台机器,就得在disk_used和disk_total后面加上{instance!=""}。这些操作在PromQL中是很简单的,但是如果放在表单里操作,就得和内部的CMDB做联动筛选了。
对于一些简单的需求,我们使用了Grafana的报警能力,所见即所得,直接在图表下面配置告警即可,报警阈值和状态很清晰。不过Grafana的报警能力很弱,只是实验功能,可以作为调试使用。
对于常见的Pod或应用监控,我们做了一些表单化,如下图所示:提取了CPU、内存、磁盘IO等常见的指标作为选择项,方便配置。


使用webhook扩展报警能力,改造alertmanager,在smessage时做加密和认证,对接内部已有报警能力,并联动用户体系,做限流和权限控制。
调用alertmanagerapi查询报警事件,进行展示和统计。
对于用户来说,封装alertmanageryaml会变的易用,但也会限制其能力,在增加报警配置时,研发和运维需要有一定的配合。如新写了一份自定义的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的场景
如果你是在Kubernetes集群内部署Prometheus,那大概率会用到prometheus-operator,他对Prometheus的配置做了CRD封装,让用户更方便的扩展Prometheus实例,同时prometheus-operator还提供了丰富的Grafana模板,包括上面提到的Master组件监控的Grafana视图,Operator启动之后就可以直接使用,免去了配置面板的烦恼。
Operator的优点很多,就不一一列举了,只提一下Operator的局限:
因为是Operator,所以依赖Kubernetes集群,如果你需要二进制部署你的Prometheus,如集群外部署,就很难用上prometheus-operator了,如多集群场景。当然你也可以在Kubernetes集群中部署Operator去监控其他的Kubernetes集群,但这里面坑不少,需要修改一些配置。
Operator屏蔽了太多细节,这个对用户是好事,但对于理解Prometheus架构就有些gap了,比如碰到一些用户一键安装了Operator,但Grafana图表异常后完全不知道如何排查,recordrule和服务发现还不了解的情况下就直接配置,建议在使用Operator之前,最好熟悉Prometheus的基础用法。
Operator方便了Prometheus的扩展和配置,对于alertmanager和exporter可以很方便的做到多实例高可用,但是没有解决Prometheus的高可用问题,因为无法处理数据不一致,Operator目前的定位也还不是这个方向,和Thanos、Cortex等方案的定位是不同的,下面会详细解释。
高可用方案
Prometheus高可用有几种方案:
基本HA:即两套Prometheus采集完全一样的数据,外边挂负载均衡。
HA+远程存储:除了基础的多副本Prometheus,还通过RemoteWrite写入到远程存储,解决存储持久化问题。
联邦集群:即Federation,按照功能进行分区,不同的Shard采集不同的数据,由Global节点来统一存放,解决监控数据规模的问题。
使用Thanos或者Victoriametrics,来解决全局查询、多副本数据Join问题。
就算使用官方建议的多副本+联邦,仍然会遇到一些问题:
官方建议数据做Shard,然后通过Federation来实现高可用,
但是边缘节点和Global节点依然是单点,需要自行决定是否每一层都要使用双节点重复采集进行保活。也就是仍然会有单机瓶颈。
另外部分敏感报警尽量不要通过Global节点触发,毕竟从Shard节点到Global节点传输链路的稳定性会影响数据到达的效率,进而导致报警实效降低。
例如服务Updown状态,API请求异常这类报警我们都放在Shard节点进行报警。
本质原因是,Prometheus的本地存储没有数据同步能力,要在保证可用性的前提下,再保持数据一致性是比较困难的,基础的HAProxy满足不了要求,比如:
集群的后端有A和B两个实例,A和B之间没有数据同步。A宕机一段时间,丢失了一部分数据,如果负载均衡正常轮询,请求打到A上时,数据就会异常。
如果A和B的启动时间不同,时钟不同,那么采集同样的数据时间戳也不同,就不是多副本同样数据的概念了。
就算用了远程存储,A和B不能推送到同一个TSDB,如果每人推送自己的TSDB,数据查询走哪边就是问题了。
因此解决方案是在存储、查询两个角度上保证数据的一致:
存储角度:如果使用RemoteWrite远程存储,A和B后面可以都加一个Adapter,Adapter做选主逻辑,只有一份数据能推送到TSDB,这样可以保证一个异常,另一个也能推送成功,数据不丢,同时远程存储只有一份,是共享数据。方案可以参考这篇文章[15]。
查询角度:上边的方案实现很复杂且有一定风险,因此现在的大多数方案在查询层面做文章,比如Thanos或者Victoriametrics,仍然是两份数据,但是查询时做数据去重和Join。只是Thanos是通过Sidecar把数据放在对象存储,Victoriametrics是把数据RemoteWrite到自己的Server实例,但查询层Thanos-Query和Victor的Promxy的逻辑基本一致。
我们采用了Thanos来支持多地域监控数据,具体方案可以看这篇文章[16]。
容器日志与事件
本文主要是Prometheus监控内容,这里只简单介绍下Kubernetes中的日志、事件处理方案,以及和Prometheus的搭配。
日志处理:
日志采集与推送:一般是Fluentd/Fluent-Bit/Filebeat等采集推送到ES、对象存储、Kafka,日志就该交给专业的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的软链。不过不同的Kubernetes版本,日志的目录格式有所变化,采集时根据版本做区分:
1.15及以下:/var/log/pods/{pod_uid}/
1.15以上:var/log/pods/{pod_name+namespace+rs+uuid}/
事件:在这里特指KubernetesEvents,Events在排查集群问题时也很关键,不过默认情况下只保留1h,因此需要对Events做持久化。一般Events处理方式有两种:
使用kube-eventer之类的组件采集Events并推送到ES
使用event_exporter之类的组件将Events转化为PrometheusMetric,同类型的还有谷歌云的stackdriver下的event-exporter。
;v=67Ulrq6DxwA
基于ApacheFlink的实时监控告警系统
关于数据中台的深度思考与总结(干干货)
日志收集Agent,阴暗潮湿的地底世界
2020继续踏踏实实的做好自己

你点的每个赞,我都当成了喜欢
