1. 程式人生 > 其它 >Kubernetes 監控--Alertmanager

Kubernetes 監控--Alertmanager

前面我們學習 Prometheus 的時候瞭解到 Prometheus 包含一個報警模組,就是我們的 AlertManager,Alertmanager 主要用於接收 Prometheus 傳送的告警資訊,它支援豐富的告警通知渠道,而且很容易做到告警資訊進行去重,降噪,分組等,是一款前衛的告警通知系統。

通過在 Prometheus 中定義告警規則,Prometheus會週期性的對告警規則進行計算,如果滿足告警觸發條件就會向Alertmanager 傳送告警資訊。

在 Prometheus 中一條告警規則主要由以下幾部分組成:

  • 告警名稱:使用者需要為告警規則命名,當然對於命名而言,需要能夠直接表達出該告警的主要內容
  • 告警規則:告警規則實際上主要由 PromQL 進行定義,其實際意義是當表示式(PromQL)查詢結果持續多長時間(During)後出發告警

在 Prometheus 中,還可以通過 Group(告警組)對一組相關的告警進行統一定義。Alertmanager 作為一個獨立的元件,負責接收並處理來自 Prometheus Server 的告警資訊。Alertmanager 可以對這些告警資訊進行進一步的處理,比如當接收到大量重複告警時能夠消除重複的告警資訊,同時對告警資訊進行分組並且路由到正確的通知方,Prometheus 內建了對郵件、Slack 多種通知方式的支援,同時還支援與 Webhook 的整合,以支援更多定製化的場景。例如,目前 Alertmanager 還不支援釘釘,使用者完全可以通過 Webhook 與釘釘機器人進行整合,從而通過釘釘接收告警資訊。同時 AlertManager 還提供了靜默和告警抑制機制來對告警通知行為進行優化。

安裝

從官方文件 https://prometheus.io/docs/alerting/configuration/ 中我們可以看到下載 AlertManager 二進位制檔案後,可以通過下面的命令執行:

$ ./alertmanager --config.file=simple.yml

其中 --config.file 引數是用來指定對應的配置檔案的,由於我們這裡同樣要執行到 Kubernetes 叢集中來,所以我們使用 Docker 映象的方式來安裝,使用的映象是:prom/alertmanager:v0.20.0。

首先,指定配置檔案,同樣的,我們這裡使用一個 ConfigMap 資源物件:(alertmanager-config.yaml)

apiVersion: v1
kind: ConfigMap
metadata:
  name: alert-config
  namespace: kube-mon
data:
  config.yml: |-
    global:
      # 當alertmanager持續多長時間未接收到告警後標記告警狀態為 resolved
      resolve_timeout: 5m
      # 配置郵件傳送資訊
      smtp_smarthost: 'smtp.163.com:25'
      smtp_from: '[email protected]'
      smtp_auth_username: '[email protected]'
      smtp_auth_password: '<郵箱密碼>'
      smtp_hello: '163.com'
      smtp_require_tls: false
    # 所有報警資訊進入後的根路由,用來設定報警的分發策略
    route:
      # 這裡的標籤列表是接收到報警資訊後的重新分組標籤,例如,接收到的報警資訊裡面有許多具有 cluster=A 和 alertname=LatncyHigh 這樣的標籤的報警資訊將會批量被聚合到一個分組裡面
      group_by: ['alertname', 'cluster']
      # 當一個新的報警分組被建立後,需要等待至少 group_wait 時間來初始化通知,這種方式可以確保您能有足夠的時間為同一分組來獲取多個警報,然後一起觸發這個報警資訊。
      group_wait: 30s

      # 相同的group之間傳送告警通知的時間間隔
      group_interval: 30s

      # 如果一個報警資訊已經發送成功了,等待 repeat_interval 時間來重新發送他們,不同型別告警傳送頻率需要具體配置
      repeat_interval: 1h

      # 預設的receiver:如果一個報警沒有被一個route匹配,則傳送給預設的接收器
      receiver: default

      # 上面所有的屬性都由所有子路由繼承,並且可以在每個子路由上進行覆蓋。
      routes:
      - receiver: email
        group_wait: 10s
        match:
          team: node
    receivers:
    - name: 'default'
      email_configs:
      - to: '[email protected]'
        send_resolved: true  # 接受告警恢復的通知
    - name: 'email'
      email_configs:
      - to: '[email protected]'
        send_resolved: true

分組:分組機制可以將詳細的告警資訊合併成一個通知,在某些情況下,比如由於系統宕機導致大量的告警被同時觸發,在這種情況下分組機制可以將這些被觸發的告警合併為一個告警通知,避免一次性接受大量的告警通知,而無法對問題進行快速定位。

這是 AlertManager 的配置檔案,我們先直接建立這個 ConfigMap 資源物件:

$ kubectl apply -f alertmanager-config.yaml
configmap/alert-config created

然後配置 AlertManager 的容器,直接使用一個 Deployment 來進行管理即可,對應的 YAML 資源宣告如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: alertmanager
  namespace: kube-mon
  labels:
    app: alertmanager
spec:
  selector:
    matchLabels:
      app: alertmanager
  template:
    metadata:
      labels:
        app: alertmanager
    spec:
      volumes:
      - name: alertcfg
        configMap:
          name: alert-config
      containers:
      - name: alertmanager
        image: prom/alertmanager:v0.20.0
        imagePullPolicy: IfNotPresent
        args:
        - "--config.file=/etc/alertmanager/config.yml"
        ports:
        - containerPort: 9093
          name: http
        volumeMounts:
        - mountPath: "/etc/alertmanager"
          name: alertcfg
        resources:
          requests:
            cpu: 100m
            memory: 256Mi
          limits:
            cpu: 100m
            memory: 256Mi

這裡我們將上面建立的 alert-config 這個 ConfigMap 資源物件以 Volume 的形式掛載到 /etc/alertmanager 目錄下去,然後在啟動引數中指定了配置檔案 --config.file=/etc/alertmanager/config.yml,然後我們可以來建立這個資源物件:

$ kubectl apply -f alertmanager-deploy.yaml
deployment.apps/alertmanager created

為了可以訪問到 AlertManager,同樣需要我們建立一個對應的 Service 物件:(alertmanager-svc.yaml)

apiVersion: v1
kind: Service
metadata:
  name: alertmanager
  namespace: kube-mon
  labels:
    app: alertmanager
spec:
  selector:
    app: alertmanager
  type: NodePort
  ports:
    - name: web
      port: 9093
      targetPort: http

使用 NodePort 型別也是為了方便測試,建立上面的 Service 這個資源物件:

$ kubectl apply -f alertmanager-svc.yaml
service/alertmanager created

AlertManager 的容器啟動起來後,我們還需要在 Prometheus 中配置下 AlertManager 的地址,讓 Prometheus 能夠訪問到 AlertManager,在 Prometheus 的 ConfigMap 資源清單中新增如下配置:

alerting:
  alertmanagers:
    - static_configs:
      - targets: ["alertmanager:9093"]

更新這個資源物件後,稍等一小會兒,執行 reload 操作即可。

報警規則

現在我們只是把 AlertManager 容器執行起來了,也和 Prometheus 進行了關聯,但是現在我們並不知道要做什麼報警,因為沒有任何地方告訴我們要報警,所以我們還需要配置一些報警規則來告訴我們對哪些資料進行報警。

警報規則允許你基於 Prometheus 表示式語言的表示式來定義報警報條件,並在觸發警報時傳送通知給外部的接收者。

同樣在 Prometheus 的配置檔案中新增如下報警規則配置:

rule_files:
- /etc/prometheus/rules.yml

其中 rule_files 就是用來指定報警規則的,這裡我們同樣將 rules.yml 檔案用 ConfigMap 的形式掛載到 /etc/prometheus 目錄下面即可,比如下面的規則:(alert-rules.yml)

apiVersion: v1
kind: ConfigMap
metadata:
  name: prometheus-config
  namespace: kube-mon
data:
  prometheus.yml: |
    global:
      scrape_interval: 15s
      scrape_timeout: 15s
      evaluation_interval: 30s  # 預設情況下每分鐘對告警規則進行計算
    alerting:
      alertmanagers:
      - static_configs:
        - targets: ["alertmanager:9093"]
    rule_files:
    - /etc/prometheus/rules.yml
  ...... # 省略prometheus其他部分
  rules.yml: |
    groups:
    - name: test-node-mem
      rules:
      - alert: NodeMemoryUsage
        expr: (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) / node_memory_MemTotal_bytes * 100 > 20
        for: 2m
        labels:
          team: node
        annotations:
          summary: "{{$labels.instance}}: High Memory usage detected"
          description: "{{$labels.instance}}: Memory usage is above 20% (current value is: {{ $value }}"

上面我們定義了一個名為 NodeMemoryUsage 的報警規則,一條報警規則主要由以下幾部分組成:

  • alert:告警規則的名稱
  • expr:是用於進行報警規則 PromQL 查詢語句
  • for:評估等待時間(Pending Duration),用於表示只有當觸發條件持續一段時間後才傳送告警,在等待期間新產生的告警狀態為pending
  • labels:自定義標籤,允許使用者指定額外的標籤列表,把它們附加在告警上
  • annotations:指定了另一組標籤,它們不被當做告警例項的身份標識,它們經常用於儲存一些額外的資訊,用於報警資訊的展示之類的

for 屬性: 這個引數主要用於降噪,很多類似響應時間這樣的指標都是有抖動的,通過指定 Pending Duration,我們可以過濾掉這些瞬時抖動,可以讓我們能夠把注意力放在真正有持續影響的問題上。

為了讓告警資訊具有更好的可讀性,Prometheus 支援模板化 label 和 annotations 中的標籤的值,通過 $labels.變數 可以訪問當前告警例項中指定標籤的值,$value 則可以獲取當前 PromQL 表示式計算的樣本值。

為了方便演示,我們將的表示式判斷報警臨界值設定為 20,重新更新 ConfigMap 資源物件,由於我們在 Prometheus 的 Pod 中已經通過 Volume 的形式將 prometheus-config 這個一個 ConfigMap 物件掛載到了 /etc/prometheus 目錄下面,所以更新後,該目錄下面也會出現 rules.yml 檔案,所以前面配置的 rule_files 路徑也是正常的,更新完成後,重新執行 reload 操作,這個時候我們去 Prometheus 的 Dashboard 中切換到 alerts 路徑下面就可以看到有報警配置規則的資料了:

頁面中出現了我們剛剛定義的報警規則資訊,而且報警資訊中還有狀態顯示,一個報警資訊在生命週期內有下面3種狀態:

  • pending: 表示在設定的閾值時間範圍內被激活了
  • firing: 表示超過設定的閾值時間被激活了
  • inactive: 表示當前報警資訊處於非活動狀態

同時對於已經 pending 或者 firing 的告警,Prometheus 也會將它們儲存到時間序列ALERTS{}中。當然我們也可以通過表示式去查詢告警例項:

ALERTS{alertname="<alert name>", alertstate="pending|firing", <additional alert labels>}

樣本值為1表示當前告警處於活動狀態(pending 或者 firing),當告警從活動狀態轉換為非活動狀態時,樣本值則為0。

我們這裡的狀態現在是 firing 就表示這個報警已經被激活了,我們這裡的報警資訊有一個 team=node 這樣的標籤,而最上面我們配置 alertmanager 的時候就有如下的路由配置資訊了:

routes:
- receiver: email
  group_wait: 10s
  match:
    team: node

所以我們這裡的報警資訊會被 email 這個接收器來進行報警,我們上面配置的是郵箱,所以正常來說這個時候我們會收到一封如下的報警郵件:

我們可以看到收到的郵件內容中包含一個 View In AlertManager 的連結,我們同樣可以通過 NodePort 的形式去訪問到 AlertManager 的 Dashboard 頁面:

$ kubectl get svc -n kube-mon
NAME           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)             AGE
alertmanager   NodePort    10.98.1.195     <none>        9093:31194/TCP      141m

然後通過 <任一Node節點>:31194 進行訪問,我們就可以檢視到 AlertManager 的 Dashboard 頁面,在這個頁面中我們可以進行一些操作,比如過濾、分組等等,裡面還有兩個新的概念:Inhibition(抑制) 和 Silences(靜默)。

  • Inhibition:如果某些其他警報已經觸發了,則對於某些警報,Inhibition 是一個抑制通知的概念。例如:一個警報已經觸發,它正在通知整個叢集是不可達的時,Alertmanager 則可以配置成關心這個叢集的其他警報無效。這可以防止與實際問題無關的數百或數千個觸發警報的通知,Inhibition 需要通過上面的配置檔案進行配置。
  • Silences:靜默是一個非常簡單的方法,可以在給定時間內簡單地忽略所有警報。Silences 基於 matchers配置,類似路由樹。來到的警告將會被檢查,判斷它們是否和活躍的 Silences 相等或者正則表示式匹配。如果匹配成功,則不會將這些警報傳送給接收者。

由於全域性配置中我們配置的 repeat_interval: 1h,所以正常來說,上面的測試報警如果一直滿足報警條件(記憶體使用率大於20%)的話,那麼每1小時我們就可以收到一條報警郵件。

一條告警產生後,還要經過 Alertmanager 的分組、抑制處理、靜默處理、去重處理和降噪處理最後再發送給接收者。這個過程中可能會因為各種原因會導致告警產生了卻最終沒有進行通知,可以通過下圖瞭解整個告警的生命週期:

WebHook 接收器

上面我們配置的是 AlertManager 自帶的郵件報警模板,我們也說了 AlertManager 支援很多中報警接收器,比如 slack、微信之類的,其中最為靈活的方式當然是使用 webhook 了,我們可以定義一個 webhook 來接收報警資訊,然後在 webhook 裡面去進行處理,需要傳送怎樣的報警資訊我們自定義就可以。

我這裡實現了一個簡單的 webhook 程式,程式碼倉庫地址:github.com/cnych/alertmanager-dingtalk-hook

當然我們得將上面這個服務部署到叢集中來,對應的資源清單如下:(dingtalk-hook.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dingtalk-hook
  namespace: kube-mon
spec:
  selector:
    matchLabels:
      app: dingtalk-hook
  template:
    metadata:
      labels:
        app: dingtalk-hook
    spec:
      containers:
      - name: dingtalk-hook
        image: cnych/alertmanager-dingtalk-hook:v0.3.2
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 5000
          name: http
        env:
        - name: PROME_URL
          value: k8s.qikqiak.com:30980
        - name: LOG_LEVEL
          value: debug
        - name: ROBOT_TOKEN
          valueFrom:
            secretKeyRef:
              name: dingtalk-secret
              key: token
        - name: ROBOT_SECRET
          valueFrom:
            secretKeyRef:
              name: dingtalk-secret
              key: secret
        resources:
          requests:
            cpu: 50m
            memory: 100Mi
          limits:
            cpu: 50m
            memory: 100Mi

---
apiVersion: v1
kind: Service
metadata:
  name: dingtalk-hook
  namespace: kube-mon
spec:
  selector:
    app: dingtalk-hook
  ports:
  - name: hook
    port: 5000
    targetPort: http

上面我們有一些環境變數配置:

  • ROBOT_TOKEN:釘釘機器人 TOKEN
  • PROME_URL:手動指定跳轉後的 Promethues 地址,預設會是 Pod 的地址
  • LOG_LEVEL:日誌級別,設定成 debug 可以看到 AlertManager WebHook 傳送的資料,方便除錯使用,不需除錯可以不設定該環境變數
  • ROBOT_SECRET:為釘釘機器人的安全設定金鑰,機器人安全設定頁面,加簽一欄下面顯示的 SEC 開頭的字串

上面我們宣告的 ROBOT_TOKEN 和 ROBOT_SECRET 環境變數,由於這是一個相對於私密的資訊,所以我們這裡從一個 Secret 物件中去獲取,通過如下命令建立一個名為 dingtalk-secret 的 Secret 物件,然後部署上面的資源物件即可:

$ kubectl create secret generic dingtalk-secret --from-literal=token=<釘釘群聊的機器人TOKEN> --from-literal=secret=<釘釘群聊機器人的SECRET> -n kube-mon
secret "dingtalk-secret" created
$ kubectl apply -f dingtalk-hook.yaml
deployment.apps "dingtalk-hook" created
service "dingtalk-hook" created
$ kubectl get pods -n kube-mon
NAME                            READY     STATUS      RESTARTS   AGE
dingtalk-hook-c4fcd8cd6-6r2b6   1/1       Running     0          45m
......

部署成功後,現在我們就可以給 AlertManager 配置一個 webhook 了,在上面的配置中增加一個路由接收器

  routes:
  - receiver: webhook
    match:
      filesystem: node
receivers:
- name: 'webhook'
  webhook_configs:
  - url: 'http://dingtalk-hook:5000'
    send_resolved: true

我們這裡配置了一個名為 webhook 的接收器,地址為:http://dingtalk-hook:5000,這個地址當然就是上面我們部署的釘釘的 webhook 的接收程式的 Service 地址。

然後我們可以更新 AlertManager 和 Prometheus 的 ConfigMap 資源物件,更新完成後,隔一會兒執行 reload 操作是更新生效,如果有報警觸發的話,隔一會兒關於這個節點檔案系統的報警就會被觸發了,由於這個報警資訊包含一個team=node 的 label 標籤,所以會被路由到 webhook 這個接收器中,也就是上面我們自定義的這個 dingtalk-hook,觸發後可以觀察這個 Pod 的日誌:

$ kubectl logs -f dingtalk-hook-cc677c46d-gf26f -n kube-mon
 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

2019-12-15 08:11:30,051 DEBUG Starting new HTTPS connection (1): oapi.dingtalk.com:443
2019-12-15 08:11:30,781 DEBUG https://oapi.dingtalk.com:443 "POST /robot/send?access_token=ff5067c95035185a752eb0fe90a1e52fd16f596c8ca89712e18ac2a3e1b7ee89&timestamp=1576397489986&sign=wOggfoW%2BAVgvi2BiHnlKd79Tvjf7S3boRAs1BoDhhTE%3D HTTP/1.1" 200 None
2019-12-15 08:11:30,951 INFO 10.244.2.129 - - [15/Dec/2019 08:11:30] "POST / HTTP/1.1" 200 -

可以看到 POST 請求已經成功了,同時這個時候正常來說就可以收到一條釘釘訊息了:

由於我們程式中是用一個非常簡單的 markdown 形式直接轉發的,所以這裡報警資訊不夠友好,沒關係,有了這個示例我們完全就可以根據自己的需要來定製訊息模板了,可以參考釘釘自定義機器人文件:https://open-doc.dingtalk.com/microapp/serverapi2/qf2nxq

自定義模板

告警通知使用的是預設模版,因為它已經編譯到二進位制包了,所以我們不需要額外配置。如果我們想自定義模版,這又該如何配置呢?

步驟一: 下載官方預設模版

$ wget https://raw.githubusercontent.com/prometheus/alertmanager/master/template/default.tmpl

步驟二: 根據自己的需求修改模版,主要是下面這一段

define "email.default.html" 
.... // 修改內容
end 

步驟三: 修改 alertmanger.yml,新增 templates 配置引數

templates:
- './template/*.tmpl'  #  自定義模版路徑

最後儲存重新載入配置即可。

記錄規則

通過 PromQL 可以實時對 Prometheus 中採集到的樣本資料進行查詢,聚合以及其它各種運算操作。而在某些 PromQL 較為複雜且計算量較大時,直接使用 PromQL 可能會導致 Prometheus 響應超時的情況。這時需要一種能夠類似於後臺批處理的機制在後臺完成這些複雜運算的計算,對於使用者而言只需要查詢這些運算結果即可。Prometheus 通過Recoding Rule 規則支援這種後臺計算的方式,可以實現對複雜查詢的效能優化,提高查詢效率。

在 Prometheus 配置檔案中,我們可以通過 rule_files 定義 recoding rule 規則檔案的訪問路徑。

rule_files:
  [ - <filepath_glob> ... ]

每一個規則檔案通過以下格式進行定義:

groups:
  [ - <rule_group> ]

一個簡單的規則檔案可能是這個樣子的:

groups:
- name: example
  rules:
  - record: job:http_inprogress_requests:sum
    expr: sum(http_inprogress_requests) by (job)

rule_group 的具體配置項如下所示:

# 分組的名稱,在一個檔案中必須是唯一的
name: <string>

# 評估分組中規則的頻率
[ interval: <duration> | default = global.evaluation_interval ]

rules:
  [ - <rule> ... ]

與告警規則一致,一個 group 下可以包含多條規則 rule。

# 輸出的時間序列名稱,必須是一個有效的 metric 名稱
record: <string>

# 要計算的 PromQL 表示式,每個評估週期都是在當前時間進行評估的,結果記錄為一組新的時間序列,metrics 名稱由 record 設定
expr: <string>

# 新增或者覆蓋的標籤
labels:
  [ <labelname>: <labelvalue> ]

根據規則中的定義,Prometheus 會在後臺完成 expr 中定義的 PromQL 表示式計算,並且將計算結果儲存到新的時間序列 record 中,同時還可以通過 labels 標籤為這些樣本新增額外的標籤。

這些規則檔案的計算頻率與告警規則計算頻率一致,都通過 global.evaluation_interval 進行定義:

global:
  [ evaluation_interval: <duration> | default = 1m ]