一 、設計架構
監控系統的總體架構大多是類似的,都有數據采集、數據處理存儲、告警動作觸發和告警,以及對監控數據的展示。
下面是 Prometheus 的架構:
Prometheus Server 負責定時從 Prometheus 采集端 Pull(拉) 監控數據。Prometheus 采集端可以是實現了 /metrics 接口的服務,可以是從第三方服務導出監控數據的 exporter,也可以是存放短生命周期服務監控數據的 Pushgateway。相比大多數采用 Push(推) 監控數據的方式,Pull 使得 Promethues Server 與被采集端的耦合度更低,Prometheus Server 更容易實現水平拓展。
對于采集的監控數據,Prometheus Server 使用內置時序數據庫 TSDB 進行存儲。同時也會使用這些監控數據進行告警規則的計算,產生的告警將會通過 Prometheus 另一個獨立的組件 Alertmanager 進行發送。Alertmanager 提供了十分靈活的告警方式,并且支持高可用部署。
對于采集到的監控數據,可以通過 Prometheus 自身提供的 Web UI 進行查詢,也可以使用 Grafana 進行展示。
二、配置 Prometheus
Prometheus 的配置項是整個組件的核心,配置是聲明式的,這點我覺得是蠻棒的,降低使用門檻,如果想熟練的使用 Prometheus,還是需要把配置文檔好好閱讀幾遍,Prometheus 的官方文檔質量還是蠻高的。最基礎的配置如下:
global
定義 Prometheus 拉取監控數據的周期 scrape_interval 和 告警規則的計算周期 evaluation_interval。
rule_files
指定了告警規則的定義文件。
scrape_configs
scrape_configs 是 Prometheus 最為重要的配置之一,指定了 Prometheus 實例如何抓取 metrics
示例:
global:
# How frequently to scrape targets by default.
[ scrape_interval: <duration> | default = 1m ]
# How long until a scrape request times out.
[ scrape_timeout: <duration> | default = 10s ]
# How frequently to evaluate rules.
[ evaluation_interval: <duration> | default = 1m ]
# The labels to add to any time series or alerts when communicating with
# external systems (federation, remote storage, Alertmanager).
external_labels:
[ <labelname>: <labelvalue> ... ]
# Rule files specifies a list of globs. Rules and alerts are read from
# all matching files.
rule_files:
[ - <filepath_glob> ... ]
# A list of scrape configurations.
scrape_configs:
[ - <scrape_config> ... ]
# Alerting specifies settings related to the Alertmanager.
alerting:
alert_relabel_configs:
[ - <relabel_config> ... ]
alertmanagers:
[ - <alertmanager_config> ... ]
# Settings related to the remote write feature.
remote_write:
[ - <remote_write> ... ]
# Settings related to the remote read feature.
remote_read:
[ - <remote_read> ... ]
scrape_configs
# scrape_configs
# job 是 Prometheus 中最基本的調度單位,一個 job 可能會擁有多個 instances,就像一個服務可能會擁有多個實例一樣。
job_name: <job_name>
# 抓取時間間隔。
[ scrape_interval: <duration> | default = <global_config.scrape_interval> ]
# 抓取超時時間。
[ scrape_timeout: <duration> | default = <global_config.scrape_timeout> ]
# 默認的 web path 是 /metrics,現在對整個社區來說也是一個約定俗成的東西,基本不會有變動。
[ metrics_path: <path> | default = /metrics ]
# 協議格式 http/https?
[ scheme: <scheme> | default = http ]
# 查詢還可以帶參數,但一般也沒這個必要。
params:
[ <string>: [<string>, ...] ]
# tls 證書配置,不過目前如果使用的服務都是跑在 kubernetes 集群內,且無對外部暴露的話,也不用考慮 tls。
tls_config:
[ <tls_config> ]
# 代理配置。
[ proxy_url: <string> ]
# 下面的都是服務發現的配置,Prometheus 原生地提供了多種服務發現的方案。
# 這里只簡單介紹 static_sd_config 和 kubernetes_sd_configs 兩種。
# 目的是為了結合服務發現實現 Prometheus 的熱更新,不必再手動地更新配置。
# 使用 prometheus-operator 本質上是與 kubernetes_sd_configs 相結合,只是 operator 幫我們屏蔽了這些復雜性。
# 對于其他服務發現體系的,可以到官網上查看具體的配置項。
# https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config
# kubernetes_sd_configs 實現的思路其實就是通過 Kubernetes REST API 獲取對應的資源的信息。
# 包括 node/service/pod/endpoints/ingress
# kubernetes 會給每種資源注入自己的信息,當然資源本身也可以自定義一些附帶信息,如 labels/annotation 等。
# 獲取到的數據都以 __meta 作為前綴,在 Prometheus 中,雙下劃線為前綴的 metrics 是不會被暴露到外部的。
# 所以可用 relabels 來將 __meta_* metrics 轉換為自己想要的形式。
kubernetes_sd_configs:
[ - <kubernetes_sd_config> ... ]
# 如果你是單機使用并且沒有任何服務發現體系的話,可以用 static_configs
# <static_config>
# targets:
# targets 就是上面指的一個 job 可能會有多個 instances 的情況,列表類型。
# [ - '<host>' ]
# Labels assigned to all metrics scraped from the targets.
# labels:
# [ <labelname>: <labelvalue> ... ]
static_configs:
[ - <static_config> ... ]
# relabel 涉及到幾種 action,action 指的是你可以根據正則捕獲結果對 label 進行何種操作,是丟棄呢,還是改寫呢?
# 具體內容可以參考下面這篇博客
# https://www.li-rui.top/2019/04/16/monitor/Prometheus%E4%B8%ADrelabel_configs%E7%9A%84%E4%BD%BF%E7%94%A8/
#
# actions 類型如下
#
# replace 根據正則匹配標簽的值進行替換標簽
# keep 根據正則匹配標簽的值保留數據采集源
# dro 根據正則匹配標簽的值剔除數據采集源
# hashmod hash 模式
# labelmap 根據正則匹配標簽的名稱進行映射
# labeldrop 根據正則匹配標簽的名稱剔除標簽
# labelkeep 根據正則匹配標簽的名稱保留標簽
relabel_configs:
[ - <relabel_config> ... ]
alertingRule
采集了 metrics 可以被告警系統使用,所以我們需要根據手上掌握的數據來定義告警規則。首先來看看官方給出的一個基礎示例。:
groups:
- name: example
rules:
- alert: HighRequestLatency
expr: job:request_latency_seconds:mean5m{job="myjob"} > 0.5
for: 10m
labels:
severity: page
annotations:
summary: High request latency
規則模板如下
# The name of the alert. Must be a valid metric name.
# 告警規則名字。
alert: <string>
# The PromQL expression to evaluate. Every evaluation cycle this is
# evaluated at the current time, and all resultant time series become
# pending/firing alerts.
# promQL 表達式,需要符合 promQL 語法
expr: <string>
# Alerts are considered firing once they have been returned for this long.
# Alerts which have not yet fired for long enough are considered pending.
# 規則匹配的持續時間
[ for: <duration> | default = 0s ]
# Labels to add or overwrite for each alert.
# labels 用于提供附帶信息,在 alertmanagers 中可以用到,可以為 golang 標準模板語法。
labels:
[ <labelname>: <tmpl_string> ]
# Annotations to add to each alert.
# Annotations 用于提供附帶信息,在 alertmanagers 中可以用到,可以為 golang 標準模板語法。
annotations:
[ <labelname>: <tmpl_string> ]
recordingRule
Prometheus 提供一種記錄規則(Recording Rule) 來支持后臺計算的方式,可以實現對復雜查詢的 PromQL 語句的性能優化,提高查詢效率。記錄規則的基本思想是,它允許我們基于其他時間序列創建自定義的 meta-time 序列。
在 Prometheus Operator 中已經有了大量此類規則,比如:
groups:
- name: k8s.rules
rules:
- expr: |
sum(rate(container_cpu_usage_seconds_total{image!="", container!=""}[5m])) by (namespace)
record: namespace:container_cpu_usage_seconds_total:sum_rate
- expr: |
sum(container_memory_usage_bytes{image!="", container!=""}) by (namespace)
record: namespace:container_memory_usage_bytes:sum
上面的這兩個規則就完全可以執行上面我們的查詢,它們會連續執行并以很小的時間序列將結果存儲起來。
sum(rate(container_cpu_usage_seconds_total{job="kubelet", image!="", container_name!=""}[5m])) by (namespace)
將以預定義的時間間隔進行評估,并存儲為新的指標,新指標與內存查詢相同,效率更高
namespace:container_cpu_usage_seconds_total:sum_rate
同樣的,我們先來看官方給出的基礎示例。
groups:
- name: example
rules:
- record: job:http_inprogress_requests:sum
expr: sum(http_inprogress_requests) by (job)
rule 規則模板如下
# The name of the time series to output to. Must be a valid metric name.
record: <string>
# The PromQL expression to evaluate. Every evaluation cycle this is
# evaluated at the current time, and the result recorded as a new set of
# time series with the metric name as given by 'record'.
# promQL 表達式,需要符合 promQL 語法
expr: <string>
# Labels to add or overwrite before storing the result.
labels:
[ <labelname>: <labelvalue> ]
? 當一個 node_exporter 被安裝和運行在被監控的服務器上后
?使用簡單的 curl命令 就可以看到 exporter 幫我們采集到的 metrics 數據,以 key/value 形式展現和保存。curl localhost:9100/metrics:9100 為exporter 默認端口號。
[root@node-3 ~]# curl localhost:9100/metrics | grep node_cpu
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0# HELP node_cpu_guest_seconds_total Seconds the cpus spent in guests (VMs) for each mode.
# TYPE node_cpu_guest_seconds_total counter
node_cpu_guest_seconds_total{cpu="0",mode="nice"} 0
node_cpu_guest_seconds_total{cpu="0",mode="user"} 0
node_cpu_guest_seconds_total{cpu="1",mode="nice"} 0
node_cpu_guest_seconds_total{cpu="1",mode="user"} 0
node_cpu_guest_seconds_total{cpu="10",mode="nice"} 0
node_cpu_guest_seconds_total{cpu="10",mode="user"} 0
node_cpu_guest_seconds_total{cpu="11",mode="nice"} 0
三、使用 Prometheus Web UI
一般的當 Prometheus 啟動之后,可以在瀏覽器輸入 http://localhost:9090 打開 Prometheus Web UI
這里我使用了 Ingress來做代理轉發,并修改本地 Host 實現的。(細節還待研究)
四、采集數據格式及分類
4.1 采集數據的格式
Prometheus 使用 metric 表示監控度量指標,它由 metric name (度量指標名稱)和 labels (標簽對)組成:
<metric name>{<label name=<label value>, ...}
Exporter 接口數據規范格式如下:
# HELP <監控指標名稱> <監控指標描述>
# TYPE <監控指標名稱> <監控指標類型>
<監控指標名稱>{ <標簽名稱>=<標簽值>,<標簽名稱>=<標簽值>...} <樣本值1> <時間戳>
<監控指標名稱>{ <標簽名稱>=<標簽值>,<標簽名稱>=<標簽值>...} <樣本值2> <時間戳>
*時間戳應為采集數據的時間,是可選項,如果 Exporter 沒有提供時間戳的話,Prometheus Server 會在拉取到樣本數據時將時間戳設置為當前時間;
metric name 指明了監控度量指標的一般特征,比如 http_requests_total 代表收到的 http 請求的總數。metric name 必須由字母、數字、下劃線或者冒號組成。冒號是保留給 recording rules 使用的,不應該被直接使用。
labels 體現了監控度量指標的維度特征,比如 http_requests_total{method=“POST”, status="200“} 代表 POST 響應結果為 200 的請求總數。Prometheus 不僅能很容易地通過增加 label 為一個 metric 增加描述維度,而且還很方便的支持數據查詢時的過濾和聚合,比如需要獲取所有響應為 200 的請求的總數時,只需要指定 http_request_total{status=“200”}。
Prometheus 將 metric 隨時間流逝產生的一系列值稱之為 time series(時間序列)。某個確定的時間點的數據被稱為 sample(樣本),它由一個 float64 的浮點值和以毫秒為單位的時間戳組成。
4.2 采集數據的分類
在了解過 Prometheus 采集數據的格式之后,我們來了解一下它的分類。Prometheus 將采集的數據分為 Counter、Gauge、Histogram、Summary 四種類型。
需要注意的是,這只是一種邏輯分類,Prometheus 內部并沒有使用采集的數據的類型信息,而是將它們做為無類型的數據進行處理。這在未來可能會改變。
下面,我們將具體介紹著四種類型。
Counter
Counter 是計數器類型,適合單調遞增的場景,比如請求的總數、完成的任務總數、出現的錯誤總數等。它擁有很好的不相關性,不會因為重啟而重置為 0。
Gauge
Gauge 用來表示可增可減的值,比如 CPU 和內存的使用量、IO 大小等。
Histogram
Histogram 是一種累積直方圖,它通常用來描述監控項的長尾效應。
舉個例子:
假設使用 Hitogram 來分析 API 調用的響應時間,使用數組 [30ms, 100ms, 300ms, 1s, 3s, 5s, 10s] 將響應時間分為 8 個區間。那么每次采集到響應時間,比如 200ms,那么對應的區間 (0, 30ms], (30ms, 100ms], (100ms, 300ms] 的計數都會加 1。最終以響應時間為橫坐標,每個區間的計數值為縱坐標,就能得到 API 調用響應時間的累積直方圖。
Summary
Summary 和 Histogram 類似,它記錄的是監控項的分位數。什么是分位數?舉個例子:假設對于一個 http 請求調用了 100 次,得到 100 個響應時間值。將這 100 個時間響應值按照從小到大的順序排列,那么 0.9 分位數(90% 位置)就代表著第 90 個數。
通過 Histogram 可以近似的計算出百分位數,但是結果并不準確,而 Summary 是在客戶端計算的,比 Histogram 更準確。不過,Summary 計算消耗的資源更多,并且計算的指標不能再獲取平均數或者關聯其他指標,所以它通常獨立使用。
4.3 數據采集流程
target:采集目標,Prometheus Server 會從這些目標設備上采集監控數據
sample: Prometheus Server 從 targets 采集回來的數據樣本
meta label: 執行 relabel 前,target 的原始標簽。可在 Prometheus 的 /targets 頁面或發送 GET /api/v1/targets 請求查看。
4.3.1 relabel (targets 標簽修改/過濾)
relabel 是 Prometheus 提供的一個針對 target 的功能,relabel 發生 Prometheus Server 從 target 采集數據之前,可以對 target 的標簽進行修改或者使用標簽進行 target 篩選。注意以下幾點:
Prometheus 在 relabel 步驟默認會為 target 新增一個名為 instance 的標簽,并設置成 “address” 標簽的值;(下圖instance標簽值為address標簽值)
在 relabel 結束后,以 “__” 開頭的標簽不會被存儲到磁盤;
meta label 會一直保留在內存中,直到 target 被移除。
relabel 配置
relabel 的基本配置項:
source_labels: [, …] #需要進行 relabel 操作的 meta labels
target_label: #relabel 操作的目標標簽,當使用 action 為 “replace” 時會把替換的結果寫入 target_label
regex: #正則表達式,用于在 source_labels 的標簽值中提取匹配的內容。默認為"(.*)"
modulus: #用于獲取源標簽值的哈希的模數
replacement: #regex 可能匹配到多個內容,replacement 指定要使用哪一個匹配內容進行替換,默認為 “$1”,表示使用第一個匹配的內容
action: <relabel_action> #定義對 source_labels 進行何種操作,默認為 “replace”
scrape_configs:
??- job_name: prometheus
????relabel_configs:
???? - source_labels: ["__address__"] #我們要替換的 meta label 為"__address__"
?????? target_label: "host" #給 targets 新增一個名為 "host" 的標簽
?????? regex: "(.*):(.*)" #將匹配的內容分為兩部分 groups--> (host):(port)
?????? replacement: $1 #將匹配的 host 第一個內容設置為新標簽的值
?????? action: replace
五 、Recording rules案例分析
recording rules作用
recording rules 是提前設置好一個比較花費大量時間運算或經常運算的表達式,其結果保存成一組新的時間序列數據。當需要查詢的時候直接會返回已經計算好的結果,這樣會比直接查詢快,也減輕了PromQl的計算壓力,同時對可視化查詢的時候也很有用,可視化展示每次只需要刷新重復查詢相同的表達式即可。
配置示范如下:
- expr: node_cpu_seconds_total * on (node) group_left(type) ecms_node_type * on (node) group_left(label_cloud_product) kube_node_labels
record: ecms_node_cpu_seconds_total
- expr: avg by (mode,node_name)(irate(ecms_node_cpu_seconds_total{label_cloud_product=""}[5m]) OR irate(ecms_node_cpu_seconds_total{label_cloud_product="enabled",type="Physical"}[5m])) * 100
record: ecms_node_cpu_utilization
ecms_node_cpu_seconds_total:
expr_rules 為:
node_cpu_seconds_total * on (node) group_left(type) ecms_node_type * on (node) group_left(label_cloud_product) kube_node_labels
將這個expr拆分為三塊:
1.node_cpu_seconds_total
2.on (node) group_left(type) ecms_node_type
3.on (node) group_left(label_cloud_product) kube_node_labels
在node_cpu_seconds_total側包括ecms_node_type的type標簽
其中:on表示指定要參與匹配的標簽,ignoring表示忽視不參與匹配的標簽
表示kube_node_labels,ecms_node_type,node_cpu_seconds_total 這三個metric用僅用node標簽匹配,其他標簽不參與。根據node的匹配度,然后把label_cloud_product和type標簽,添加到node_cpu_seconds_total中。這邊的group_left的有點類似sql中的left join的意思。即node_cpu_seconds_total中如存在node標簽,而在ecms_node_type和kube_node_labels不存在的話,這個sample是要匹配上的,而不是廢棄。
這邊的SQL語法可以學習下 PromQL 語法
ecms_node_cpu_utilization:
這個較為簡單,表示ecms_node_cpu_seconds_total這個metric的標簽為label_cloud_product為空的sample,或者是標簽為label_cloud_product=enable,type="Physical"的sample5分鐘采樣的irate算法,最后按照mode,node_name求平均值
irate算法
irate 適用于變化頻率高的 counter 類型數據,計算范圍向量中時間序列的每秒平均增長率。基于最后兩個數據點。當 counter 出現單調性中斷會自動進行調整,與 rate 不同的是,irate 只會選取時間范圍內最近的兩個點計算,當選定的時間范圍內僅包含兩個數據點時,不考慮外推情況,rate 和 irate 并無明顯區別。
group_left用法:
Many-to-one / one-to-many 向量匹配
這種匹配模式下,某一邊會有多個元素跟另一邊的元素匹配。這時就需要使用 group_left 或 group_right 組修飾符來指明哪邊匹配元素較多,左邊多則用 group_left,右邊多則用 group_right。其語法如下:
<vector expr> <bin-op> ignoring(<label list>) group_left(<label list>) <vector expr>
<vector expr> <bin-op> ignoring(<label list>) group_right(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) group_left(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) group_right(<label list>) <vector expr>
參考:
https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/
https://prometheus.io/docs/prometheus/latest/querying/functions/
https://blog.csdn.net/ActionTech/article/details/105786785
https://github.com/chenjiandongx/prometheus101