引言
TKE團隊負責公有云,私有云場景下近萬個集群,數百萬核節點的運維管理作業,為了監控規模如此龐大的集群聯邦,TKE團隊在原生Prometheus的基礎上進行了大量探索與改進,研發出一套可擴展,高可用且兼容原生配置的Prometheus集群系統,理論上可支持無限的series數目和存盤容量,支持納管TKE集群,EKS集群以及自建K8s集群的監控訴求,
本文從TKE的架構出發,逐步介紹了整個監控系統的演程序序,包括早期的方案和遇到的問題,社區方案的瓶頸,我們的改進原理等,
TKE架構簡介
為了讓讀者更好理解我們的場景,我們首先簡單介紹一下TKE的基礎架構,
TKE團隊是公有云界首家采用Kubernetes in Kubernetes進行集群聯邦管理的Kubernetes運營團隊,其核心思想就是用一個Meta Cluster來托管其他集群的apiserver,controller-manager,scheduler,監控套件等非業務組件,在Meta Cluster中的組件對用戶而言是隱藏的,如下圖所示,
上圖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系統中存盤于告警,核心組件如下圖,

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,而我們希望整個監控資料采集系統對用戶是隱藏的,
這部分資料將通過控制臺輸出給用戶

地域級別
成功采集到了屬于每個用戶集群的資料,但是,對于一些地域級別的監控,包括
- Meta Cluster中的管理組件
- Cluster-monitor組件自身
- 整個地域級別的集合資訊,如總集群數,集群平均節點數,平均創建時間等資料
通過單個Cluster-monitor無法采集,需要構建更上一級的地域級別監控,
Region Prometheus不僅拉取如meta cluster operator,meta cluster service controller等核心組件的資料外,還通過Prometheus聯邦介面拉取Cluster-monitor中的單集群資料進行二次聚合,產生地域級別集群的資料,地域級別資料直接存在本地,不寫往Argus,因為這部分資料需要對接Grafana,由團隊內部使用,
全網級別
我們在單地域監控的基礎上又構建了一層全網級別的監控,用于監控
- 支撐環境組件監控
- 所有地域的Region Prometheus資料再聚合得到全網級別指標

全網資料也是給內部人員查看,
架構總覽

逐漸暴露出的問題
上述介紹的架構雖然解決了我們對于大規模集群聯邦的基本監控訴求,但是依舊存在幾點不足,
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原生方式進行管理,
整體模型
假設我們有了這么一個模型,那么我們的監控就可以變成下面這種架構,在這種模型下,我們擁有了單個地域下所有我們要的原始資料,

- 去掉了Cluster-monitor中的Prometheus
- 去掉了Region Prometheus
高性能采集
這一節介紹我們是如何實作理想模型中的高性能采集器的
Prometheus采集原理
各模塊的關系
首先我們先了解一下Prometheus的采集原理,為后面修改Prometheus實作高可用分片打下基礎,下圖展示了Prometheus采集時各模塊的關系

- 配置管理模塊:該模塊負責接收配置更新動作,所有依賴組態檔的模塊,在初始化的時候都會向配置管理模塊注冊配置更新監聽函式,
- 服務發現模塊:當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相關性
壓測方法

壓測資料

壓測結論
- target個數對Prometheus的整體負載影響不大
series規模壓測
壓測方法

壓測資料

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

總計 5118w series,
壓測結論
- 當series數目高于300w時,Prometheus記憶體將暴增
- 按等比例換算,單Prometheus采集300節點以上的集群時會記憶體會出現較大漲幅
實作可分片高可用Prometheus
有大量節點數目高于300的集群,通過前面的壓測,單個Prometheus確實存在性能瓶頸,那我們根據前面的采集原理,嘗試修改Prometheus讓其支持橫向擴縮容,
設計原則
無論怎么修改,我們希望保持以下特性
- 擴縮容時不斷點
- 負載均衡
- 100%兼容原來的組態檔及采集能力
核心原理
再來回顧一下上邊的采集原理圖,看看我們應該在哪個地方進行修改,

從上圖中,我們發現,負載產生的源泉是target scraper,如果減少target scraper個數,就能減少整體采集到的series,從而降低負載,
假設我們有多個Prometheus共享相同的組態檔,那么理論上他們產生出來的target scraper應當是一模一樣的,如果多個Prometheus之間能夠相互協調,根據每個target scraper抓取的目標資料量情況,分配這些target scraper,就是實作負載的均攤,如下圖所示,

實作動態打散
-
為了實作上述方案,我們需要一個獨立于所有Prometheus的負載協調器,協調器周期性(15s) 進行負載計算,該協調器負責收集所有target scraper的資訊,以及所有Prometheus的資訊,隨后通過分配演算法,為每個Prometheus分配一些target scraper,最后將結果同步給所有Prometheus,
-
相應的,每個Prometheus需要添加一個本地協調模塊,該模塊負責和獨立的協調器進行對接,上報本Prometheus通過服務發現發現的所有target,以及上一次采集獲知的target的資料量,另外該模塊也接受協調器下發的采集任務資訊,用于控制本Prometheus應該開啟哪些target scraper,

targets分配演算法
當協調器收集到所有target資訊后,需要將target分配給所有Prometheus在分配時,我們保持以下原則
- 優先分配到正在采集該target的Prometheus
- 負載盡可能均衡
我們最終采用了如下演算法來分配target
- 規定target負載 = series * 每分鐘采集次數,
- 將各個Prometheus的target資訊進行匯總,得到全域資訊,假設為global_targets,并全部標記為未分配,
- 計算每個Prometheus理論上平均應該負責的采集負載,設為avg_load,
- 針對每個Prometheus,嘗試將其正在采集的target分配給他,前提是該Prometheus負載不超過avg_load,并將成功分配的target在global_targets中標記為已分配,
- 遍歷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洗掉,我們采用了以下方法進行縮容

- 將多余的Prometheus標記為閑置,并記錄當前時間,
- 閑置的Prometheus上的target會全部被轉移,并且不再參與后續任務分配,
- 當閑置Prometheus所有資料已上報遠端(后續將介紹),將實體洗掉,
- 特別的,如果在閑置程序中,出現了擴容操作,則將閑置最久的實體重新取消閑置,繼續參與作業,
高可用
在上述介紹的方案中,當某個Prometheus的服務不可用時,協調器會第一時間把target轉移到其他Prometheus上繼續采集,在協調周期很短(5s)的情況下,出現斷點的幾率其實是非常低的,但是如果需要更高的可用性,更好的方法是進行資料冗余,即每個targets都會被分配給多個Prometheus實體,從而達到高可用的效果,
關于存盤的問題
到目前為止,我們雖然將Prometheus的采集功能成功分片化,但是,各個Prometheus采集到的資料是分散的,我們需要一個統一的存盤機制,將各個Prometheus采集到的資料進行整合,

統一存盤
在上一節最后,我們引出,我們需要一個統一的存盤來將分片化的Prometheus資料進行存盤,業界在這方面有不少優秀的開源專案,我們選取了知名度最高的兩個專案,從架構,接入方式,社區活躍度,性能等各方面做了調研,
Thanos vs Cortex
整體比較
Thanos簡介
Thanos是社區十分流行的Prometheus高可用解決方案,其設計如圖所示

從采集側看,Thanos,利用Prometheus邊上的Thanos sidecar,將Prometheus落在本地的資料盤上傳至物件存盤中進行遠程存盤,這里的Prometheus可以有多個,各自上報各自的資料,
查詢時,優先從各Prometheus處查詢資料,如果沒查到,則從物件存盤中查詢歷史資料,Thanos會將查詢到的資料進行去重,Thanos的設計十分符合我們前面的采集方案提到的統一存盤,接入后如圖所示,

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

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

- 從架構上來看,Cortex似乎更加復雜,運維難度也高
- 從接入方式看,Thanos對原來的Prometheus組態檔沒有改動,屬于無侵入方式,而Cortex需要在組態檔中加入remote write,另外目前版本的Prometheus無法通過引數關閉本地存盤,所以即使只使用remote write存盤到Cortex, Prometheus本地還是會有資料,
社區現狀

- 從社區活躍度上看,Thanos表現更加優秀
性能壓測
上文從架構角度對兩個專案進行了一番對比,但是實際使用中,他兩表現如何呢,我們進行性能壓測:
壓測方式


我們保持兩個系統series總量總是擁有相同的變化,從查詢性能,系統負載等多方面,去評估他們之前的優劣
壓測結果
-
穩定性:不同資料規模下,組件是否正常作業
從資料上看 Thanos 更加穩定一些, -
查詢性能:不同資料規模下,查詢的效率
從資料上看,Thanos的查詢效率更高, -
未啟用Ruler資源消耗:沒有啟動Ruler情況下,各組件的負載
就采集和查詢而言,Thanos的資源消耗要比Cortex低很多,
在整個壓測程序中,我們發現Cortex的性能遠沒有官方宣稱的好,當然也可能是我們的調參不合理,但是這也反應出Cortex的使用難度極高,運維十分復雜(上百的引數),整體使用體驗非常差,反觀Thanos整體表現和官方介紹的較為相近,運維難度也比較低,系統較好把控,
選型
從前面的分析對比來看,Thanos無論是從性能還是從社區活躍度,還是從接入方式上看,較Cortex都有比較大的優勢,所以我們選擇采用Thanos方案來作為統一存盤,
Kvass系統整體實作
到目前為止,我們通過實作可分片Prometheus加Thanos,實作了一套與原生Prometheus配置100%兼容的高性能可伸縮的Kvass監控系統,組件關系如圖:

接入多個k8s集群
上圖我們只畫了一套采集端(即多個共享同一份組態檔的Prometheus,以及他們的協調器),實際上系統支持多個采集端,即一個系統可支持多個Kubernetes集群的監控,從而得到多集群全域資料視圖,

Kvass-operator
回顧舊版本監控在運維方法的不足,我們希望我們的新監控系統有用完善的管理工具,且能用Kubernetes的方式進行管理,我們決定使用operator模式進行管理,Kvass-operator就是整個系統的管理中心,它包含如下三種自定義資源
- Thanos:定義了Thanos相關組件的配置及狀態,全域唯一,
- Prometheus: 每個Prometheus定義了一個Prometheus集群的配置,例如其關聯的Kubernetes集群基礎資訊,協調演算法的一些閾值等
- Notification: 定義了告警渠道,Kvass-operator負責根據其定義去更新云上告警配置

Prometheus-operator及集群內采集配置管理
由于Prometheus組態檔管理比較復雜,CoreOS開源了一個Prometheus-operator專案,用于管理Prometheus及其組態檔,它支持通過定義ServiceMonitor,PodMonitor這兩種相比于原生組態檔具有更優可讀性的自定義型別,協助用戶生成最終的采集組態檔,
我們希望實作一種虛擬Prometheus機制,即每個user cluster能夠在自己集群內部管理其所對應的Prometheus采集組態檔,進行ServiceMonitor和PodMonitor的增刪改查,也就是說,Prometheus就好像部署在自己集群里面一樣,
為了達到這種效果,我們引入并修改了Prometheus-operator,新版Prometheus-operator會連接上用戶集群進行ServiceMonitor和PodMonitor的監聽,并將組態檔生成在采集側,
另外我們將協調器和Prometheus-operator放在了一起,

基于Kvass的TKE監控方案
通過一步一步改進,我們最終擁有了一套支持多集群采集,并支持擴縮容的高可用監控系統,我們用其替換原來監控方案中的Cluster-monitor + Region Prometheus,實作了文章之初的訴求,
最初版本

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

相比于舊版本監控的指標預定義,新版本監控系統由于Prometheus是可擴縮容的,所以是可以支持用戶上報自定義資料的,
總結
專案思路
Kvass的設計不是天馬行空拍腦袋決定的,而是在當前場景下一些問題的解決思路所組成的產物,
客觀看待舊版本
雖然我們整篇文章就是在介紹一種用于取代舊版本監控的新系統,但是這并不意味著我們覺得舊版本監控設計得差勁,只是隨著業務的發展,舊版本監控系統所面臨的場景相較于設計之初有了較大變化,當時合理的一些決策,在當前場景下變得不再適用而已,與其說是替換,不如稱為為演進,
先設計模型
相比于直接開始系統落地,我們更傾向于先設計系統模型,以及確定設計原則,系統模型用于理清我們到底是要解決什么問題,我們的系統應該由哪幾個核心模塊組件,每個模塊最核心要解決的問題是什么,有了系統模型,就等于有了設計藍圖和思路,
確定設計原則
在系統設計程序中,我們尤為重視設計的原則,即無論我們采用什么形式,什么方案,哪些特性是新系統必須要有的,對于Kvass而言,原生兼容是我們首要的設計原則,我們希望無論我們怎么設計,對用戶集群而言,就是個Prometheus,
落地
在整體研發程序中,我們也踩了不少坑,Cortex的架構設計相比于thaos而言,采用了索引與資料分離的方式,設計上確實更加合理,理論上對于大規模資料,讀取性能會更優,并且Cortex由于原生就支持多租戶,實作了大量引數用于限制用戶的查詢規模,這點是Thanos有待加強的地方,我們最初的方案也嘗試采用Cortex來作為統一存盤,但是在實際使用時,發現Cortex存在記憶體占用高,調參復雜等問題,而Thanos相比而言,性能較為穩定,也更加切近我們的場景,我們再結合壓測報告,選擇將存盤切換為Thanos,
產品化
由于Kvass系統所以解決的問題具有一定普適性,TKE決定將其作為一個子產品對用戶暴露,為用戶提供基于Kvass的云原生系統,該產品目前已開放內測,

【騰訊云原生】云說新品、云研新術、云游新活、云賞資訊,掃碼關注同名公眾號,及時獲取更多干貨!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/693.html
標籤:其他

