概述
不久前,我們在文章《如何擴展單個Prometheus實作近萬Kubernetes集群監控?》中詳細介紹了TKE團隊大規模Kubernetes聯邦監控系統Kvass的演程序序,其中介紹了針對規模較大的集群,我們是如何通過修改Prometheus代碼來實作橫向擴縮容的,經過方案上的改進,Kvass目前已經支持以Sidecar的方式實作Prometheus集群化,而不是修改Prometheus代碼,由于方案對社區有一定價值,團隊決定將專案開源出來,分享給社區,專案地址
本文首先將給出Prometheus的單機性能瓶頸,以及現有的社區集群化方案,隨后將詳細介紹開源版Kvass的設計思想,使用案例及壓測結果,
另外,騰訊云容器團隊在Kvass的設計思想上進一步優化,構建了支持多集群的高性能云原生監控服務,產品目前已正式公測,歡迎讀者試用,傳送門: https://console.cloud.tencent.com/tke2/prometheus
后續章節我們也將直接使用該產品來展示Kvass對大規模集群的監控能力,

Prometheus
Prometheus依靠其強勁的單機性能,靈活的PromSQL,活躍的社區生態,逐漸成為云原生時代最核心的監控組件,被全球各大產商用于監控他們的核心業務,
然而,面對大規模監控目標(數千萬series)時,由于原生Prometheus只有單機版本,不提供集群化功能,開發人員不得不通過不斷增加機器的配置來滿足Prometheus不斷上漲的記憶體,
單機性能瓶頸
我們對單機Prometheus進行的壓測,用以探測單個Prometheus分片的合理負載,壓測的目標有兩個,
- 確定target數目對Prometheus負載的關系
- 確定series數目和Prometheus負載的關系
target相關性
我們保持總series為100萬不變, 通過改變target個數,觀察Prometheus負載變動,

壓測結果
| 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的負載變動,

壓測結果
| 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等方式將資料進行統一匯總,如下圖所示,讀者也可以直接參考【官方檔案】,

組態檔分割
還有一種方法是根據業務進行job層面的分割,不同Prometheus使用完全獨立的采集配置,其中包含了不同的job,,

上述方案存在的問題
無論是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 還負責對分片集群做擴縮容,

服務發現
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并預期有相同的負載,

/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的效果,

每個分片都會有一個Kvass sidecar,其核心功能包括從Kvass coordinator接受本分片負責的target串列,生成新的組態檔給該分片的Prometheus使用,另外,Kvass sidecar還會劫持抓取請求,維護target最新負載,Kvass sidecar還作為PrometheusAPI的網關,修正部分請求結果,

組態檔生成
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

我們來看一個例子,假如原來的配置是一個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/kvass/tree/master/examples
讀者可以將專案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,

我們看下單個分片記憶體中的series數目,發現只有2個target的量

我們再通過thanos-query來查看全域資料,發現資料是完整的(其中metrics0為指標生成器生成的指標名)


云原生監控
騰訊云容器團隊在Kvass的設計思想上進一步優化,構建了高性能支持多集群云原生監控服務,產品目前已正式公測,
大集群監控
這一節我們就直接使用云原生監控服務來監控一個規模較大的真實集群,測驗一下Kvass監控大集群的能力,

集群規模
我們關聯的集群規模大致如下
- 1060個節點
- 64000+ Pod
- 96000+ container
采集配置
我們直接使用云原生監控服務在關聯集群默認添加的采集配置,目前已包含了社區主流的監控指標:
- kube-state-metrics
- node-exporer
- kubelet
- cadvisor
- kube-apiserver
- kube-scheduler
- kube-controler-manager


測驗結果

- 總計3400+target, 2700+萬series
- 總計擴容了17個分片
- 每個分片series穩定在200w以下
- 每個分片消耗記憶體在6-10G左右
云原生監控所提供的默認Grafana面板也能正常拉取

targets串列也能正常拉取

多集群監控
值得一提的是,云原生監控服務不僅支持監控單個大規模集群,還可以用同個實體監控多個集群,并支持采集和告警模板功能,可一鍵將采集告警模板下發至各地域各個集群,徹底告別了每個集群重復添加配置的問題,

總結
本文從問題分析,設計目標,原理剖析,使用案例等方面詳細介紹了一種開源Prometheus集群化技術,可在不修改Prometheus代碼的前提下使其支持橫向擴縮容,從而監控單機Prometheus無法監控的大規模集群,
【騰訊云原生】云說新品、云研新術、云游新活、云賞資訊,掃碼關注同名公眾號,及時獲取更多干貨!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/227633.html
標籤:其他

