如何擴展單個Prometheus實現近萬Kubernetes集羣監控?

引言

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

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

TKE架構簡介

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

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

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團隊主要使用的監控系統。運維

數據流:ide

  • 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

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