vivo 容器集群監控系統優化之道
一、背景介紹
隨著vivo業務遷移到容器平臺,vivo云原生監控體系面臨著指標量快速上漲帶來的一系列挑戰,本文將分享vivo 容器化項目中容器監控遇到的問題以及我們的解決和優化方法。
二、監控架構
首先對vivo容器監控架構進行一個簡單的介紹。
- 【架構高可用】:集群維度的雙副本 Prometheus 采集底層exporter數據,adapter多實例自動選主實現容災。
- 【數據持久化】:通過remoteWrite將數據存儲到后端的VictoriaMetrics中進行持久化存儲,Grafana使用VictoriaMetrics做為數據源展示和告警。
- 【監控統一化】:通過remoteWrite將數據交由kafka-adapter轉發到Kafka,由公司基礎監控等服務通過消費Kafka中的數據來進行展示和告警。
原生Prometheus沒有提供高可用的標準方案,我們通過自研 Adapter “分組選舉”方式實現去重,即每個 Prometheus 副本對應一組 Adapter,兩組 Adapter 之間會進行選主,只有Leader組的 Adapter才會轉發數據。通過這種方式既實現了去重,也實現了Prometheus雙副本高可用。
三、問題現象
過去幾年來,vivo容器化服務快速增長,監控流量上漲數倍,我們主要遇到如下三個問題:監控組件負載快速升高、容器監控數據斷點和數據存儲組件負載陡增。
3.1 監控組件負載快速升高
容器化每次部署IP都會變化的特性,導致容器監控指標量相比物理機和虛擬機要高出好幾個數量級。同時由于集群規模的不斷增加以及業務的快速增長,導致監控 Prometheus、VictoriaMetrics 負載快速增高,給我們容器監控帶來極大的挑戰。監控總時間序列可以精簡為以下的表達式,即與 Pod數量、Pod指標量、指標Label維度數量呈線性關系:
TotalSeries = PodNum * PerPodMetrics * PerLabelCount
而隨著集群規模的不斷增加以及容器數量的不斷增多,監控序列就會快速增長,同時監控組件負載快速升高,會對容器監控系統的穩定性產生影響。
3.2 監控丟點現象
vivo容器層面(業務)的監控數據則通過自研Adapter轉發給Kafka,進而存儲到公司基礎監控做業務監控展示和告警配置,同時也存儲一份到Druid做更多維度的統計報表。我們在推送監控數據的時候發現,Pod維度的指標難以保證發送頻率,即配置指標采集頻率為 10s,但是在推送的數據中頻率無法做到10s,且會有波動。下圖為 采集頻率設置 10s,查詢1分鐘的指標數據。
一分鐘內某一容器的 container_cpu_user_seconds_total 指標的值:
可以看到只取到了4個值,與期望的 6個值不相符,即發生了“掉點”的情況,監控面板按指定頻率展示數據會有頻繁掉0現象。
3.3 數據存儲組件負載陡增
vivo容器監控使用 VictoriaMetrics的v1.59.1-cluster版本做為后端時序數據庫來持久化存儲監控數據。在使用過程中發現 VictoriaMetrics的負載有不定期增高的情況,而后端數據庫的延遲會導致監控查詢告警功能的異常影響用戶體驗。
四、解法
4.1 監控組件負載快速升高
我們使用 Prometheus-Operator 管理 Prometheus,因為優化方案中有 Prometheus-Operator 相關的名詞,故為了下面的理解,先對 Prometheus-Operator 做一個介紹:
上圖是Prometheus-Operator官方提供的架構圖,下面來介紹下圖中各組件:
- Operator: Operator是最核心的部分,作為一個控制器,他會去創建Prometheus、ServiceMonitor資源對象,然后會一直監控并維持這些資源對象的狀態。
- Prometheus:這種資源對象就是作為Prometheus Server存在, Operator 根據自定義資源 Prometheus 類型中定義的內容而部署的 Prometheus Server。Prometheus 也可以通過 labelSelector 去匹配多個ServiceMonitor。
- ServiceMonitor:ServiceMonitor就是exporter的各種抽象,exporter是用來提供專門提供metrics數據接口的服務。Prometheus就是通過ServiceMonitor提供的metrics數據接口去 pull 數據的。該資源通過 Labels 來選取對應的 Service Endpoint,讓 Prometheus Server 通過選取的 Service 來獲取 Metrics 信息。一個 ServiceMonitor 可以通過 labelSelector 的方式去匹配一類 Service。
- Service:Service是Kubernetes內建資源用于把一組擁有相同功能的Pod封裝起來,并提供一個統一的入口來暴露這組Pod的服務,從而為客戶端訪問提供了一個穩定的訪問地址,屏蔽了底層Pod的變化和故障。Service可以結合Kubernetes中的其他組件實現負載均衡、服務發現、集群內部通信等功能。
我們重點關注 ServiceMonitor,因為ServiceMonitor為我們提供了指定 target 采集的配置,例如采集頻率,target內指標過濾,指標中label重命名等等操作。
對于監控組件負載快速升高問題的解決,我們主要從兩個方面著手,分別是指標治理以及性能優化。
圖片
4.1.1 指標治理
1、過濾未使用指標
第一個工作是過濾無用指標,Prometheus 會去從 Target 中去獲取指標數據。每個 Target 下會有多個 endponit。每一個endpoint 又會提供幾十上百個指標,而每個指標下的數據量也很大。但在生產環境中我們實際上用到的指標可能只有幾十個,Prometheus采集過來我們又沒有用到的指標就會浪費資源并降低監控系統的穩定性。因此我們需要對Prometheus 采集到的指標進行一定程度的過濾,從而減少資源的占用。
通過Prometheus提供的
scrape_samples_scraped指標對采集的 target進行分析找到采集樣本量大的Target。
我們進行指標過濾主要就是關注這些數據量大的 target,在進行指標過濾之前需要收集 Prometheus所采集的所有指標數據以及當前監控告警面板以及相關依賴服務所使用到的指標。再根據采集的指標和正在使用的指標進行正則表達式的書寫。最終在對應 target的 ServiceMonitor將正則表達式寫入。Prometheus則會過濾掉我們不需要的指標。下面就是過濾 cAdvisor這個 target 的 container_threads 開頭的指標的示例。
# 過濾 container_threads 開頭的指標
- action: drop
regex: container_threads(.*)
sourceLabels:
- __name__
完成指標精簡后,監控單次采集樣本量從 1000萬降低到 250萬。Prometheus 的CPU 使用量降低 70% ,內存 使用量降低 55%。
2、過濾低優先級 pod 相關指標
對精簡后的指標進行分析,發現 Pod維度的監控指標占比為70%,且有相當比例的 Pod 是集群組件的 Daemonset的Pod。而Daemonset的Pod是隨著集群規模的增加而增加的。
而對于大部分的集群組件Daemonset,因為設置了資源上限,我們不需要關注其資源消耗情況,只需要關注是否存活和正常提供服務即可。故可以對這部分 Pod 的指標進行一個精簡,不收集這些 Pod的 memory、cpu 等容器監控指標。
一個 Daemonset 提供的 Pod 的 Namespace 是相同的,且Pod名稱前綴相同。
# 名稱為cadvisor的 daemonset 提供的 pod
cadvisor-xxxx1 1/1 Running
cadvisor-xxxx2 1/1 Running
且 cAdvisor 提供的指標中包含了 namespace 和 pod 的 label。
container_memory_cache{cnotallow="POD", namespace="monitoring", pod="kube-state-metrics-xxxx-xxxx", service="cadvisor"}
所以我們通過在 ServiceMonitor 上面組合指標的 pod 和 namespace,并與指定的規則進行匹配丟棄掉我們不需要的 daemonset pod 的序列。
# 過濾掉 monitoring namespace 下面 telegraf 這個daemosnet提供的 pod 的相關指標
- action: drop
regex: monitoring@telegraf(.*)
separator: '@'
sourceLabels:
- namespace
- pod
在對集群中部分ds Pod 的指標進行過濾后。
對 cAdvisor 的單次采集數據量下降 70%。效果十分明顯。
4.1.2 性能優化
1、均衡 Prometheus 負載
vivo 容器監控架構中最核心的組件就是 Prometheus了,而 Prometheus 也是日常出現問題最多的一個組件,因為 Prometheus 不僅是需要采集數據,還需要將數據通過remote_write的方式推送的VictoriaMetrics 和 Kafka中。
圖片
將監控 target 進行分類交由不同的組 Prometheus 采集,且每類 Prometheus 為雙副本模式。隨著集群規模的增加,發現當前模式的不合理之處,即因為Pod維度監控數據量級十分高,導致container 類型 Prometheus 負載遠遠高于其他類型的 Prometheus。在高負載的情況下面會發生重啟,remote_write 異常等情況。
我們 Prometheus組件架構為按類型采集監控指標。其中 Container類型的 Prometheus采集的指標數量遠遠大于 Component、Host、Resource類型。故需要手動平衡 集群中 Prometheus的負載 將集群的 container類型 Prometheus上面kubelet 和 kube-state-metrics 轉移到 resource-Prometheus 上面 降低 container-Prometheus負載。(與此同時resource-Prometheus也會發送到 kafka-adapter)。且在負載低的Prometheus上 監控跳轉面板用到的監控指標(核心指標)進行重復采集,盡可能防止數據丟失。降低了container-Prometheus 40%的負,極大的減少了Prometheus異常情況的發生。
2、減少Prometheus存儲數據時間
我們的第2個修改點在 Prometheus的數據存儲時間上,Prometheus的默認存儲時間為2周,后續測試發現 Prometheus的存儲數據時間對內存的影響很大,且現在監控數據的持久化存儲都放在 VictoriaMetrics 上面,Prometheus存儲的數據主要用于排查問題,不需要承擔持久化存儲的任務。故我們修改Prometheus采集的數據本地存儲時間從7天改為2天。Prometheus 內存消耗降低 40%。
4.2 監控丟點現象
4.2.1 問題定位
最初我們認為是 Prometheus 在remote_write 的過程中發生了丟點的情況,但是通過在社區查詢和配置問題Prometheus 遠程寫相關的監控指標發現Prometheus并沒有在遠程寫的時候丟棄數據,且發現在推送數據的過程中只有kubelet 內置的 cAdvisor提供的數據有"丟點"的情況。 故我們開始研究指標提供端組件 cAdvisor,發現cAdvisor”丟點”問題的原因在于 cAdvisor 組件有自己的刷新頻率 和 時間戳。cAdvisor 會去 cgroup 中讀取數據并存儲到內存中供外部使用。Kubelet的cAdvisor 的刷新數據頻率達不到 10s,并且會根據刷新時間放到指標中。 而 Prometheus 按 10s 采集頻率去采集數據時,底層的 cAdvisor 如果還沒有去刷新數據,內存中則還是上次的數據。而cAdvisor 在0.31版本及之后的版本中添加了時間戳支持,即 cadvisor 提供的數據會帶上自己的時間戳。當 Prometheus 去采集 cadviosr數據時會以 cAdvisor提供的時間戳為準。故當 Prometheus 按照ServiceMonitor 設置的采集頻率10s去采集cAdvisor 提供的數據時,如果在此期間 cAdvisor 沒有進行數據更新,則Prometheus會采集到與上次采集時間戳和值相同的情況,Prometheus 就只會記錄一條數據。這就是cAdvisor “丟點”的本質。cAdvisor的刷新頻率由 housekeeping相關參數 和 抖動 機制確定。
kubelet 內置 cAdvisor設置的參數:
// Kubelet 內置 cadvisor 默認參數
// cadvisor housekeeping 的間隔,刷新數據的間隔
const defaultHousekeepingInterval = 10 * time.Second
// cadvisor 是否開啟動態 housekeeping
const allowDynamicHousekeeping = true
/*
cadvisor housekeeping 的最大間隔,allow_dynamic_housekeeping=true的時候, 會判斷容器活躍程度動態的調整 HousekeepingInterval, 當發現一段時間為容器狀態為發生改變會將 housekeeping 的間隔 設置為maxHousekeepingInterval 。
*/
const maxHousekeepingInterval = 15 * time.Second
4.2.2 解決方案
根據上面的分析,定位到無法保證指標頻率的根本原因在于 cAdvisor 的默認啟動參數,而目前我們采集的cAdvisor是內置于 kubelet 中的,如果要修改參數的話需要改動 kubelet。故綜合考慮集群穩定性和維護成本等因素,我們決定以 daemonset 的方式部署一套cAdvisor,并根據需求設置 housekeeping 相關的參數來保證 cAdvisor 刷新數據的頻率,并設置 Prometheus 采集 cAdvisor 數據的時候忽略 cAdvisor 自帶的時間戳,即通過單獨部署的 cAdvisor 提供Pod的監控數據并保證監控數據的頻率。
我們的工作主要在部署 cAdvisor 和 修改對應的 ServiceMonitor。
1、部署 cAdvisor:主要是確定cAdvisor啟動參數, 主要操作為禁用dynamic_housekeeping 和 設置 housekeeping_interval 為 1s,來保證 cAdvisor 獲取數據的頻率。
containers:// 參數設置
- -allow_dynamic_housekeeping=false
- -housekeeping_interval=1s
2、創建對應的ServiceMonitor 讓 cAdvisor 讓Prometheus 忽略cadviosr自帶的時間戳。(因為 cadviosr自身的抖動機制,頻率無法固定)
cAdvisor的抖動機制:
// return jitter(cd.housekeepingInterval, 1.0)
func jitter(duration time.Duration, maxFactor float64) time.Duration {
if maxFactor <= 0.0 {
maxFactor = 1.0
}
wait := duration + time.Duration(rand.Float64()*maxFactor*float64(duration))
return wait
}
cAdvisor的 ServiceMonitor上配置忽略指標自帶時間戳
通過單獨部署的 cAdvisor和 Prometheus 的忽略 cAdvisor 自帶的時間戳,我們成功的解決了容器監控斷點的問題,保證了監控的頻率。
spec:
endpoints:
- honorLabels: true
// 忽略時間戳
honorTimestamps: false
interval: 10s
可以看到再去采集 1分鐘內的容器相關監控數據,是很標準的 6 個數據點。至此監控“掉點”問題解決。
監控面板展示數據連續,無中斷現象發生。
4.3 后端數據庫負載突增
4.3.1 問題定位
從監控架構圖可以看到,我們使用社區 v1.59.1-cluster版本的VictoriaMetrics 作為監控后端的時序數據庫,并在Prometheus 采集的數據后通過remote_write寫入到時序數據庫VictoriaMetrics中進行持久化存儲,Grafana會從VictoriaMetrics 讀取數據展示在對應的 Dashboard上面。而在我們的實際使用中發現,Prometheus 遠程寫入VictoriaMetrics有不定期延遲飆升的現象。
Prometheus寫入VictoriaMetrics延遲
寫入數據庫延遲的飆升會導致,監控面板展示延時、監控誤告警等一系列問題。
我們對 VictoriaMetrics的詳細指標進行分析。發現 vmstorage 組件在底層執行 indexdb merge 操作的時候,其 CPU、內存等資源使用量會有一個突增, 即indexdb 的 Merge 操作非常消耗資源。
4.3.2 解決方案
正是由于VictoriaMetrics 這些的資源突增,導致自己負載過高,無法正常響應 Prometheus的 remote_write的數據。我們所期望的是在 indexdb merge 的時候,資源使用量的增長不會影響到正常的數據插入和查詢。經過查詢相關資源得到VictoriaMetrics在1.73.0版本中對indexdb的 merge進行優化,提升了整體性能。故我們對VictoriaMetrics 進行了版本升級以優化這個問題。在版本升級后,未發現 VictoriaMetrics 在indexdb merge時有資源突增的情況發生。
五、總結
在Kubernetes大規模使用的今天,以 Prometheus 為核心的監控系統已成為云原生監控領域的事實標準。原生Prometheus沒有提供高可用和持久化存儲的標準方案,vivo通過自研adapter實現了Prometheus雙副本高可用,并將數據寫入到VictoriaMetrics中實現了數據的持久化存儲。而在業務規模的增長的過程中,監控數據量也在快速增長,對監控采集和存儲組件都帶來了不小的壓力,當前我們通過降低數據提供端指標數量、優化采集組件參數、升級開源存儲組件版本的方式,提升了監控系統的性能。
而架構的演變是隨著業務規模的增長而不斷的演變改進的,未來我們將結合業務實際規模優化監控架構提升容器監控整體性能。后續我們規劃在監控采集、監控查詢、監控數據提供三個方向繼續提升提供系統性能:
- 【監控采集】:改進數據采集端架構,應用自動分片從而降低采集端壓力。
- 【監控查詢】:應用 PrometheusRule以降低常用查詢耗時,提升監控查詢體驗。
- 【監控數據提供】:在exporter端降低數量從而更穩定提供的數據。