打造雲原生大型分佈式監控系統
笑談監控系統
隨着時間的積累,出現故障的風險越來越高,事故的發生總是出人預料,如果採用人力運維的方式,對於故障定位、故障處理都是很大的挑戰。故障的時間越長,面臨的損失越大,所以在發展到一定程度的團隊都需要一套完善的監控系統
一套完善的監控系統最重要的就是本身永遠不可以故障,即使平臺故障也要確保監控可能告警出來,所以監控系統本身的高可用,是我們一直在追求的,先來看看一個完備的監控系統應該考慮哪些功能
監控系統設計面臨什麼問題
監控系統會對很多角色進行監控,我把他分爲這幾個大類:服務器、容器、服務或應用、網絡、存儲、中間件,根據業界的方案,不同的分類使用不同的採集器進行採集
在功能上要考慮哪些問題?
-
支持標記不同監控指標來源,方便理清楚業務來源 -
支持聚合運算,轉換指標的含義、組合用來進行計算、彙總、分析 -
告警、報表、圖形化大屏展示 -
保存歷史數據便於溯源
在易用性上應該考慮
-
支持配置增減監控項,自定義監控
-
支持配置表達式進行計算
-
最好有自動發現,在新增服務器或新增pod等資源時自動納入監控
-
支持配置告警策略定義告警範圍與閾值,支持自定義告警
方案選型
從以上方面考慮,應該選用哪些開源方案呢?業界常見的有Elasticsearch
、Nagios
、zabbix
、prometheus
,其他方案比較小衆不做討論
-
Elasticsearch
是一個實時的分佈式搜索和分析引擎,支持分片、搜索速度快,一般和Logstash
、Kibana
結合起來一起用,也就是ELK
,更擅長文檔日誌的搜索分析 -
Nagios
: 優點是出錯的服務器、應用和設備會自動重啓,自動日誌滾動;配置靈活,可以自定義 shell 腳本,通過分佈式監控模式;並支持以冗餘方式進行主機監控,報警設置多樣,以及命令重新加載配置文件無需打擾 Nagios 的運行。缺點是事件控制檯功能很弱,插件易用性差;對性能、流量等指標的處理不給力;看不到歷史數據,只能看到報警事件,很難追查故障原因;配置複雜,初學者投入的時間、精力和成本比較大。 -
zabbix
入門容易、上手簡單、功能強大,容易配置和管理,但是深層次需求需要非常熟悉zabbix
並進行大量的二次定製開發,二次開發太多是不可接受的 -
prometheus
幾乎支撐了上面所有的需求,可視化展示可以接入grafana
,可以用promSQL
語言來做聚合查詢,不需要定製;可以使用打tag
的方式,對每個指標分類;強大的社區針對各種應用、網絡、服務器等設備、角色都提供了採集方案以及無侵入式的高可用方案,這個就是今天討論的重點
根據上面的種種原因,綜合來看prometheus比較合適
prometheus與他的缺陷
-
從上面的架構圖可以看出,
prometheus
是在客戶端部署採集器(exporter)的形式來採集數據,服務端主動向prometheus
通信來拉取數據 -
客戶端也可以通過推送數據到
PushGateway
再交給prometheus
拉取 -
prometheus
有自動發現的能力,簡單配置以後就可以主動拉取平臺接口獲取監控範圍:azure、consul、openstack等,並針對檢測角色配置tag,如果和業務強相關,可以定製修改代碼,拉取自己平臺的接口來識別監控角色和動態打tag -
prometheus也有告警的能力,接入官方提供的
AlertManager
組件可以檢測產生告警,再使用webhook
接入自己的告警郵件/短信通知平臺 -
這裏的問題在於無法通過頁面配置告警策略、也無法存儲告警記錄,可以在
AlertManager
後面加一些組件來告警收斂、靜默、分組、存儲 -
告警策略的動態配置,可以寫程序根據策略生成告警配置、放到
prometheus
指定目錄下,並調用prometheus
熱更新接口
唯一要解決的就是負載量大時出現的性能問題以及高可用問題
單機prometheus的部署存在的問題
prometheus的架構決定是他更適合單機的部署方案,單機部署在壓力過大時可以通過服務器升配的方式緩解壓力,但是依然會存在共性的問題
-
採集速率會因爲cpu/網絡通信限制導致卡頓,採集速度變慢,指標在週期內未主動拉取的時候會丟失本次的指標,這裏可以把採集週期拉長,後果是粒度變粗,不建議拉太長;另一種方式就是減少無用指標的採集
-
查詢時也是因爲同樣的原因速度會受到限制,數據存儲時間範圍過多時,對磁盤會有很大的壓力
-
單點故障時就完全沒有辦法了,直接服務不可用
單點高負載考慮什麼方案?
參考前一次的文章,高負載的時候自動水平擴展,並做負載均衡,首先想到的水平擴展方式就是Prometheus
提供的分組能力
相應於把prometheus分片,通過配置的方式各採集部分節點,這種方式有三個問題
-
數據分散運維困難
-
要來回切換數據源,看不到全局視圖
解決這個問題,考慮增加一個存儲位置彙總數據(remote write)
這裏考慮使用TSDB彙總,需要支持擴容的、支持集羣保證高可用的TSDB
但是需要在TSDB上層再加一個查詢組件來做查詢,會喪失原生的查詢語句能力,可以考慮把TSDB替換成prometheus
節點,用聯邦的形式存儲
這種情況可以滿足基本的使用要求,通過prometheus自監控來通知運維人員手動擴容修改分組,有沒有更自動一點的方式呢?
彈性伸縮(自動水平伸縮)
彈性伸縮的前提有三個
-
要能監控當前節點負載狀態,預判擴容時機
-
需要維護服務啓停方式、自動創建服務並放到相應節點上
-
同時要能修改
prometheus
各節點數據採集範圍
上k8s
做容器編排是最直接的方案,可以解決創建和銷燬服務的問題,也是可以通過cpu使用率或自定義指標完成橫向擴容的,但解決不了的問題是修改prometheus
節點配置,動態分配採集範圍,考慮使用以下方案
-
prometheus
注意要配置節點反親和性(k8s配置podAntiAffinity
) -
寫一個調度器通過
k8s api
檢測prometheus
節點狀態 -
通過k8s檢測節點故障以及負載情況,使用
hash
分攤壓力,擴展prometheus
的sd
自動發現功能,帶上自己的hostname
來獲取調度器提供的數據範圍
用這種方式就不需要修改配置文件了,因爲是prometheus
接口端定時更新監控範圍
-
根據具體運行情況伸縮prometheus,不需要再配置configmap
到這裏你可能有一個疑問,假如我監控服務器用上面的方式,那麼多接收端,再加一個redis
集羣的監控,應該放到哪個節點上呢?答案是可以專門創建獨立於此自動伸縮方案的prometheus
來進行少量數據監控,或者直接放到所有節點上,在上層再考慮去重的問題,這個我們一會討論。
到目前爲止分片以後分散了壓力,但還沒有解決的問題是數據分散無法彙總查詢、單點故障數據丟失的問題。
彙總查詢可能你會想到剛剛說的聯邦部署,但壓力又彙總到一點上了,不能根本的解決問題;解決單點故障應該使用冗餘的形式部署,給每個監控範圍分配2個及以上監控節點,但會導致客戶端拉取次數翻倍,也不建議。
如何保證單點故障數據不丟失
爲了避免無法彙總查詢、單點故障數據丟失的問題,這裏打算接入一個高可用方案thanos
,把prometheus
設置爲無狀態應用,並開啓遠程寫把數據推送到thanos
這樣的話prometheus
本身不存儲數據,即使掛掉部分節點,只要保證node
夠多也會再自動伸縮出新的節點,期間讀取到的採集範圍會先負載變大,然後又得到緩解,整個過程在2個週期內解決
PS: ,Prometheus在將採集到的指標寫入遠程存儲之前,會先緩存在內存隊列中,然後打包發送給遠端存儲,以減少連接數量,要提高寫入速率需要修改配置項queue_config
簡單介紹下thanos
,thanos
是無侵入式的高可用方案,負責對prometheus
產生的數據進行彙總、計算、去重、壓縮、存儲、查詢、告警,他實現了prometheus
提供的查詢接口,對外部而言查詢prometheus
還是查詢thanos
的效果完全一樣,是無感知的
一起來實現分佈式高可用監控系統
如何讓我們來實現一個這樣的組件,你會怎麼做呢?
把分片數據寫入到存儲,其他組件和存儲通信,thanos
的主流方案也是這麼做的
如上圖所示所有的組件都會與對象存儲通信,完成數據存儲或者讀取的功能
-
使用對象存儲做存儲引擎
-
和
prometheus
節點一同部署sidecar
,每個節點對應一個,定期放數據推送到對象存儲 -
Ruler
負責判定告警以及根據規則做指標聚合運算 -
Compact
負責降準壓縮,一份數據變三份,一般是分爲1分鐘、5分鐘、1小時寫回存儲,查詢時間粒度越大呈現指標粒度越粗,防止前端數據刷爆 -
Query
與其他組件通過grpc
的方式進行通信讀取數據,它不和對象存儲直接通信,而是在中間加了一層gateway
網關 -
上圖的方案
sidecar
不是我這次的架構,其他是一樣的,sidecar
的原理是把採集到的數據使用緩存到本地(默認2小時數據爲熱數據),冷數據才推送,近期數據存儲本地,查詢時再做彙總會有一定的壓力,同時單點故障問題還是沒有解決
如果是小規模集羣無網絡壓力可以使用sidercar
不要在接收端存儲
和prometheus
部署在一起的sidercar
違背了容器中的簡單性原則,也提高存儲壓力,把他們剝離開試試?
我的想法是收集數據推送,然後進行存儲,由其他組件完成與存儲的通信
如上圖,Receive
組件實現了remote write
接口,Prometheus
可以將數據實時推送到Receive
上;Receive
本身實際上相當於一個沒有收集功能的Prometheus
,那此時Prometheus
就不再需要存儲數據,之前的方案就可以實施了
-
對象存儲中的數據具有不可修改特性,也就是說一旦寫入就變成只讀了
-
Prometheus
本地存儲的原理是接受到的數據寫到本地文件存儲裏面組成WAL
文件列表,Receive
也是這麼做的,然後超過一定時限後生成block
,這些block
會上傳到對象存儲 -
Query
組件來近期數據(默認2小時內)查詢recevie,過期後使用對象存儲 -
receive
使用k8s的dnssrv
功能做服務發現,便於下游拉取數據而不要使用k8s的service:ip
自帶的負載均衡 -
receive
自帶了hash算法,可以把上游遠程寫過來的流量均勻分佈在各個節點上,這裏可以採用k8s的service
自動輪訓,recevie
會把請求route
到相應節點上
爲防止prometheus掛掉一個導致的數據丟失問題,給prometheus加一個副本,然後在query時去重,主要由query
的--query.replica-label
參數和Prometheus
配置的 prometheus_replica
參數來實現,如下圖
同樣的其他組件,如ruler
也可以配置冗餘部署rule_replica
就不展開講了
還好recevie
自帶了分佈式一致性算法,不然就要自己實現一個了,到此我們解決了
-
數據接收端能應對海量數據的壓力均衡
-
解決了prometheus部署在不同集羣上時查詢延遲高的問題
-
解決了跨節點數據複合運算(
ruler
) -
解決了數據壓縮降準
hashring真的是分佈式一致性算法嗎
我們知道分佈式一致性算法可以解決下面的問題
-
在壓力增加時做到自動擴容,壓力減小時自動縮容
-
擴縮容時必須要保障數據不丟失,單點故障時數據也不可以丟失
-
擴縮容時數據映射落點要一致,不然會出現數據斷連
但是實際使用過程中,不難發現,還是會發生數據丟失,這引起了我的興趣
這一塊的官網介紹很少,hashring
的endpoints
參考下面的代碼,你會發現0 1 2 的方式就是k8s
的statefulset
爲pod
分配的name,所以recevie
要以sts
的方式部署,並提前把副本數與配置關係對應起來,3節點已經可以支撐很大數量的數據處理了
thanos-receive-hashrings.json: |
[
{
"hashring": "soft-tenants",
"endpoints":
[
"thanos-receive-0.thanos-receive.thanos.svc.cluster.local:10901",
"thanos-receive-1.thanos-receive.thanos.svc.cluster.local:10901",
"thanos-receive-2.thanos-receive.thanos.svc.cluster.local:10901"
]
}
]
在源碼裏發現,實際上這裏並沒有使用分佈式一致性算法!! 在hashring.go
函數裏可以看到,這是一個簡單的hash mod
,所以hashring
是有誤導性的
func (s simpleHashring) GetN(tenant string, ts *prompb.TimeSeries, n uint64) (string, error) {
if n >= uint64(len(s)) {
return "", &insufficientNodesError{have: uint64(len(s)), want: n + 1}
}
return s[(hash(tenant, ts)+n)%uint64(len(s))], nil
}
提煉出來是這樣的hash算法
hash(string(tenant_id) + sort(timeseries.labelset).join())
-
tenant_id
是指數據源帶上租戶,可以給不同租戶分配自己的hash -
具體的
hash
算法使用xxHash
參考文末資料5
解決的辦法也有了,可以通過配置多副本冗餘的方式,把receive的數據冗餘到其他位置,設置receive.replication-factor
配置,然後拉取數據的時候因爲使用的是服務發現,和所有服務通信的方式,可以在一定程序上保證數據不丟失
PS: 冗餘也會有點問題,算法是先選hash mod
後的節點,比如是第n個,然後如果factor
是2,就再選n+1
和n+2
,然後發請求給n
,這個時候如果n
掛了其實會失敗,相對而言n+1
或者n+2
節點掛了的話不會對這部分的數據有影響
當receive出現故障是怎麼處理的
當發生擴縮容的時候,由於hashring發生變化,所有的節點需要將write-ahead-log
的數據flush
到TSDB
塊並上傳到OSS
中(如果配置了的話),因爲這些節點之後將有一個新的分配。之前已存在節點上的時間序列不需要作調整,只是後面過來的請求按新的分發來尋找該去的receiver
節點。
這個過程不需要重啓receive
,代碼裏有watch,可以檢測hashring
的變化
注意,這種情況發生的flush
可能會產生較小的TSDB
塊,但compactor
模塊可以將它們優化合並,因此不會有什麼問題。
當有receiver
節點發生故障時,prometheus
的遠程寫會在後端目標無響應或503時進行重試,因此,receiver
一定時間的服務掛掉是可以容忍的。如果這種掛機時間是不可接受的話,可以將副本數配置爲 3 或以上,這樣即使有一個receiver
節點掛掉,還有其他receiver
節點來接收寫請求
業務指標計算問題
如果有非常複雜的業務指標,需要從其他地方採集推送,最好的方式是寫成採集器exporter
,在ruler
進行復合運算,當然也有可能出現表達式寫不出來的尷尬問題
考慮寫成k8s
的job定時任務
,把數據推送到PushGateway
,再交給prometheus
去拉取
PS1: 注意按exporter
的開發標準,不允許出現重複指標哦
PS2:如果要刪除過期的垃圾數據可以調用PushGateway
的http://%s/metrics/job/%s/instance/%s/host/
接口進行刪除
告警策略動態更新/告警記錄儲存的問題
要動態生成告警策略,可以寫一個服務接收請求,調用k8s生成configmap,並通知ruler
進行熱更新
-
更新策略配置文件configmap(同步更新到pod裏會有一定的延遲,使用 subPath
是無法熱更新的,注意configMapAndSecretChangeDetectionStrategy: Watch
參數必須爲默認參數Watch
) -
把configmap掛載相應的ruler上面
全景視圖
最後
當然對於一個成熟的監控系統來說,除了發現故障及時告警以外,還應該有更多的功能,這不是本次討論的範圍,如果有時間未來會寫寫
-
運營故障報表和資源日報週報月報等用於趨勢分析 -
低負載報表用於分析服務器利用率,防止資源浪費 -
有了故障趨勢和更多的重要指標覆蓋,可以結合AI進行故障預測,在故障發生前提前預測
最後的最後
針對全k8s的集羣監控來說,還有更簡單的方式來監控,那就是Prometheus Operator
,可以非常簡單的創建k8s
的資源,比如收集器Prometheus
、採集器的抽象ServiceMonitor
、AlertManager
等,要監控什麼數據就變成直接操作k8s
集羣的資源對象了
監控可能爲其他應用的水平伸縮服務服務,使用Prometheus Adpater
來自定義監控某些指標,來達到自動擴縮容的目的
監控還可以爲運維平臺服務,提供故障自動修復
一句話,只要監控運維平臺做得足夠好,運維人員都得失業
引用與拓展資料
-
1、7 款你不得不瞭解的開源雲監控工具
-
2、Thanos在TKEStack中的實踐 - Even - A super concise theme for Hugo
-
3、Prometheus Remote Write配置 - 時序數據庫 TSDB - 阿里雲
-
4、Thanos - Highly available Prometheus setup with long term storage capabilities
-
5、xxHash - Extremely fast non-cryptographic hash algorithm
本文分享自微信公衆號 - 編程三分鐘(coding3min)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。