詳解云原生全棧監控
前言
當前全球企業云化、數字化進程持續加速,容器、微服務等云原生技術在軟件架構中快速滲透,IT 架構云化、復雜化持續驅動性能監控市場。企業云化、數字化持續轉型,以及為了考慮系統的彈性、效率,企業軟件開發中大量云原生技術的應用推動全球 IT 監控市場快速變化,如何全面、有效的對容器、K8s、微服務進行監控是當下云原生技術面臨的重要課題。
背景和挑戰
云化產品通常采用服務化框架,由一系列微服務組成,且微服務是可以獨立運行的進程,不同服務可使用不同開發語言,可能分布部署在幾千臺服務器上,甚至可能橫跨多個不同的數據中心,服務間使用輕量的通信機制;服務之間存在復雜的調用關系,對運維人員理解系統的行為或分析系統性能帶來巨大挑戰 如:
(1)容器是否正常運行
(2)K8S是否正常運行。
(3)微服務是正常
(5)業務調用出現問題,如何快速找出哪個服務發生失敗?
(6)某個業務調用耗時較長,如何快速找到性能瓶頸點?
(7)如何快速獲取某次調用的業務日志進行分析定位?
解決方案
概述
云原生監控體系包括:Healthchecks、Metrics、Logging、Tracing。Healthchecks:健康檢查可以定期檢查某個應用的存活狀態;Metrics:度量指標監控,在離散的時間點上產生數值點;Logging:日志監控;Tracing:調用鏈監控。
各種監控工具適用場景如下圖所示:
健康檢查
微服務架構,為了保證所有服務可用,當服務發生問題時能及時摘除有問題的服務需要定期檢測服務可用性,即健康檢查。通常健康健康檢查包括TCP與HTTP兩種。即定時發送TCP或HTTP請求,根據響應來確定服務是否可用。一般通過TCP定期請求來判定網絡層是否正常,而通過Http請求判斷應用層是否正常。服務要配置好請求接口,檢測服務定期向指定的接口發送http請求,并根據接口響應碼和響應時間判斷。Spring boot的end port /health可以檢查應用的健康狀態,舉例說,當我們訪問 http://localhost:8088/health 時,可以看到 HealthEndPoint 給我們提供默認的監控結果,包含 磁盤檢測和數據庫檢測。
{
"status": "UP",
"diskSpace": {
"status": "UP",
"total": 398458875904,
"free": 315106918400,
"threshold": 10485760
},
"db": {
"status": "UP",
"database": "MySQL",
"hello": 1
}
}
容器監控
容器監控使用Prometheus-cAdvisor,cAdvisor是谷歌專為監控容器性能狀態設計的一個開源工具,cAdvisor提供有Push和Pull兩種獲取性能數據的接口。Push接口指的是由cAdvisor主動將數據周期性的推送到遠端的存儲服務中,Influxdb與cAdvisor的對接就是通過這個接口完成的。而Pull接口則允許外部訪問服務隨時主動從cAdvisor獲取到當時時刻的性能數據,然后自行處理,Prometheus與cAdvisor的對接用的是這種方法。
基于容器的微服務監控和原始的監控是有很大區別的,因為服務的實例生存周期很短,分分鐘可能就會有容器的生滅。微服務的容器與宿主機的監控離不開CPU、內存、磁盤、網卡這些基礎的性能指標,對于宿主機的監控來說,我們可以依然使用原始的監控方式,每個宿主機安裝一個代理來采集服務器的性能指標,代理在采集性能指標的時候可以打上時間戳和相應的標簽來區分不同性能指標的數據維度(metric),然后將監控數據匯總到時間序列數據庫,里面的數據可以對接目前一些開源的組件來進行可視化的展示,也可以對接報警服務(結合報警服務的報警策略)進行報警。
容器的監控自然就和宿主機不太一樣了,我們不能說給每個容器鏡像內部都集成一個監控代理(agent),這樣的話侵入性太強,不易于維護。Prometheus有很多的Exporter可以用來采集監控數據,例如我們想采集Kubernetes上所有容器(pod)的性能指標的話,Promethus可以通過直接配置多個Kubernetes ApiServer的Endpoints來監控整個Kubernetes集群。
K8S監控
K8S集群層面選擇使用Prometheus。集群層面的監控又分為Node、K8S基礎組件、K8S資源對象三大類。
1.對于Node的監控,Prometheus提供了node-exporter,可采集到CPU、內存、磁盤IO、磁盤使用率、網絡包量、帶寬等數據;
2.K8S基礎組件類的kubelet、kube-apiserver、kube-controller-manager 和 kube-scheduler等,都提供了 metrics接口暴露自身的運行時的監控數據,這些數據都可被部署在K8S集群中的Prometheus 直接拉取到;
3.結合cadvisor 和kube-state-metrics ,可直接采集到K8S中Pod的 CPU、內存、磁盤 IO、網絡 IO 等數據。由CoreOS開源的Kube-Prometheus項目,極大簡化了Prometheus的安裝部署運維工作。
基于Kubernetes實現的微服務應用級的監控插件,如下圖:
在Kubernetes的master節點,也就是安裝apiserver的那臺服務器上運行一個監控插件,該插件可以通過一個kubernetes提供的官方客戶端來訪問apiserver,首先我們要告知插件要監控哪個namespace下的哪個service,然后,插件通過和apiserver進行交互獲取某個service下所有Pods的實例,插件會并發訪問所有pod提供的/metrics接口(Path可配),并給每個pod的返回數據(json格式,遵守一定的數據格式契約)打上pod_name的標簽來標識每個pod返回的metrics,打上pod_name標簽的同時也會打上service_name的標簽用來區分具體是哪個service的監控數據。
Kubernetes主要提供了如下5種服務發現模式和Prometheus進行集成:Node、Pod、Endpoints、Service、Ingress。監控K8S將使用Prometheus federation的形式,k8s集群外部的Prometheus從k8s集群中Prometheus拉取監控數據,外部的Prometheus才是監控數據的存儲。k8s集群中部署Prometheus的數據存儲層可以簡單的使用emptyDir,數據只保留24小時(或更短時間)即可,部署在k8s集群上的這個Prometheus實例即使發生故障也可以放心的讓它在集群節點中漂移。
1)創建namespace取名ns-monitor
2)在k8s中部署node-exporter
Node-exporter用于采集kubernetes集群中各個節點的物理指標,比如:Memory、CPU等。可以直接在每個物理節點是直接安裝,這里我們使用DaemonSet部署到每個節點上,使用 hostNetwork: true 和 hostPID: true 使其獲得Node的物理指標信息,配置tolerations使其在master節點也啟動一個pod。
#創建node-exporter.yml文件:
3-1)創建編輯rabc.yml。
rbac.yml定義了Prometheus容器訪問k8s apiserver所需的ServiceAccount和ClusterRole及ClusterRoleBinding。
3-2)創建編輯configmap.yml 進行configmap中的prometheus的配置文件。
3-3)prometheus-deploy.yml定義Prometheus的部署 。
3-4)prometheus-svc.yml定義Prometheus的Service。
需要將Prometheus以NodePort, LoadBalancer或使用Ingress暴露到集群外部,這樣外部的Prometheus才能訪問它 。
3-5)使用yml文件創建對象。
kubectl create -f rbac.yml
kubectl create -f configmap.yml
kubectl create -f prometheus-deploy.yml
kubectl create -f prometheus-svc.yml
4)配置配置Prometheus Federation
完成Kubernetes集群上的Prometheus的部署之后,下面將配置集群外部的Prometheus使其從集群內部的Prometheus拉取數據。實際上只需以靜態配置的形式添加一個job就可以。
5)配置pushgateway
日志監控
Fluentd是一個通用的信息收集、整理、轉發的流式數據處理工具。默認情況下Docker會將所有容器輸出到系統控制臺的內容重定向到以容器ID命名的一個本地目錄中,只需要定期采集所有這些目錄的內容就能一字不漏的將容器的輸出捕獲出來,這種方式的侵入性很小,但由于是周期性的收集,日志在匯聚端(例如Kibana)的展示會有一定的延時,延時長度與日志收集的周期相關。相反的,如果使用Docker的日志驅動(啟動docker后臺服務時指定參數–log-driver=fluentd)將獲得實時性很好的匯聚端日志展示,但由于日志直接發送到了遠端的Fluentd服務,會使得在本地主機上的docker logs命令失效。
兩種方式的共性在于:不論通過哪一種方式,收集到的日志都能夠以容器名稱、鏡像、標簽等對容器使用十分友好的維度進行檢索。Kubernetes 集群本身不提供日志收集的解決方案,我們采用fluentd-->kafka-->logstash-->elasticsearch-->kibana的方式,直接在應用程序中將日志信息推送到采集后端。
調用鏈監控
調用鏈定義:在系統完成一次業務調用的過程中,把服務之間的調用信息(時間、接口、層次、結果)打點到日志中,然后將所有的打點數據連接為一個樹狀鏈條就產生了一個調用鏈。跟蹤系統把過程中產生的日志信息進行分析處理,將業務端到端的執行完整的調用過程進行還原,根據不同維度進行統計分析;從而標識出有異常的服務調用,能夠快速分析定界到出異常的服務;同時可根據數據統計分析系統性能瓶頸。
Dapper, a Large-Scale Distributed Systems Tracing Infrastructure 描述了其中的原理和一般性的機制。模型中包含的術語也很多,理解最主要的兩個即可:
Trace:一次完整的分布式調用跟蹤鏈路。
Span:跨服務的一次調用;多個 Span 組合成一次 Trace 追蹤記錄。
下面通過一次用戶服務請求來完成調用鏈過程模擬:
左圖為一個和5臺服務器相關的一個服務,包括:前端(A),兩個中間層(B和C),以及兩個后端(D和E)。當一個用戶(這個用例的發起人)發起一個請求時,首先到達前端,然后發送兩個RPC到服務器B和C。B會馬上做出反應,但是C需要和后端的D和E交互之后再返還給A,由A來響應最初的請求。右表示對應 Span 的管理關系。每個節點是一個 Span,表示一個調用。至少包含 Span 的名、父 SpanId 和 SpanId。節點間的連線下表示 Span 和父 Span 的關系。所有的 Span 屬于一個跟蹤,共用一個 TraceId。從圖上可以看到對前端 A 的調用 Span 的兩個子 Span 分別是對 B 和 C 調用的 Span,D 和 E 兩個后端服務調用的 Span 則都是 C 的子 Span。跟蹤系統根據用戶請求每次生成的全局唯一的ID(TraceId),TraceId 在span間傳遞,將不同服務的“孤立的”日志串在一起,重組還原出更多有價值的信息。如今調用鏈系統有很多實現,用的比較多的如 zipkin ,還有已經加入 CNCF 基金會并且用的越來越多的 Jaeger。
調用鏈模型格式
為了能將一系列埋點串接成一個完整的調用鏈,并區分不同請求的調用鏈日志信息,同時信息中需要包含請求狀態與時長,對于不同業務應用可能需要有特殊的信息記錄到日志中;所以調用鏈日志信息(Span)應包含如下內容:
一次業務請求調用鏈模型:
對于Trace而言,最基礎的能力是能夠記錄請求在多個服務之間調用的傳播、依賴關系并進行可視化。而從Trace本身的數據特點而言,它是規則化、標準化且帶有依賴關系的訪問日志,因此可以基于Trace去計算并挖掘更多的價值。下面是SLS OpenTelemetry Trace的實現架構,核心是通過數據編排計算Trace原始數據并得到聚合數據,并基于SLS提供的接口實現各類Trace的附加功能。例如:
1.依賴關系:這是絕大部分的Trace系統都會附帶的功能,基于Trace中的父子關系進行聚合計算,得到Trace Dependency
2.服務/接口黃金指標:Trace中記錄了服務/接口的調用延遲、狀態碼等信息,基于這些數據可以計算出QPS、延遲、錯誤率等黃金指標。
3.上下游分析:基于計算的Dependency信息,按照某個Service進行聚合,統一Service依賴的上下游的指標
4.中間件分析:Trace中對于中間件(數據庫/MQ等)的調用一般都會記錄成一個個Span,基于這些Span的統計可以得到中間件的QPS、延遲、錯誤率。
告警相關:通常基于服務/接口的黃金指標設置監控和告警,也可以只關心整體服務入口的告警(一般對父Span為空的Span認為是服務入口調用)。
Metrics:
- 通常都是range查詢,每次查詢某一個單一的指標/時間線,或者一組時間線進行聚合,例如統一某個應用所有機器的平均CPU
- 時序類的查詢一般QPS都較高(主要有很多告警規則),為了適應高QPS查詢,需要把數據的聚合性做好
- 對于這類數據都會有專門的時序引擎來支撐,目前主流的時序引擎基本上都是用類似于LSM Tree的思想來實現,以適應高吞吐的寫入和查詢(Update、Delete操作很少)
- 同時可觀測性數據還有一些共性的特點,例如高吞吐寫入(高流量、QPS,而且會有Burst)、超大規模查詢特點、時間訪問特性(冷熱特性、訪問局部性等)。
業務調用鏈路監控
Skywalking是一款比較優秀的開源的應用性能監控工具,支持對分布式系統的監控、跟蹤和診斷。它提供了如下的主要功能特性:
Service Topology監控
調用鏈路監控可以從兩個角度去看待。通過給服務添加探針并產生實際的調用之后,我們可以通過Skywalking的前端UI查看服務之間的調用關系。我們簡單模擬一次服務之間的調用。新建兩個服務,service-provider以及service-consumer,服務之間簡單的通過Feign Client 來模擬遠程調用。
從圖中可以看到:
- 有兩個服務節點:provider & consumer
- 有一個數據庫節點:localhost【mysql】
- 一個注冊中心節點
consumer消費了provider提供出來的接口。
一個系統的拓撲圖讓我們清晰的認識到系統之間的應用的依賴關系以及當前狀態下的業務流轉流程。細心的可能發現圖示節點consumer上有一部分是紅色的,紅色是什么意思呢?
紅色代表當前流經consumer節點的請求有一段時間內是響應異常的。當節點全部變紅的時候證明服務現階段內就徹底不可用了。運維人員可以通過Topology迅速發現某一個服務潛在的問題,并進行下一步的排查并做到預防。
Skywalking Trace監控
Skywalking通過業務調用監控進行依賴分析,提供給我們服務之間的服務調用拓撲關系、以及針對每個endpoint的trace記錄。
我們在之前看到consumer節點服務中發生了錯誤,讓我們一起來定位下錯誤是發生在了什么地方又是什么原因呢?
在每一條trace的信息中都可以看到當前請求的時間、GloableId、以及請求被調用的時間。我們分別看一看正確的調用和異常的調用。
Trace調用鏈路監控
圖示展示的是一次正常的響應,這條響應總耗時19ms,它有4個span:
- span1 /getStore = 19ms 響應的總流轉時間
- span2 /demo2/stores = 14ms feign client 開始調用遠程服務后的響應的總時間
- span3 /stores = 14ms 接口服務響應總時間
- span4 Mysql = 1ms 服務提供端查詢數據庫的時間
這里span2和span3的時間表現相同,其實是不同的,因為這里時間取了整。
在每個Span中可以查看當前Span的相關屬性。
- 組件類型: SpringMVC、Feign
- Span狀態: false
- HttpMethod: GET
- Url:
http://192.168.16.125:10002/demo2/stores
這是一次正常的請求調用Trace日志,可能我們并不關心正常的時候,畢竟一切正常不就是我們期待的么!我們再來看下,異常狀態下我們的Trace以及Span又是什么樣的呢。
發生錯誤的調用鏈中Span中的is error標識變為true,并且在名為Logs的TAB中可以看到錯誤發生的具體原因。根據異常情況我們就可以輕松定位到影響業務的具體原因,從而快速定位問題,解決問題。通過Log我們看到連接被拒,那么可能是我們的網絡出現了問題(可能性小,因為實際情況如果網絡出現問題我們連這個trace都看不到了),也有可能是服務端配置問題無法正確建立連接。通過異常日志,我們迅速就找到了問題的關鍵。
服務性能監控
服務性能可以實現以下關鍵指標:
1.關鍵業務指標:響應時間、Apex、吞吐率、錯誤率
2.事務:耗時百分比、響應時間、吞吐量、Apdex、錯誤率、調用次數
3.數據庫:SQL耗時、平均響應時間、吞吐率、SQL語句執行計劃、代碼堆棧
4.NoSQL:Memcached/Redis/MogooDB的操作總耗時、平均響應時間、吞吐率
5.外部應用:HTTP/Thrif/Dubbo/Web Service的響應時間占比、平均響應時間、響應總時間、吞吐率、錯誤率
6.MQ:RabbitMQ/JMS/ActiveMQ生產者、消費者的消息總數、每分鐘消息數、平均消息發送時間、總流量
7.JVM:內存使用量、線程、HTTP會話