1. 程式人生 > 實用技巧 >如何用Prometheus監控十萬container的Kubernetes叢集

如何用Prometheus監控十萬container的Kubernetes叢集

此文轉載自:https://my.oschina.net/u/4365667/blog/4755357
大咖揭祕Java人都栽在了哪?點選免費領取《大廠面試清單》,攻克面試難關~>>>

概述

不久前,我們在文章《如何擴充套件單個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無法監控的大規模叢集。