如何用Prometheus监控十万container的Kubernetes集群

概述

不久前,咱们在文章《如何扩展单个Prometheus实现近万Kubernetes集群监控?》中详细介绍了TKE团队大规模Kubernetes联邦监控系统Kvass的演进过程,其中介绍了针对规模较大的集群,咱们是如何经过修改Prometheus代码来实现横向扩缩容的。通过方案上的改进,Kvass目前已经支持以Sidecar的方式实现Prometheus集群化,而不是修改Prometheus代码。因为方案对社区有必定价值,团队决定将项目开源出来,分享给社区。项目地址node

本文首先将给出Prometheus的单机性能瓶颈,以及现有的社区集群化方案,随后将详细介绍开源版Kvass的设计思想,使用案例及压测结果。linux

另外,腾讯云容器团队在Kvass的设计思想上进一步优化,构建了支持多集群的高性能云原生监控服务,产品目前已正式公测,欢迎读者试用。传送门: https://console.cloud.tencent...git

后续章节咱们也将直接使用该产品来展现Kvass对大规模集群的监控能力。
1github

Prometheus

Prometheus依靠其强劲的单机性能,灵活的PromSQL,活跃的社区生态,逐渐成为云原生时代最核心的监控组件,被全球各大产商用于监控他们的核心业务。golang

然而,面对大规模监控目标(数千万series)时,因为原生Prometheus只有单机版本,不提供集群化功能,开发人员不得不经过不断增长机器的配置来知足Prometheus不断上涨的内存。api

单机性能瓶颈

咱们对单机Prometheus进行的压测,用以探测单个Prometheus分片的合理负载,压测的目标有两个。缓存

  • 肯定target数目对Prometheus负载的关系
  • 肯定series数目和Prometheus负载的关系

target相关性

咱们保持总series为100万不变, 经过改变target个数,观察Prometheus负载变更。
2
压测结果架构

target数量 CPU (core) mem (GB)
100 0.17 4.6
500 0.19 4.2
1000 0.16 3.9
5000 0.3 4.6
  • 从表中咱们发现target数目的改动对Prometheus负载的影响并非强相关的。在target数目增加50倍的状况下,CPU消耗有小量增加,可是内存几乎不变。

series相关性

咱们保持target数目不变,经过改变总series数,观察Prometheus的负载变更。并发

3

压测结果app

series数量 (万) CPU (core) mem (GB) 查询1000 series 15m数据(s)
100 0.191 3.15 0.2
300 0.939 20.14 1.6
500 2.026 30.57 1.5
  • 从表中,Prometheus的负载受到series的影响较大,series越多,资源消耗越大。
  • 当series数据超过300万时,Prometheus内存增加较为明显,须要使用较大内存的机器来运行。
压测过程当中,咱们使用了工具去生成预期数目的series,工具生成的series每一个label的长度及值的长度都较小,固定为10个字符左右。咱们的目的是观察相对负载变化,实际生产中因为label长度不一样,服务发现机制的消耗不一样,相同的series数目所消耗的负载会比压测中高很多。

现有集群化方案

针对单机Prometheus在大规模数据监控时的性能瓶颈问题,社区目前已经存在一些分片化方案,主要包括如下几种。

hash_mod

Prometheus官方支持经过Relabel机制,在配置文件中,对采集上来的数据进行hash,经过在不一样Prometheus实例的配置文件中指定不一样的moduleID来进行分片化,而后经过联邦,Thanos等方式将数据进行统一汇总,以下图所示,读者也能够直接参考【官方文档】。
4

配置文件分割

还有一种方法是根据业务进行job层面的分割,不一样Prometheus使用彻底独立的采集配置,其中包含了不一样的job,。
5

上述方案存在的问题

不管是hash_mod的方式,仍是配置文件分割的方式,其本质都是将数据切分到多个采集配置中,由不一样Prometheus进行采集。二者都存在如下几个缺点。

  • 对预监控数据要有所了解:使用上述方法的前提是使用者必须对监控对象会上报的数据有所了解,例如必须知道监控对象会上报某个用于hash_mod的label,或者必须知道不一样job的总体规模,才能对job进行划分。
  • 实例负载不均衡:虽然上述方案预期都是但愿将数据打散到不一样Prometheus实例上,但实际上经过某些label的值进行hash_mod的,或者干脆按job进行划分的方式并不能保证每一个实例最终所采集的series数是均衡的,实例依旧存在内存占用太高的风险。
  • 配置文件有侵入:使用者必须对原配置文件进行改造,加入Relabel相关配置,或者将一份配置文件划分红多份,因为配置文件再也不单一,新增,修改配置难度大大增长。
  • 没法动态扩缩容:上述方案中的因为配置是根据实际监控目标的数据规模来特殊制定的,并无一种统一的扩缩容方案,能够在数据规模增加时增长Prometheus个数。固然,用户若是针对本身业务实际状况编写扩缩容的工具确实是能够的,可是这种方式并不能在不一样业务间复用。
  • 部分API再也不正常:上述方案将数据打散到了不一样实例中,而后经过联邦或者Thanos进行汇总,获得全局监控数据,可是在不额外处理的状况下会致使部分Prometheus 原生API没法获得正确的值,最典型的是/api/v1/targets ,上述方案下没法获得全局targets值。

Kvass的原理

设计目标

针对上述问题,咱们但愿设计一种无侵入的集群化方案,它对使用者表现出来的,是一个与原生Prometheus配置文件一致,API兼容,可扩缩容的虚拟Prometheus。具体而言,咱们有如下设计目标。

  • 无侵入,单配置文件:咱们但愿使用者看到的,修改的都是一份原生的配置文件,不用加任何特殊的配置。
  • 无需感知监控对象:咱们但愿使用者再也不须要预先了解采集对象,不参与集群化的过程。
  • 实例负载尽量均衡:咱们但愿能根据监控目标的实际负载来划分采集任务,让实例尽量均衡。
  • 动态扩缩容:咱们但愿系统可以根据采集对象规模的变化进行动态扩缩容,过程当中数据不断点,不缺失。
  • 兼容核心PrometheusAPI:咱们但愿一些较为核心的API,如上边提到的/api/v1/target接口是正常的。

架构

Kvass由多个组件构成,下图给出了Kvass的架构图,咱们在架构图中使用了Thanos,实际上Kvass并不强依赖于Thanos,能够换成其余TSDB。

  • Kvass sidecar: 用于接收Coordinator下发的采集任务,生成新的配置文件给Prometheus,也服务维护target负载状况。
  • Kvass coordinator: 该组件是集群的中心控制器,负责服务发现,负载探测,targets下发等。
  • Thanos 组件: 图中只使用了Thanos sidecar与Thanos query,用于对分片的数据进行汇总,获得统一的数据视图。

Coordinator

Kvass coordinaor 首先会代替Prometheus对采集目标作服务发现,实时得到须要采集的target列表。

针对这些target,Kvass coordinaor会负责对其作负载探测,评估每一个target的series数,一旦target负载被探测成功,Kvass coordinaor 就会在下个计算周期将target分配给某个负载在阈值如下的分片。

Kvass coordinaor 还负责对分片集群作扩缩容。
7

服务发现

Kvass coordinaor引用了原生Prometheus的服务发现代码,用于实现与Prometheus 100%兼容的服务发现能力,针对服务发现获得的待抓取targets,Coordinaor会对其应用配置文件中的relabel_configs进行处理,获得处理以后的targets及其label集合。服务发现后获得的target被送往负载探测模块进行负载探测。

负载探测

负载探测模块从服务发现模块得到处理以后的targets,结合配置文件中的抓取配置(如proxy,证书等)对目标进行抓取,随后解析计算抓取结果,得到target的series规模。

负载探测模块并不存储任何抓取到的指标数据,只记录target的负载,负载探测只对target探测一次,不维护后续target的负载变化,长期运行的target的负载信息由Sidecar维护,咱们将在后面章节介绍。

target分配与扩容

在Prometheus单机性能瓶颈那一节,咱们介绍过Prometheus的内存和series相关,确切来讲,Prometheus的内存和其head series直接相关。Prometheus 会将最近(默认为2小时)采集到的数据的series信息缓存在内存中,咱们若是能控制好每一个分片内存中head series的数目,就能有效控制每一个分片的内存使用量,而控制head series实际就是控制分片当前采集的target列表。

基于上边的思路,Kvass coordinaor会周期性的对每一个分片当前采集的target列表进行管理:分配新target,删除无效target。

在每一个周期,Coordinaor会首先从全部分片得到当前运行状态,其中包括分片当前内存中的series数目及当前正在抓取的target列表。随后针对从服务发现模块获得的全局target信息进行如下处理

  • 若是该target已经被某个分片抓取,则继续分配给他,分片的series数不变。
  • 若是该target没有任何分片抓取,则从负载探测模块得到其series(若是还未探测完则跳过,下个周期继续),从分片中挑一个目前内存中series加上该target的series后依然比阈值低的,分配给他。
  • 若是当前全部分片无法容纳全部待分配的targets,则进行扩容,扩容数量与全局series总量成正比。

target迁移和缩容

在系统运行过程当中,target有可能会被删除,若是某个分片的target被删除且超过2小时,则该分片中的head series就会下降,也就是出现了部分空闲,由于target分配到了不一样分片,若是有大量target被删除,则会出现不少分片的内存占用都很低的状况,这种状况下,系统的资源利用率很低,咱们须要对系统进行缩容。

当出现这种情时,Coordinaor会对target进行迁移,即将序号更大的分片(分片会从0进行编号)中的target转移到序号更低的分片中,最终让序号低的分片负载变高,让序号高的分片彻底空闲出来。若是存储使用了thanos,并会将数据存储到cos中,则空闲分片在通过2小时候会删除(确保数据已被传到cos中)。

多副本

Kvass的分片当前只支持以StatefulSet方式部署。

Coordinator将经过label selector来得到全部分片StatefulSet,每一个StatefulSet被认为是一个副本,StatefulSet中编号相同的Pod会被认为是同一个分片组,相同分片组的Pod将被分配相同的target并预期有相同的负载。
8

/api/v1/targets接口

上文提到Coordinator根据配置文件作了服务发现,获得了target列表,因此Coordinator实际上能够获得/api/v1/targets接口所须要的返回结果集合,可是因为Coordinator只作了服务发现,并不进行实际采集,因此target的采集状态(例如健康状态,上一次采集时间等)都没法直接得知。

当Coordinator接收到/api/v1/targets请求时,他会基于服务发现获得的target集合,结合向Sidecar(若是target已分配)或向探测模块(target还未分配)询问target采集状态,综合后将正确的/api/v1/targets结果返回。

Sidecar

上一节介绍了Kvass coordinaor的基本功能,要想系统正常运行,还须要Kvass sidecar的配合,其核心思想是将配置文件中全部服务发现模式所有改为static_configs并直接将已经relabel过的target信息写入配置中,来达到消除分片服务发现和relabel行为,只采集部分target的效果。
9

每一个分片都会有一个Kvass sidecar,其核心功能包括从Kvass coordinator接受本分片负责的target列表,生成新的配置文件给该分片的Prometheus使用。另外,Kvass sidecar还会劫持抓取请求,维护target最新负载。Kvass sidecar还做为PrometheusAPI的网关,修正部分请求结果。
10

配置文件生成

Coordinaor通过服务发现,relabel及负载探测后,会将target分配给某个分片,并将target信息下发给Sidecar,包括

  • target的地址,
  • target预估的series值
  • target的hash值
  • 处理完relabel以后的label集合。

Sidecar根据从Coordinator获得的target信息,结合原始配置文件,生成一个新的配置文件给Prometheus使用,这个新的配置文件作了以下改动。

  • 将全部服务发现机制改成static_configs模式,并直接写入target列表,每一个target包含通过relabel以后的label值
  • 因为如今target已经relabel过了,因此删除job配置中的relabel_configs项,可是依旧保留metrics_rebale_configs
  • 将target的label中的scheme字段所有替换成http,并将原schme以请求参数的形式加入到label集合中
  • 将target的job_name以请求参数的形式加入到label集合中* 注入proxy_url将全部抓取请求代理到Sidecar

11

咱们来看一个例子,假如原来的配置是一个kubelet的采集配置

global:
  evaluation_interval: 30s
  scrape_interval: 15s
scrape_configs:
- job_name: kubelet
  honor_timestamps: true
  metrics_path: /metrics
  scheme: https
  kubernetes_sd_configs:
  - role: node
  bearer_token: xxx
  tls_config:
    insecure_skip_verify: true
  relabel_configs:
  - separator: ;
    regex: __meta_kubernetes_node_label_(.+)
    replacement: $1
    action: labelmap

经过注入将生成一个新的配置文件

global:
  evaluation_interval: 30s
  scrape_interval: 15s
scrape_configs:
- job_name: kubelet                                        
  honor_timestamps: true                                                                      
  metrics_path: /metrics                                   
  scheme: https  
  proxy_url: http://127.0.0.1:8008 # 全部抓取请求代理到Sidecar
  static_configs:                                          
  - targets:                                               
    - 111.111.111.111:10250                                   
    labels:                                                
      __address__: 111.111.111.111:10250                      
      __metrics_path__: /metrics                           
      __param__hash: "15696628886240206341"                
      __param__jobName: kubelet                            
      __param__scheme: https  # 保存原始的scheme                             
      __scheme__: http        # 设置新的scheme,这将使得代理到Sidecar的抓取请求都是http请求
      # 如下是通过relabel_configs处理以后获得的label集合
      beta_kubernetes_io_arch: amd64                       
      beta_kubernetes_io_instance_type: QCLOUD             
      beta_kubernetes_io_os: linux                         
      cloud_tencent_com_auto_scaling_group_id: asg-b4pwdxq5
      cloud_tencent_com_node_instance_id: ins-q0toknxf     
      failure_domain_beta_kubernetes_io_region: sh         
      failure_domain_beta_kubernetes_io_zone: "200003"     
      instance: 172.18.1.106                               
      job: kubelet                                         
      kubernetes_io_arch: amd64                            
      kubernetes_io_hostname: 172.18.1.106                 
      kubernetes_io_os: linux

上边新生成的配置文件是Prometheus真正使用的配置文件,Sidecar经过Coordinator下发的target列表来生成配置,就可让Prometheus有选择性得进行采集。

抓取劫持

在上边的配置生成中,咱们会将proxy注入到job的配置中,而且target的label中,scheme会被设置成http,因此Prometheus全部的抓取请求都会被代理到Sidecar,之因此要这么作,是由于Sidecar须要维护每一个target新的series规模,用于Coordinator查阅后做为target迁移的参考。

从上边配置生成咱们能够看到,有如下几个额外的请求参数会被一并发送到Sidecar

  • hash:target的hash值,用于Sidecar识别是哪一个target的抓取请求,hash值由Coordinator根据target的label集合进行计算得到并传递给Sidecar。
  • jobName:是哪一个job下的抓取请求,用于Sidecar根据原配置文件中job的请求配置(如原proxy_url,证书等)对抓取目标发起真正的请求。
  • scheme:这里的scheme是target经过relabel操做以后最终获得的协议值,虽然在job配置文件中已经有scheme字段,但Prometheus配置文件依旧支持经过relabel指定某个target的请求协议。在上述生成新配置过程当中,咱们将真实的scheme保存到这个参数里,而后将scheme所有设置成http。

有了上述几个参数,Sidecar就能够对抓取目标发起正确的请求,并获得监控数据,在统计的target此次抓取的series规模后,Sidecar会将监控数据拷贝一份给Prometheus。

API代理

因为Sidecar的存在,部分发往Prometheus的API请求须要被特殊处理,包括

  • /-/reload:因为Prometheus真正使用的配置文件由Sidecar生成,针对该接口,须要由Sidecar去处理并在处理成功后调用Prometheus的/-/reload接口。
  • /api/v1/status/config:该接口须要由Sidecar处理并把原配置文件返回。
  • 其余接口直接发往Prometheus。

全局数据视图

因为咱们将采集目标分散到了不一样分片中,致使每一个分片的数据都只是全局数据的一部分,因此咱们须要使用额外的组件来将全部数据进行汇总并去重(多副本的状况下),获得全局数据视图。

以thanos为例

thanos是一个很是好的方案,经过加入thanos组件,能够很方便得获得kvass集群的全局数据视图。固然咱们也能够经过加入remote writer配置来使用其余TSDB方案,例如influxdb,M3等等。

使用例子

这一节咱们经过一个部署例子,来直观感觉一下Kvass的效果,相关yaml文件能够在这里找到https://github.com/tkestack/k...
读者能够将项目clone到本地,并进入examples。

git clone https://github.com/tkestack/kvass.git
cd kvass/examples

部署数据生成器

咱们提供了一个metrics数据生成器,能够指定生成必定数量的series,在本例子中,咱们将部署6个metrics生成器副本,每一个会生成10045 series (其中45 series为golang的metrics)。

kubectl create -f  metrics.yaml

部署kvass

如今咱们部署基于Kvass的Prometheus集群,用以采集这6个metrics生成器的指标。

首先咱们部署rbac相关配置

kubectl create -f kvass-rbac.yaml

接着部署一个Prometheus config文件,这个文件就是咱们的原始配置,咱们在这个配置文件中,使用kubernetes_sd来作服务发现

kubectl create -f config.yaml

配置以下

global:
  scrape_interval: 15s
  evaluation_interval: 15s
  external_labels:
    cluster: custom
scrape_configs:
- job_name: 'metrics-test'
  kubernetes_sd_configs:
    - role: pod
  relabel_configs:
  - source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_name]
    regex: metrics
    action: keep
  - source_labels: [__meta_kubernetes_pod_ip]
    action: replace
    regex: (.*)
    replacement: ${1}:9091
    target_label: __address__
  - source_labels:
    - __meta_kubernetes_pod_name
    target_label: pod

如今咱们来部署Kvass coordinator

kubectl create -f coordinator.yaml

咱们在Coordinator的启动参数中设置每一个分片的最大head series数目不超过30000

--shard.max-series=30000

咱们如今就能够部署带有Kvass sidecar的Prometheus了,这里咱们只部署单个副本

kubectl create -f prometheus-rep-0.yaml

部署thanos-query

为了获得全局数据,咱们须要部署一个thanos-query

kubectl create -f thanos-query.yaml

查看结果

根据上述计算,监控目标总计6个target, 60270 series,根据咱们设置每一个分片不能超过30000 series,则预期须要3个分片。

咱们发现,Coordinator成功将StatefulSet的副本数改为了3。
14

咱们看下单个分片内存中的series数目,发现只有2个target的量
15

咱们再经过thanos-query来查看全局数据,发现数据是完整的(其中metrics0为指标生成器生成的指标名)
16

17

云原生监控

腾讯云容器团队在Kvass的设计思想上进一步优化,构建了高性能支持多集群云原生监控服务,产品目前已正式公测。

大集群监控

这一节咱们就直接使用云原生监控服务来监控一个规模较大的真实集群,测试一下Kvass监控大集群的能力。
18

集群规模

咱们关联的集群规模大体以下

  • 1060个节点
  • 64000+ Pod
  • 96000+ container

采集配置

咱们直接使用云原生监控服务在关联集群默认添加的采集配置,目前已包含了社区主流的监控指标:

  • kube-state-metrics
  • node-exporer
  • kubelet
  • cadvisor
  • kube-apiserver
  • kube-scheduler
  • kube-controler-manager

19

20

测试结果

21

  • 总计3400+target, 2700+万series
  • 总计扩容了17个分片
  • 每一个分片series稳定在200w如下
  • 每一个分片消耗内存在6-10G左右

云原生监控所提供的默认Grafana面板也能正常拉取
22

targets列表也能正常拉取
23

多集群监控

值得一提的是,云原生监控服务不只支持监控单个大规模集群,还能够用同个实例监控多个集群,并支持采集和告警模板功能,可一键将采集告警模板下发至各地域各个集群,完全告别了每一个集群重复添加配置的问题。
24

总结

本文从问题分析,设计目标,原理剖析,使用案例等方面详细介绍了一种开源Prometheus集群化技术,可在不修改Prometheus代码的前提下使其支持横向扩缩容,从而监控单机Prometheus没法监控的大规模集群。

【腾讯云原生】云说新品、云研新术、云游新活、云赏资讯,扫码关注同名公众号,及时获取更多干货!!