Kubernetes 監控--PromQL
Prometheus 通過指標名稱(metrics name)以及對應的一組標籤(label)唯一定義一條時間序列。指標名稱反映了監控樣本的基本標識,而 label 則在這個基本特徵上為採集到的資料提供了多種特徵維度。使用者可以基於這些特徵維度過濾、聚合、統計從而產生新的計算後的一條時間序列。
PromQL 是 Prometheus 內建的資料查詢語言,其提供對時間序列資料豐富的查詢,聚合以及邏輯運算能力的支援。並且被廣泛應用在 Prometheus 的日常應用當中,包括對資料查詢、視覺化、告警處理。可以這麼說,PromQL 是 Prometheus 所有應用場景的基礎,理解和掌握 PromQL 是我們使用 Prometheus 必備的技能。
時間序列
前面我們通過 node-exporter 暴露的 metrics 服務,Prometheus 可以採集到當前主機所有監控指標的樣本資料。例如:
# HELP node_cpu_seconds_total Seconds the cpus spent in each mode. # TYPE node_cpu_seconds_total counter node_cpu_seconds_total{cpu="0",mode="idle"} 6.62885731e+06 # HELP node_load1 1m load average. # TYPE node_load1 gauge node_load1 2.29
其中非 #
開頭的每一行表示當前 node-exporter 採集到的一個監控樣本:node_cpu_seconds_total 和 node_load1 表明了當前指標的名稱、大括號中的標籤則反映了當前樣本的一些特徵和維度、浮點數則是該監控樣本的具體值。
Prometheus 會將所有采集到的樣本資料以時間序列的方式儲存在記憶體資料庫中,並且定時儲存到硬碟上。時間序列是按照時間戳和值的序列順序存放的,我們稱之為向量(vector),每條時間序列通過指標名稱(metrics name)和一組標籤集(labelset)命名。如下所示,可以將時間序列理解為一個以時間為 X 軸的數字矩陣:
^ │ . . . . . . . . . . . . . . . . . . . node_cpu_seconds_total{cpu="cpu0",mode="idle"} │ . . . . . . . . . . . . . . . . . . . node_cpu_seconds_total{cpu="cpu0",mode="system"} │ . . . . . . . . . . . . . . . . . . node_load1{} │ . . . . . . . . . . . . . . . . . . v <------------------ 時間 ---------------->
在時間序列中的每一個點稱為一個樣本(sample),樣本由以下三部分組成:
- 指標(metric):metric name 和描述當前樣本特徵的 labelsets
- 時間戳(timestamp):一個精確到毫秒的時間戳
- 樣本值(value): 一個 float64 的浮點型資料表示當前樣本的值
如下所示:
<--------------- metric ---------------------><-timestamp -><-value->
http_request_total{status="200", method="GET"}@1434417560938 => 94355
http_request_total{status="200", method="GET"}@1434417561287 => 94334
http_request_total{status="404", method="GET"}@1434417560938 => 38473
http_request_total{status="404", method="GET"}@1434417561287 => 38544
http_request_total{status="200", method="POST"}@1434417560938 => 4748
http_request_total{status="200", method="POST"}@1434417561287 => 4785
在形式上,所有的指標(Metric)都通過如下格式表示:
<metric name>{<label name> = <label value>, ...}
- 指標的名稱(metric name)可以反映被監控樣本的含義(比如,http_request_total - 表示當前系統接收到的 HTTP 請求總量)。指標名稱只能由 ASCII 字元、數字、下劃線以及冒號組成並必須符合正則表示式
[a-zA-Z_:][a-zA-Z0-9_:]*
。 - 標籤(label)反映了當前樣本的特徵維度,通過這些維度 Prometheus 可以對樣本資料進行過濾,聚合等。標籤的名稱只能由 ASCII 字元、數字以及下劃線組成並滿足正則表示式 [a-zA-Z_][a-zA-Z0-9_]*。
每個不同的 metric_name和 label 組合都稱為時間序列,在 Prometheus 的表示式語言中,表示式或子表示式包括以下四種類型之一:
- 瞬時向量(Instant vector):一組時間序列,每個時間序列包含單個樣本,它們共享相同的時間戳。也就是說,表示式的返回值中只會包含該時間序列中的最新的一個樣本值。而相應的這樣的表示式稱之為瞬時向量表示式。
- 區間向量(Range vector):一組時間序列,每個時間序列包含一段時間範圍內的樣本資料,這些是通過將時間選擇器附加到方括號中的瞬時向量(例如[5m]5分鐘)而生成的。
- 標量(Scalar):一個簡單的數字浮點值。
- 字串(String):一個簡單的字串值。
所有這些指標都是 Prometheus 定期從 metrics 介面那裡採集過來的。採集的間隔時間的設定由 prometheus.yaml 配置中的 scrape_interval 指定。最多抓取間隔為30秒,這意味著至少每30秒就會有一個帶有新時間戳記錄的新資料點,這個值可能會更改,也可能不會更改,但是每隔 scrape_interval 都會產生一個新的資料點。
指標型別
從儲存上來講所有的監控指標 metric 都是相同的,但是在不同的場景下這些 metric 又有一些細微的差異。 例如,在 Node Exporter 返回的樣本中指標 node_load1 反應的是當前系統的負載狀態,隨著時間的變化這個指標返回的樣本資料是在不斷變化的。而指標 node_cpu_seconds_total 所獲取到的樣本資料卻不同,它是一個持續增大的值,因為其反應的是 CPU 的累計使用時間,從理論上講只要系統不關機,這個值是會一直變大。
為了能夠幫助使用者理解和區分這些不同監控指標之間的差異,Prometheus 定義了4種不同的指標型別:Counter(計數器)、Gauge(儀表盤)、Histogram(直方圖)、Summary(摘要)。
在 node-exporter 返回的樣本資料中,其註釋中也包含了該樣本的型別。例如:
# HELP node_cpu_seconds_total Seconds the cpus spent in each mode.
# TYPE node_cpu_seconds_total counter
node_cpu_seconds_total{cpu="cpu0",mode="idle"} 362812.7890625
Counter
Counter (只增不減的計數器) 型別的指標其工作方式和計數器一樣,只增不減。常見的監控指標,如 http_requests_total、node_cpu_seconds_total 都是 Counter 型別的監控指標。
Counter 是一個簡單但又強大的工具,例如我們可以在應用程式中記錄某些事件發生的次數,通過以時間序列的形式儲存這些資料,我們可以輕鬆的瞭解該事件產生的速率變化。PromQL 內建的聚合操作和函式可以讓使用者對這些資料進行進一步的分析,例如,通過 rate() 函式獲取 HTTP 請求量的增長率:
rate(http_requests_total[5m])
查詢當前系統中,訪問量前 10 的 HTTP 請求:
topk(10, http_requests_total)
Gauge
與 Counter 不同,Gauge(可增可減的儀表盤)型別的指標側重於反應系統的當前狀態。因此這類指標的樣本資料可增可減。常見指標如:node_memory_MemFree_bytes(主機當前空閒的記憶體大小)、node_memory_MemAvailable_bytes(可用記憶體大小)都是 Gauge 型別的監控指標。通過 Gauge 指標,使用者可以直接檢視系統的當前狀態:
node_memory_MemFree_bytes
對於 Gauge 型別的監控指標,通過 PromQL 內建函式 delta() 可以獲取樣本在一段時間範圍內的變化情況。例如,計算 CPU 溫度在兩個小時內的差異:
delta(cpu_temp_celsius{host="zeus"}[2h])
還可以直接使用 predict_linear() 對資料的變化趨勢進行預測。例如,預測系統磁碟空間在4個小時之後的剩餘情況:
predict_linear(node_filesystem_free_bytes[1h], 4 * 3600)
Histogram 和 Summary
除了 Counter 和 Gauge 型別的監控指標以外,Prometheus 還定義了 Histogram 和 Summary 的指標型別。Histogram 和 Summary 主用用於統計和分析樣本的分佈情況。
在大多數情況下人們都傾向於使用某些量化指標的平均值,例如 CPU 的平均使用率、頁面的平均響應時間,這種方式也有很明顯的問題,以系統 API 呼叫的平均響應時間為例:如果大多數 API 請求都維持在 100ms 的響應時間範圍內,而個別請求的響應時間需要 5s,那麼就會導致某些 WEB 頁面的響應時間落到中位數上,而這種現象被稱為長尾問題。
為了區分是平均的慢還是長尾的慢,最簡單的方式就是按照請求延遲的範圍進行分組。例如,統計延遲在 0~10ms 之間的請求數有多少而 10~20ms 之間的請求數又有多少。通過這種方式可以快速分析系統慢的原因。Histogram 和 Summary 都是為了能夠解決這樣的問題存在的,通過 Histogram 和Summary 型別的監控指標,我們可以快速瞭解監控樣本的分佈情況。
例如,指標 prometheus_tsdb_wal_fsync_duration_seconds 的指標型別為 Summary。它記錄了 Prometheus Server 中 wal_fsync 的處理時間,通過訪問 Prometheus Server 的 /metrics 地址,可以獲取到以下監控樣本資料:
# HELP prometheus_tsdb_wal_fsync_duration_seconds Duration of WAL fsync.
# TYPE prometheus_tsdb_wal_fsync_duration_seconds summary
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.5"} 0.012352463
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.9"} 0.014458005
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.99"} 0.017316173
prometheus_tsdb_wal_fsync_duration_seconds_sum 2.888716127000002
prometheus_tsdb_wal_fsync_duration_seconds_count 216
從上面的樣本中可以得知當前 Prometheus Server 進行 wal_fsync 操作的總次數為216次,耗時2.888716127000002s。其中中位數(quantile=0.5)的耗時為0.012352463,9分位數(quantile=0.9)的耗時為0.014458005s。
在 Prometheus Server 自身返回的樣本資料中,我們還能找到型別為 Histogram 的監控指標prometheus_tsdb_compaction_chunk_range_seconds_bucket:
# HELP prometheus_tsdb_compaction_chunk_range_seconds Final time range of chunks on their first compaction
# TYPE prometheus_tsdb_compaction_chunk_range_seconds histogram
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="100"} 71
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="400"} 71
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="1600"} 71
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="6400"} 71
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="25600"} 405
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="102400"} 25690
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="409600"} 71863
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="1.6384e+06"} 115928
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="6.5536e+06"} 2.5687892e+07
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="2.62144e+07"} 2.5687896e+07
prometheus_tsdb_compaction_chunk_range_seconds_bucket{le="+Inf"} 2.5687896e+07
prometheus_tsdb_compaction_chunk_range_seconds_sum 4.7728699529576e+13
prometheus_tsdb_compaction_chunk_range_seconds_count 2.5687896e+07
與 Summary 型別的指標相似之處在於 Histogram 型別的樣本同樣會反應當前指標的記錄的總數(以 _count 作為字尾)以及其值的總量(以 _sum 作為字尾)。不同在於 Histogram 指標直接反應了在不同區間內樣本的個數,區間通過標籤 le 進行定義。
查詢
當 Prometheus 採集到監控指標樣本資料後,我們就可以通過 PromQL 對監控樣本資料進行查詢。基本的 Prometheus 查詢的結構非常類似於一個 metric 指標,以指標名稱開始。
查詢結構
比如只查詢 node_cpu_seconds_total 則會返回所有采集節點的所有型別的 CPU 時長資料,當然如果資料量特別特別大的時候,直接在 Grafana 執行該查詢操作的時候,則可能導致瀏覽器崩潰,因為它同時需要渲染的資料點太多。
接下來,可以使用標籤進行過濾查詢,標籤過濾器支援4種運算子:
-
=
等於 -
!=
不等於 -
=~
匹配正則表示式 -
!~
與正則表示式不匹配
標籤過濾器都位於指標名稱後面的{}內,比如過濾 master 節點的 CPU 使用資料可用如下查詢語句:
node_cpu_seconds_total{instance="ydzs-master"}
正則匹配: PromQL 查詢語句中的正則表示式匹配使用 RE2語法。
此外我們還可以使用多個標籤過濾器,以逗號分隔。多個標籤過濾器之間是 AND 的關係,所以使用多個標籤進行過濾,返回的指標資料必須和所有標籤過濾器匹配。
例如如下查詢語句將返回所有以 ydzs-為字首的節點的並且是 idle 模式下面的節點 CPU 使用時長指標:
node_cpu_seconds_total{instance=~"ydzs-.*", mode="idle"}
範圍選擇器
我們可以通過將時間範圍選擇器([])附加到查詢語句中,指定為每個返回的區間向量樣本值中提取多長的時間範圍。每個時間戳的值都是按時間倒序記錄在時間序列中的,該值是從時間範圍內的時間戳獲取的對應的值。
時間範圍通過數字來表示,單位可以使用以下其中之一的時間單位:
- s - 秒
- m - 分鐘
- h - 小時
- d - 天
- w - 周
- y - 年
比如 node_cpu_seconds_total{instance="ydzs-master", mode="idle"} 這個查詢語句,如果新增上 [1m] 這個時間範圍選擇器,則我們可以得到如下所示的資訊:
可以看到上面的兩個時間序列都有4個值,這是因為我們 Prometheus 中配置的抓取間隔是15秒,所以,我們從圖中的 @ 符號後面的時間戳可以看出,它們之間的間隔基本上就是15秒。
但是現在如果我們在 Prometheus 的頁面中查詢上面的語句,然後切換到 Graph 選項卡的時候,則會出現如下所示的錯誤資訊:
這是因為現在每一個時間序列中都有多個時間戳多個值,所以沒辦法渲染,必須是標量或者瞬時向量才可以繪製圖形。
不過通常區間向量都會應用一個函式後變成可以繪製的瞬時向量,Prometheus 中對瞬時向量和區間向量有很多操作的函式,不過對於區間向量來說最常用的函式並不多,使用最頻繁的有如下幾個函式:
- rate(): 計算整個時間範圍內區間向量中時間序列的每秒平均增長率
- irate(): 僅使用時間範圍中的最後兩個資料點來計算區間向量中時間序列的每秒平均增長率,irate 只能用於繪製快速變化的序列,在長期趨勢分析或者告警中更推薦使用 rate 函式
- increase(): 計算所選時間範圍內時間序列的增量,它基本上是速率乘以時間範圍選擇器中的秒數
我們選擇的時間範圍持續時間將確定圖表的粒度,比如,持續時間 [1m] 會給出非常尖銳的圖表,從而很難直觀的顯示出趨勢來,看起來像這樣:
對於一個一小時的圖表,[5m] 顯示的圖表看上去要更加合適一些,更能顯示出 CPU 使用的趨勢:
對於更長的時間跨度,可能需要設定更長的持續時間,以便消除波峰並獲得更多的長期趨勢圖表。我們可以簡單比較持續時間為[5m] 和 [30m] 的一天內的圖表:
有的時候可能想要檢視5分鐘前或者昨天一天的區間內的樣本資料,這個時候我們就需要用到位移操作了,位移操作的關鍵字是 offset,比如我們可以查詢30分鐘之前的 master 節點 CPU 的空閒指標資料:
node_cpu_seconds_total{instance="ydzs-master", mode="idle"} offset 30m
需要注意的是 offset 關鍵字需要緊跟在選擇器({})後面。
同樣位移操作也適用於區間向量,比如我們要查詢昨天的前5分鐘的 CPU 空閒增長率:
關聯查詢
Prometheus 沒有提供類似與 SQL 語句的關聯查詢的概念,但是我們可以通過在 Prometheus 上使用 運算子 來組合時間序列,可以應用於多個時間序列或標量值的常規計算、比較和邏輯運算。
如果將運算子應用於兩個瞬時向量,則它將僅應用於匹配的時間序列,當且僅當時間序列具有完全相同的標籤集的時候,才認為是匹配的。當表示式左側的每個序列和右側的一個序列完全匹配的時候,在序列上使用這些運算子才可以實現一對一匹配。
比如如下的兩個瞬時向量:
node_cpu_seconds_total{instance="ydzs-master", cpu="0", mode="idle"}
和
node_cpu_seconds_total{instance="ydzs-node1", cpu="0", mode="idle"}
如果我們對這兩個序列做加法運算來嘗試獲取 master 和 node1 節點的總的空閒 CPU 時長,則不會返回任何內容了:
這是因為這兩個時間序列沒有完全匹配標籤。我們可以使用 on 關鍵字指定只希望在 mode 標籤上進行匹配,就可以計算出結果來:
需要注意的是新的瞬時向量包含單個序列,其中僅包含 on 關鍵字中指定的標籤。
不過在 Prometheus 中還有很多 聚合操作,所以,如果我們真的想要獲取節點的 CPU 總時長,我們完全不用這麼操作,使用 sum 操作要簡單得多:
sum(node_cpu_seconds_total{mode="idle"}) by (instance)
on 關鍵字只能用於一對一的匹配中,如果是多對一或者一對多的匹配情況下,就不行了,比如我們可以通過 kube-state-metrics 這個工具來獲取 Kubernetes 叢集的各種狀態指標,包括 Pod 的基本資訊,比如我們執行如下所示的查詢語句:
container_cpu_user_seconds_total{namespace="kube-system"} * on (pod) kube_pod_info
就會出現 Error executing query: multiple matches for labels: many-to-one matching must be explicit (group_left/group_right) 這樣的錯誤提示,這是因為左側的序列資料在同一個 Pod 上面有可能會有多條時間序列,所以不能簡單通過 on (pod) 來進行查詢。
要解決這個問題,我們可以使用 group_left 或group_right 關鍵字。這兩個關鍵字將匹配分別轉換為多對一或一對多匹配。左側和右側表示基數較高的一側。因此,group_left 意味著左側的多個序列可以與右側的單個序列匹配。結果是,返回的瞬時向量包含基數較高的一側的所有標籤,即使它們與右側的任何標籤都不匹配。
例如如下所示的查詢語句就可以正常獲取到結果,而且獲取到的時間序列資料包含所有的標籤:
container_cpu_user_seconds_total{namespace="kube-system"} * on (pod) group_left() kube_pod_info
瞬時向量和標量結合
此外我們還可以將瞬時向量和標量值相結合,這個很簡單,就是簡單的數學計算,比如:
node_cpu_seconds_total{instance="ydzs-master"} * 10
會為瞬時向量中每個序列的每個值都乘以10。這對於計算比率和百分比得時候非常有用。
- 除了
*
之外,其他常用的算數運算子當然也支援:+、-、*、/、%、^
。 - 還有其他的比較運算子:
==、!=、>、<、>=、<=
。 - 邏輯運算子:and、or、unless,不過邏輯運算子只能用於瞬時向量之間。
除了這些關於 PromQL 最基本的知識點之外,還有很多相關的使用方法,可以參考官網相關介紹:https://prometheus.io/docs/prometheus/latest/querying/basics/。