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


引言

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM