如何擴充套件單個Prometheus實現近萬Kubernetes叢集監控?

騰訊雲原生發表於2020-08-31

引言

TKE團隊負責公有云,私有云場景下近萬個叢集,數百萬核節點的運維管理工作。為了監控規模如此龐大的叢集聯邦,TKE團隊在原生Prometheus的基礎上進行了大量探索與改進,研發出一套可擴充套件,高可用且相容原生配置的Prometheus叢集系統,理論上可支援無限的series數目和儲存容量,支援納管TKE叢集,EKS叢集以及自建K8s叢集的監控訴求。

本文從TKE的架構出發,逐步介紹了整個監控系統的演進過程,包括早期的方案和遇到的問題,社群方案的瓶頸,我們的改進原理等。

TKE架構簡介

為了讓讀者更好理解我們的場景,我們首先簡單介紹一下TKE的基礎架構。

TKE團隊是公有云界首家採用Kubernetes in Kubernetes進行叢集聯邦管理的Kubernetes運營團隊,其核心思想就是用一個Meta Cluster來託管其他叢集的apiserver,controller-manager,scheduler,監控套件等非業務元件,在Meta Cluster中的元件對使用者而言是隱藏的,如下圖所示。

img上圖Meta Cluster中的元件對於使用者而言都是隱藏的。支撐環境服務用於直接處理來至TKE控制檯的請求。

  • Meta Cluster用於管理叢集的控制皮膚元件,如apiserver等
  • Meta Cluster中還與一些隱藏的功能元件,例如監控元件
  • 支撐服務用於接收來至控制檯的請求,並連線到使用者叢集進行實際操作

早期的監控方案

需求

TKE早期監控方案不支援使用者新增業務相關的監控指標,只包括叢集運維關注的監控,主要希望監控的目標如下:

  • 每個使用者叢集的核心元件監控,如apiserver, scheduler, controller-manager等
  • 每個使用者叢集的基礎資源監控,如Pod狀態,Deployment負載,叢集總負載等
  • Meta Cluster中所有元件的監控,包含Cluster-monitor自身,一些Meta Cluster自身的addon元件等
  • 支撐環境元件的監控,如支援web server服務處理成功率,外部介面呼叫成功率等

架構

叢集級別

在上一節的TKE架構圖中,我們在Meta Cluster中看到每個叢集有一套Cluster-monitor元件,該元件就是單叢集級別的監控採集套件。Cluster-monitor包含了以Prometheus為核心的一系列元件,其基本功能就是採集每個使用者叢集的基礎監控資料,例如Pod負載,Deployment負載,Node CPU使用率等,採集到的資料將直接寫到雲監控團隊提供的Argus系統中儲存於告警。核心元件如下圖。

img

Barad:雲監控提供的多維監控系統,是雲上其他服務主要使用的監控系統,其相對成熟穩定,但是不靈活,指標和label都需要提前在系統上設定好。

Argus:雲監控團隊提供的多維業務監控系統,其特點是支援較為靈活的指標上報機制和強大的告警能力。這是TKE團隊主要使用的監控系統。

資料流:

  • Prometheus從kubelet採集container負載資訊,從kube-state-metrics採集叢集後設資料,比如Pod狀態,Node狀態等。資料在Prometheus進行聚合,產生固定的聚合指標,如container級別指標,Pod級別指標。採集到的資料寫往兩個地方,一部分資料寫往Argus系統,這部分資料用於支撐TKE控制檯上的監控皮膚及告警,另外一部分資料會寫往Barad系統,這是因為更早時期的TKE支援在Barad控制檯配置容器相關的告警,這份資料是為了使舊版告警能繼續使用。
  • 另外一條資料流是Barad-importer元件會從Barad(雲監控)處拉取節點相關的資料,比如CPU使用率,記憶體使用率等,並將資料匯入Argus系統,從而使得Argus也能進行節點相關的資料展示和告警。這裡沒有選擇社群主流的node-exporter來收集節點資料是因為node-exporter需要在使用者叢集內部署Daemonset,而我們希望整個監控資料採集系統對使用者是隱藏的。

這部分資料將通過控制檯輸出給使用者

img

地域級別

成功採集到了屬於每個使用者叢集的資料,但是,對於一些地域級別的監控,包括

  • Meta Cluster中的管理元件
  • Cluster-monitor元件自身
  • 整個地域級別的集合資訊,如總叢集數,叢集平均節點數,平均建立時間等資料

通過單個Cluster-monitor無法採集。需要構建更上一級的地域級別監控。

imgRegion Prometheus不僅拉取如meta cluster operator,meta cluster service controller等核心元件的資料外,還通過Prometheus聯邦介面拉取Cluster-monitor中的單叢集資料進行二次聚合,產生地域級別叢集的資料。地域級別資料直接存在本地,不寫往Argus,因為這部分資料需要對接Grafana,由團隊內部使用。img

全網級別

我們在單地域監控的基礎上又構建了一層全網級別的監控。用於監控

  • 支撐環境元件監控
  • 所有地域的Region Prometheus資料再聚合得到全網級別指標

img

全網資料也是給內部人員檢視。img

架構總覽

img

逐漸暴露出的問題

上述介紹的架構雖然解決了我們對於大規模叢集聯邦的基本監控訴求,但是依舊存在幾點不足。

Prometheus效能不足

原生Prometheus並不支援高可用,也不能做橫向擴縮容,當叢集規模較大時,單一Prometheus會出現效能瓶頸,無法正常採集資料,我們將在後續章節中給出Prometheus的壓測資料。

採集週期過長

目前採集週期是1m,我們希望能降低到15s。

原始資料儲存時長過短

由於雲監控所能提供的Argus系統的聚合能力有限,我們並沒有將Cluster-monitor採集到的資料直接輸出到Argus,而是將資料按預定的指標進行聚合,只傳送聚合過的資料,TKE控制檯在資料展示時只做時間上的聚合。而原始資料我們只儲存15分鐘。如果加長時間進行本地儲存,我們需要為每個Cluster-monitor部署雲硬碟,由於TKE存在部分空叢集(節點個數為0),這會產生資源浪費。

不支援跨叢集查詢

由於每個叢集的資料都是本地落盤,Region Prometheus由於效能有限的原因,只採集了部分聚合指標,使得無法進行跨叢集原始資料的聚合查詢,而這類查詢對於獲取單使用者多叢集的綜合資料是很有幫助的。

運維難度大

每一級Prometheus都是單獨管理的,缺乏全域性管理工具。

設計理想模型

怎樣的監控系統,可以同時解決上述幾個問題呢?我們先構思一個理想模型,稱之為Kvass

採集【高效能】

先看採集,我們採集側遇到的問題主要就是效能問題,即我們希望Kvass擁有以下能力

  • 高效能:有無限效能的採集器。
  • 原生:支援原生Prometheus主流的配置方式,包括Prometheus operator所支援的ServiceMonitor,PodMonitor等。

儲存【長期儲存】

儲存側,我們遇到的問題是儲存時長,以及資源利用率,我們希望Kvass的儲存擁有以下能力

  • 時長可能達到1年
  • 儲存資源利用率高

展示【全域性檢視】

展示側,我們遇到的問題是無法得到全域性檢視,所以,對於理想化的展示,我們希望Kvass的展示擁有以下能力

  • 能對接Grafana
  • 可以跨叢集聚合查詢
  • 支援原生Prometheus語句

告警【原生】

告警側,我們希望能支援原生Prometheus的告警配置。

運維【便捷】

我們希望Kvass沒有過於複雜的配置項,且系統擁有一套完整的運維工具,能使用Kubernetes原生方式進行管理。

整體模型

假設我們有了這麼一個模型,那麼我們的監控就可以變成下面這種架構,在這種模型下,我們擁有了單個地域下所有我們要的原始資料。

img

  • 去掉了Cluster-monitor中的Prometheus
  • 去掉了Region Prometheus

高效能採集

這一節介紹我們是如何實現理想模型中的高效能採集器的

Prometheus採集原理

各模組的關係

首先我們先了解一下Prometheus的採集原理,為後面修改Prometheus實現高可用分片打下基礎。下圖展示了Prometheus採集時各模組的關係

img

  • 配置管理模組:該模組負責接收配置更新動作,所有依賴配置檔案的模組,在初始化的時候都會向配置管理模組註冊配置更新監聽函式。
  • 服務發現模組:當job配置了服務發現時,target的個數是動態變化的,該模組負責做服務發現並生成target的變化資訊,並通知抓取模組。
  • 儲存模組:該模組有兩部分組成,一個是本地TSDB模組,一個是遠端儲存模組,該模組負責將target採集到的資料進行本地儲存,同時也管理遠端儲存的傳送過程。
  • 抓取模組:該模組是抓取的核心模組,其負責根據配置檔案以及服務發現模組給出的target資訊,生成多個job物件,每個job物件包含多個target scaper物件,每個target scraper物件都會啟動一個協程,週期性地對目標進行指標抓取,併傳送到儲存模組。

記憶體佔用

我們已經從Prometheus在實際中的表現知道Prometheus對記憶體使用會隨著採集目標的規模增長而增長,那Prometheus的記憶體到底用在哪了?

儲存模組

  • Prometheus的儲存不是將每個採集到的點都直接落盤,而是會先寫入wal檔案,採集一段時間後,將wal壓縮成塊。在這期間,儲存模組需要快取所有series的label資訊,並且在壓縮的時候,也需要產生較大的臨時記憶體消耗。
  • 遠端儲存的原理是通過監聽wal檔案的變化,將wal檔案中的點逐步傳送到遠端,在一個wal檔案被完全傳送完之前,遠端儲存管理器也會快取所有發現的series的label資訊,並且維護多個傳送佇列,這也是記憶體消耗比較大的地方。

抓取模組

  • 對於每個target,每個series只有第一次被儲存的時候才會把series的label資訊傳給儲存模組,儲存模組會返回一個id,target scraper就會將series進行hash並與id對應,後續抓取時,本series只需將id和值告訴儲存模組即可。hash與id的對應表也比較佔記憶體。

Prometheus效能壓測

壓測目的

分析了Prometheus的採集原理後,我們可以想確定以下幾個事情

  • target數目對Prometheus負載的關係
  • series規模和Prometheus負載的關係

target相關性

壓測方法

img

壓測資料

壓測結論
  • target個數對Prometheus的整體負載影響不大

series規模壓測

壓測方法

img

壓測資料

官方大規模叢集各個資源產生的series

以下表格中的資源個數為Kubenetes官方給出的大規模叢集應該包含的資源數 series個數通過統計cadvisor 和kube-state-metrics的指標得出

總計 5118w series。

壓測結論
  • 當series數目高於300w時,Prometheus記憶體將暴增
  • 按等比例換算,單Prometheus採集300節點以上的叢集時會記憶體會出現較大漲幅

實現可分片高可用Prometheus

有大量節點數目高於300的叢集,通過前面的壓測,單個Prometheus確實存在效能瓶頸。那我們根據前面的採集原理,嘗試修改Prometheus讓其支援橫向擴縮容。

設計原則

無論怎麼修改,我們希望保持以下特性

  • 擴縮容時不斷點
  • 負載均衡
  • 100%相容原來的配置檔案及採集能力

核心原理

再來回顧一下上邊的採集原理圖,看看我們應該在哪個地方進行修改。

img

從上圖中,我們發現,負載產生的源泉是target scraper,如果減少target scraper個數,就能減少整體採集到的series,從而降低負載。

假設我們有多個Prometheus共享相同的配置檔案,那麼理論上他們產生出來的target scraper應當是一模一樣的。如果多個Prometheus之間能夠相互協調,根據每個target scraper抓取的目標資料量情況,分配這些target scraper,就是實現負載的均攤。如下圖所示。

img

實現動態打散

  • 為了實現上述方案,我們需要一個獨立於所有Prometheus的負載協調器,協調器週期性(15s) 進行負載計算,該協調器負責收集所有target scraper的資訊,以及所有Prometheus的資訊,隨後通過分配演算法,為每個Prometheus分配一些target scraper,最後將結果同步給所有Prometheus。

  • 相應的,每個Prometheus需要新增一個本地協調模組,該模組負責和獨立的協調器進行對接,上報本Prometheus通過服務發現發現的所有target,以及上一次採集獲知的target的資料量,另外該模組也接受協調器下發的採集任務資訊,用於控制本Prometheus應該開啟哪些target scraper。

    img

targets分配演算法

當協調器收集到所有target資訊後,需要將target分配給所有Prometheus在分配時,我們保持以下原則

  • 優先分配到正在採集該target的Prometheus
  • 負載儘可能均衡

我們最終採用瞭如下演算法來分配target

  1. 規定target負載 = series * 每分鐘採集次數。
  2. 將各個Prometheus的target資訊進行彙總,得到全域性資訊,假設為global_targets,並全部標記為未分配。
  3. 計算每個Prometheus理論上平均應該負責的採集負載,設為avg_load。
  4. 針對每個Prometheus,嘗試將其正在採集的target分配給他,前提是該Prometheus負載不超過avg_load,並將成功分配的target在global_targets中標記為已分配。
  5. 遍歷global_targets,針對步驟3剩下的target, 有以下幾種情況

4.1 如果之前沒有采集過,則隨機分配個一個Prometheus。

4.2 如果原來採集的Prometheus負載未超過avg_load,則分配給他。

4.3 找到所有Prometheus中負載最低的例項,如果該例項目前的負載總和加上當前target的負載依舊小於avg_load,則分配他給,否則分配給原來的採集的Prometheus。

我們還可以用虛擬碼來表示這個演算法:

func load(t target) int {
  return  t.series * (60 / t.scrape_interval)
}
func reBalance(){
    global_targets := 所有Prometheus的targets資訊彙總
    avg_load = avg(global_targets)
    for 每個Prometheus {
      p := 當前Prometheus
      for 正在採集的target{
         t := 當前target
         if p.Load <= avg_load {
           p.addTarget(t)
           global_targets[t] = 已分配
           p.Load += load(t)
         }
      }
    }
    for global_targets{
       t := 當前target
       if t 已分配{
         continue
       }
       p := 正在採集t的Prometheus
       if p 不存在 {
         p = 隨機Prometheus
       }else{
          if p.Load > avg_load {
             exp := 負載最輕的Prometheus
             if exp.Load + load(t) <= avg_load{
               p = exp
             }
          }
       }
       p.addTarget(t)
       p.Load += load(t)
    }
}

targets交接

當一個Prometheus上的target抓取任務被分配到另外一個Prometheus時,需要增加一種平滑轉移機制,確保轉移過程中不掉點。這裡我們容忍重複點,因為我們將在後面將資料去重。

target交接的實現非常簡單,由於各個Prometheus的target更新幾乎是同時發生的,所以只需要讓第一個Prometheus的發現抓取任務被轉移後,延遲2個抓取週期結束任務即可。

擴容

協調器會在每個協調週期計算所有Prometheus的負載,確保平均負載不高於一個閾值,否則就會增加Prometheus個數,在下個協調週期採用上邊介紹的targets交接方法將一部分targets分配給它。

縮容

考慮到每個Prometheus都有本地資料,縮容操作並不能直接將多餘的Prometheus刪除。我們採用了以下方法進行縮容

img

  • 將多餘的Prometheus標記為閒置,並記錄當前時間。
  • 閒置的Prometheus上的target會全部被轉移,並且不再參與後續任務分配。
  • 當閒置Prometheus所有資料已上報遠端(後續將介紹),將例項刪除。
  • 特別的,如果在閒置過程中,出現了擴容操作,則將閒置最久的例項重新取消閒置,繼續參與工作。

高可用

在上述介紹的方案中,當某個Prometheus的服務不可用時,協調器會第一時間把target轉移到其他Prometheus上繼續採集,在協調週期很短(5s)的情況下,出現斷點的機率其實是非常低的。但是如果需要更高的可用性,更好的方法是進行資料冗餘,即每個targets都會被分配給多個Prometheus例項,從而達到高可用的效果。

關於儲存的問題

到目前為止,我們雖然將Prometheus的採集功能成功分片化,但是,各個Prometheus採集到的資料是分散的,我們需要一個統一的儲存機制,將各個Prometheus採集到的資料進行整合。

img

統一儲存

在上一節最後,我們引出,我們需要一個統一的儲存來將分片化的Prometheus資料進行儲存。業界在這方面有不少優秀的開源專案,我們選取了知名度最高的兩個專案,從架構,接入方式,社群活躍度,效能等各方面做了調研。

Thanos vs Cortex

整體比較

Thanos簡介

Thanos是社群十分流行的Prometheus高可用解決方案,其設計如圖所示

img

從採集側看,Thanos,利用Prometheus邊上的Thanos sidecar,將Prometheus落在本地的資料盤上傳至物件儲存中進行遠端儲存,這裡的Prometheus可以有多個,各自上報各自的資料。

查詢時,優先從各Prometheus處查詢資料,如果沒查到,則從物件儲存中查詢歷史資料,Thanos會將查詢到的資料進行去重。Thanos的設計十分符合我們前面的採集方案提到的統一儲存。接入後如圖所示。

img

Cortex簡介

Cortex是Weavework公司開源的Prometheus相容的TSDB,其原生支援多租戶,且官方宣傳其具有非常強大的效能,能儲存高達2500萬級別的series,其架構如圖所示

img

從架構圖不難發現,Cortex比Thanos要複雜得多,外部依賴也多,估計整體運維難度的比較大。Cortex不再使用Prometheus自帶的儲存,而是讓Prometheus通過remote write將資料全部寫到Cortex系統進行統一的儲存。Cortex通過可分片接收器來接收資料,隨後將資料塊儲存到物件儲存中,而將資料索引儲存到Memcache中。

img

  • 從架構上來看,Cortex似乎更加複雜,運維難度也高
  • 從接入方式看,Thanos對原來的Prometheus配置檔案沒有改動,屬於無侵入方式,而Cortex需要在配置檔案中加入remote write,另外目前版本的Prometheus無法通過引數關閉本地儲存,所以即使只使用remote write儲存到Cortex, Prometheus本地還是會有資料。

社群現狀

  • 從社群活躍度上看,Thanos表現更加優秀

效能壓測

上文從架構角度對兩個專案進行了一番對比,但是實際使用中,他兩表現如何呢,我們進行效能壓測:

壓測方式

img

img

我們保持兩個系統series總量總是擁有相同的變化,從查詢效能,系統負載等多方面,去評估他們之前的優劣

壓測結果
  • 穩定性:不同資料規模下,元件是否正常工作

    img從資料上看 Thanos 更加穩定一些。

  • 查詢效能:不同資料規模下,查詢的效率

    img從資料上看,Thanos的查詢效率更高。

  • 未啟用Ruler資源消耗:沒有啟動Ruler情況下,各元件的負載

    img就採集和查詢而言,Thanos的資源消耗要比Cortex低很多。

在整個壓測過程中,我們發現Cortex的效能遠沒有官方宣稱的好,當然也可能是我們的調參不合理,但是這也反應出Cortex的使用難度極高,運維十分複雜(上百的引數),整體使用體驗非常差。反觀Thanos整體表現和官方介紹的較為相近,運維難度也比較低,系統較好把控。

選型

從前面的分析對比來看,Thanos無論是從效能還是從社群活躍度,還是從接入方式上看,較Cortex都有比較大的優勢。所以我們選擇採用Thanos方案來作為統一儲存。

Kvass系統整體實現

到目前為止,我們通過實現可分片Prometheus加Thanos,實現了一套與原生Prometheus配置100%相容的高效能可伸縮的Kvass監控系統。元件關係如圖:

img

接入多個k8s叢集

上圖我們只畫了一套採集端(即多個共享同一份配置檔案的Prometheus,以及他們的協調器),實際上系統支援多個採集端,即一個系統可支援多個Kubernetes叢集的監控,從而得到多叢集全域性資料檢視。

img

Kvass-operator

回顧舊版本監控在運維方法的不足,我們希望我們的新監控系統有用完善的管理工具,且能用Kubernetes的方式進行管理。我們決定使用operator模式進行管理,Kvass-operator就是整個系統的管理中心,它包含如下三種自定義資源

  • Thanos:定義了Thanos相關元件的配置及狀態,全域性唯一。
  • Prometheus: 每個Prometheus定義了一個Prometheus叢集的配置,例如其關聯的Kubernetes叢集基礎資訊,協調演算法的一些閾值等
  • Notification: 定義了告警渠道,Kvass-operator負責根據其定義去更新雲上告警配置

img

Prometheus-operator及叢集內採集配置管理

由於Prometheus配置檔案管理比較複雜,CoreOS開源了一個Prometheus-operator專案,用於管理Prometheus及其配置檔案,它支援通過定義ServiceMonitor,PodMonitor這兩種相比於原生配置檔案具有更優可讀性的自定義型別,協助使用者生成最終的採集配置檔案。

我們希望實現一種虛擬Prometheus機制,即每個user cluster能夠在自己叢集內部管理其所對應的Prometheus採集配置檔案,進行ServiceMonitor和PodMonitor的增刪改查,也就是說,Prometheus就好像部署在自己叢集裡面一樣。

為了達到這種效果,我們引入並修改了Prometheus-operator。新版Prometheus-operator會連線上使用者叢集進行ServiceMonitor和PodMonitor的監聽,並將配置檔案生成在採集側。

另外我們將協調器和Prometheus-operator放在了一起。

img

基於Kvass的TKE監控方案

通過一步一步改進,我們最終擁有了一套支援多叢集採集,並支援擴縮容的高可用監控系統,我們用其替換原來監控方案中的Cluster-monitor + Region Prometheus。實現了文章之初的訴求。

最初版本

img

新方案

我們上邊介紹的方案,已經可以整體替換早期方案中的Region Prometheus及Cluster-monitor。現在我們再加入一套Thanos,用於將全網資料進行整合。

img

相比於舊版本監控的指標預定義,新版本監控系統由於Prometheus是可擴縮容的,所以是可以支援使用者上報自定義資料的。

總結

專案思路

Kvass的設計不是天馬行空拍腦袋決定的,而是在當前場景下一些問題的解決思路所組成的產物。

客觀看待舊版本

雖然我們整篇文章就是在介紹一種用於取代舊版本監控的新系統,但是這並不意味著我們覺得舊版本監控設計得差勁,只是隨著業務的發展,舊版本監控系統所面臨的場景相較於設計之初有了較大變化,當時合理的一些決策,在當前場景下變得不再適用而已。與其說是替換,不如稱為為演進。

先設計模型

相比於直接開始系統落地,我們更傾向於先設計系統模型,以及確定設計原則,系統模型用於理清我們到底是要解決什麼問題,我們的系統應該由哪幾個核心模組元件,每個模組最核心要解決的問題是什麼,有了系統模型,就等於有了設計藍圖和思路。

確定設計原則

在系統設計過程中,我們尤為重視設計的原則,即無論我們採用什麼形式,什麼方案,哪些特性是新系統必須要有的,對於Kvass而言,原生相容是我們首要的設計原則,我們希望無論我們怎麼設計,對使用者叢集而言,就是個Prometheus。

落地

在整體研發過程中,我們也踩了不少坑。Cortex的架構設計相比於thaos而言,採用了索引與資料分離的方式,設計上確實更加合理,理論上對於大規模資料,讀取效能會更優,並且Cortex由於原生就支援多租戶,實現了大量引數用於限制使用者的查詢規模,這點是Thanos有待加強的地方。我們最初的方案也嘗試採用Cortex來作為統一儲存,但是在實際使用時,發現Cortex存在記憶體佔用高,調參複雜等問題,而Thanos相比而言,效能較為穩定,也更加切近我們的場景,我們再結合壓測報告,選擇將儲存切換為Thanos。

產品化

由於Kvass系統所以解決的問題具有一定普適性,TKE決定將其作為一個子產品對使用者暴露,為使用者提供基於Kvass的雲原生系統,該產品目前已開放內測。

img

【騰訊雲原生】雲說新品、雲研新術、雲遊新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多幹貨!!

相關文章