Prometheus 查詢語言的最基本功能之一是實時彙總時間序列數據。GitLab基礎架構團隊的傑出工程師 Andrew Newdigate 認爲 Prometheus 查詢語言也能夠用於檢測時間序列數據中的異常。本博客文章解釋了異常檢測如何與 Prometheus 一塊兒工做,幷包括您須要在本身的系統上親自嘗試的代碼片斷。php
爲何異常檢測有效?html
異常檢測對 GitLab 很是重要的四個關鍵緣由:git
診斷事件:咱們能夠快速找出哪些服務執行超出正常範圍,並減小檢測事件的平均時間(MTTD),從而更快地解決問題。web
檢測應用程序性能迴歸:例如,若是引入 n+1 迴歸,發現一個服務以很高的速率調用另外一個服務,能夠迅速找到問題並加以解決。api
識別並解決濫用問題:GitLab 提供免費計算(GitLab CI/CD)和託管(GitLab Pages),會被一小部分用戶加以利用。安全
安全性:異常檢測對於發現 GitLab 時間序列數據中的異常趨勢相當重要。網絡
因爲以上以及其餘許多緣由,Andrew 研究了是否能夠僅經過使用 Prometheus 查詢和規則對 GitLab 時間序列數據執行異常檢測。架構
正確的聚合級別是什麼?ide
首先,時間序列數據必須正確聚合。儘管能夠將相同的技術應用到許多其餘類型的指標,Andrew 使用了標準計數器 http_requests_total 做爲數據源進行演示。函數
http_requests_total{ job="apiserver", method="GET", controller="ProjectsController", status_code="200", environment="prod" }
上述示例指標有一些額外的維度:method,controller,status_code,environment,如同 Prometheus 添加的維度 instance 和 job 同樣。
接下來,您必須爲正在使用的數據選擇正確的聚合級別。這有點像「金髮姑娘問題」-太多,太少仍是恰到好處-但這對於發現異常相當重要。過多地彙總數據,數據將縮減爲太小的維度,從而產生兩個潛在的問題:
可能會錯過真正的異常,由於聚合隱藏了數據子集中出現的問題。
若是確實檢測到異常,則不對異常進行更多調查,則很難將其歸因於系統的特定部分。
可是,聚合的數據匯太少,最終可能會獲得一系列樣本量很是小的數據。如此可能致使誤報,並可能將真實數據標記爲離羣值(outliers)。
恰到好處:咱們的經驗代表,正確的聚合級別是服務級別,所以咱們將 job 和 environment 標籤標籤包括進來,但刪除了其餘維度。在本演講的如下部分中使用的數據聚合包括:job、http requests、五分鐘速率(基本上是五分鐘窗口中覆蓋 job 和 environment 之上的速率)。
- record: job:http_requests:rate5m expr: sum without(instance, method, controller, status_code) (rate(http_requests_total[5m])) # --> job:http_requests:rate5m{job="apiserver", environment="prod"} 21321 # --> job:http_requests:rate5m{job="gitserver", environment="prod"} 2212 # --> job:http_requests:rate5m{job="webserver", environment="prod"} 53091
使用 z-score 進行異常檢測
一些統計學的主要原理能夠應用於 Prometheus 檢測異常。
若是知道 Prometheus 序列的平均值和標準誤差(σ),則可使用該系列中的任何樣原本計算 z-score。z-score 表示爲:與平均值的標準誤差值。所以 z-score 爲 0 表示 z-score 與具備正態分佈的數據的平均值相同,而 z-score 爲 1 則相對於平均值爲 1.0σ,依此類推。
假設基礎數據是正態分佈的,則 99.7% 的樣本的 z-score 應介於 0 到 3 之間。z-score 距離 0 越遠,它越不可能出現。咱們將此特性應用於檢測 Prometheus 序列中的異常。
使用樣本數量較大的數據計算指標的平均值和標準誤差。在此示例中,咱們使用了一週的數據。若是假設咱們每分鐘評估一次記錄規則,那麼一週的時間,能得到 10,000 多個樣本。
# Long-term average value for the series - record: job:http_requests:rate5m:avg_over_time_1w expr: avg_over_time(job:http_requests:rate5m[1w]) # Long-term standard deviation for the series - record: job:http_requests:rate5m:stddev_over_time_1w expr: stddev_over_time(job:http_requests:rate5m[1w])
一旦有了聚合的平均值和標準差,就能夠計算 Prometheus 查詢的 z-score。
# Z-Score for aggregation ( job:http_requests:rate5m - job:http_requests:rate5m:avg_over_time_1w ) / job:http_requests:rate5m:stddev_over_time_1w
根據正態分佈的統計原理,咱們能夠假設任何超出大約 +3 到 -3 範圍的值都是異常。咱們能夠圍繞這一原則創建警報。例如,當聚合超出此範圍超過五分鐘時,咱們將收到警報。
GitLab.com 頁面服務 48 小時的 RPS, ±3 z-score 區域爲綠色
z-score 在圖形上難以解釋,由於它們沒有度量單位。可是此圖表上的異常很容易檢測。出如今綠色區域(表示 z-score 在 +3 或 -3 範圍內)以外的任何值都是異常。
若是不是正態分佈怎麼辦?
可是,請稍等:咱們大躍進的假設潛在的聚合具備正態分佈。若是咱們使用非正態分佈的數據計算 z-score,結果將不正確。有許多統計技術能夠測試您的數據是否爲正態分佈,可是最好的選擇是測試您的潛在數據的 z-score 約爲 +4 到 -4。
( max_over_time(job:http_requests:rate5m[1w]) - avg_over_time(job:http_requests:rate5m[1w]) ) / stddev_over_time(job:http_requests:rate5m[1w]) # --> {job="apiserver", environment="prod"} 4.01 # --> {job="gitserver", environment="prod"} 3.96 # --> {job="webserver", environment="prod"} 2.96 ( min_over_time(job:http_requests:rate5m[1w]) - avg_over_time(job:http_requests:rate5m[1w]) ) / stddev_over_time(job:http_requests:rate5m[1w]) # --> {job="apiserver", environment="prod"} -3.8 # --> {job="gitserver", environment="prod"} -4.1 # --> {job="webserver", environment="prod"} -3.2
兩個 Prometheus 查詢測試 z-score 的最小和最大值。
若是結果返回的範圍是 +20 到 -20,則尾巴太長,結果將傾斜。還要記住,這須要在聚合而不是非聚合的序列上運行。可能沒有正態分佈的指標包括諸如錯誤率、等待時間、隊列長度等,可是不管如何,在固定閾值下告警,許多這些指標都趨向於工做的很好。
使用季節性檢測異常
儘管時間序列數據爲正態分佈時,計算 z-score 效果很好,可是還有第二種方法能夠產生更準確的異常檢測結果。季節性是時間序列指標的一個特徵,其中該指標會經歷按期且可預測的變化,這些變化會在每一個週期重複出現。
週一至週日連續四個星期的每秒 Gitaly 請求(RPS)
該圖說明了連續四周的週一到週日的 7 天中 Gitaly 的 RPS(每秒請求數)速率。七天範圍稱爲「偏移」,表示須要度量的模式。圖上的每一個星期都有不一樣的顏色。數據的季節性由圖表中所示趨勢的一致性表示 —— 每一個星期一早晨,RPS 速率都會上升,而在星期五晚上,RPS 速率會逐漸降低,每週如此。
經過利用時間序列數據中的季節性,能夠建立更準確的預測,從而更好地進行異常檢測。
如何利用季節性?
使用 Prometheus 計算季節性,須要在一些不一樣的統計原理上迭代。
在第一次迭代中,咱們經過將目前滾動的一週的增加趨勢(注:平均值)與前一週的值相加來計算。經過從目前滾動的一週平均值中減去上週的滾動一週平均值來計算增加趨勢。
- record: job:http_requests:rate5m_prediction expr: > job:http_requests:rate5m offset 1w # Value from last period + job:http_requests:rate5m:avg_over_time_1w # One-week growth trend - job:http_requests:rate5m:avg_over_time_1w offset 1w
第一次迭代有點狹窄;咱們使用本週和上週的五分鐘窗口來得出咱們的預測。
在第二次迭代中,將上週的四個小時平均值做爲平均值,並將其與本週進行比較,以擴大範圍。所以,若是要預測一個星期一上午 8 點的指標值,不是使用一週前的相同五分鐘窗口,而是使用前一週早上的上午 6 點至上午 10 點的指標平均值。
- record: job:http_requests:rate5m_prediction expr: > avg_over_time(job:http_requests:rate5m[4h] offset 166h) # Rounded value from last period + job:http_requests:rate5m:avg_over_time_1w # Add 1w growth trend - job:http_requests:rate5m:avg_over_time_1w offset 1w
在查詢中使用 166 個小時而不是一週,由於要根據一天中的當前時間使用四個小時,所以須要將偏移減小兩個小時。
兩週的 Gitaly 服務 RPS(黃色)vs 預測(藍色)
將實際的 Gitaly RPS(黃色)與 預測(藍色)進行比較代表,計算至關準確。可是,這種方法有缺陷。由於 5 月 1 日是國際勞動節,一個許多國家慶祝的節日,GitLab 的使用量低於日常的星期三。因爲增加率是由前一週的使用狀況決定的,所以咱們對下週(5 月 8 日,星期三)RPS 的預測會比 若是 5 月 1 日(星期三)沒有假期更低。
能夠經過在 5 月 1 日(星期三)以前連續三週(以前的星期三,再以前的星期三和三週以前的星期三)進行三個預測來解決此問題。查詢保持不變,但偏移量已調整。
- record: job:http_requests:rate5m_prediction expr: > quantile(0.5, label_replace( avg_over_time(job:http_requests:rate5m[4h] offset 166h) + job:http_requests:rate5m:avg_over_time_1w - job:http_requests:rate5m:avg_over_time_1w offset 1w , "offset", "1w", "", "") or label_replace( avg_over_time(job:http_requests:rate5m[4h] offset 334h) + job:http_requests:rate5m:avg_over_time_1w - job:http_requests:rate5m:avg_over_time_1w offset 2w , "offset", "2w", "", "") or label_replace( avg_over_time(job:http_requests:rate5m[4h] offset 502h) + job:http_requests:rate5m:avg_over_time_1w - job:http_requests:rate5m:avg_over_time_1w offset 3w , "offset", "3w", "", "") ) without (offset)
三個星期三的三個預測與實際 5 月 8 日(星期三,國際勞動節以後的一週)的 Gitaly RPS
在該圖上,繪製了 5 月 8 日星期三和 5 月 8 日以前連續三個星期的三個預測。能夠看到其中兩個預測是好的,可是 5 月 1 日的預測仍遠未達到基準。
並且,咱們不須要三個預測,咱們想要一個預測。取平均值是不可行的,由於它將被傾斜的 5 月 1 日 RPS 數據所稀釋。相反,咱們要計算中位數。Prometheus 沒有中位數查詢,但可使用分位數聚合來代替中位數。該方法的一個問題是,試圖在一個聚合中包括三個系列,而這三個系列實際上在三週內都是相同的系列。換句話說,它們都具備相同的標籤,所以鏈接它們很棘手。爲避免混淆,咱們建立了一個名爲 offset 的標籤,並使用 label-replace 函數爲三個星期添加 offset。接下來,在分位數聚合中,將其去除,以得到了三個中間值。
- record: job:http_requests:rate5m_prediction expr: > quantile(0.5, label_replace( avg_over_time(job:http_requests:rate5m[4h] offset 166h) + job:http_requests:rate5m:avg_over_time_1w - job:http_requests:rate5m:avg_over_time_1w offset 1w , "offset", "1w", "", "") or label_replace( avg_over_time(job:http_requests:rate5m[4h] offset 334h) + job:http_requests:rate5m:avg_over_time_1w - job:http_requests:rate5m:avg_over_time_1w offset 2w , "offset", "2w", "", "") or label_replace( avg_over_time(job:http_requests:rate5m[4h] offset 502h) + job:http_requests:rate5m:avg_over_time_1w - job:http_requests:rate5m:avg_over_time_1w offset 3w , "offset", "3w", "", "") ) without (offset)
如今,從三個聚合系列中得出中值的預測更加準確。
中位數預測與實際 Gitaly RPS 的比較,5 月 8 日(星期三,國際勞動節以後的一週)
怎麼知道預測是真正準確的?
爲了測試預測的準確性,能夠返回 z-score。可使用 z-score 來測量樣本與標準誤差預測值之間的差距。偏離預測的標準誤差越多,則特定值是異常可能性就越大。
Gitaly 服務的預測正常範圍 ±1.5σ
咱們能夠更新 Grafana 圖表以使用季節性預測而不是每週滾動平均值。一天中特定時間的正常範圍以綠色陰影顯示。任何落在綠色陰影區域以外的東西都被認爲是異常值。在這種狀況下,離羣值發生在週日下午,此時咱們的雲提供商遇到了一些網絡問題。在咱們的預測的任一側使用±2σ的邊界是肯定季節性預測的異常值的一種很好的方法。
如何使用 Prometheus 設置警報
若是要爲異常事件設置警報,能夠對 Prometheus 應用一個很是簡單的規則,該規則檢查指標的 z-score 是否在標準誤差 +2 或 -2 之間。
- alert: RequestRateOutsideNormalRange expr: > abs( ( job:http_requests:rate5m - job:http_requests:rate5m_prediction ) / job:http_requests:rate5m:stddev_over_time_1w ) > 2 for: 10m labels: severity: warning annotations: summary: Requests for job { { $labels.job }} are outside of expected operating parameters
在 GitLab,咱們使用了自定義路由規則,該規則會在檢測到任何異常時 pings Slack,但不會尋呼值班的支持人員。
總結
Prometheus 可用於某些類型的異常檢測
正確級別的數據聚合是異常檢測的關鍵
若是數據具備正態分佈,則 z-score 是一種有效的方法
季節性指標能夠爲異常檢測提供出色的結果
譯文連接:https://www.cyningsun.com/01-22-2020/use-prometheus-for-anomaly-detection.html
Kubernetes管理員認證(CKA)培訓
本次CKA培訓將於11月20到22日在北京開課,培訓基於最新考綱,經過線下授課、考題解讀、模擬演練等方式,幫助學員快速掌握Kubernetes的理論知識和專業技能,並針對考試作特別強化訓練,讓學員能從容面對CKA認證考試,使學員既能掌握Kubernetes相關知識,又能經過CKA認證考試,學員可屢次參加培訓,直到經過認證。點擊下方圖片或者閱讀原文連接查看詳情。