實現基於 Grafana Loki 的日誌報警

對於生產環境以及一個有追求的運維人員來說,哪怕是毫秒級別的宕機也是不能容忍的。對基礎設施及應用進行適當的日誌記錄和監控非常有助於解決問題,還可以幫助優化成本和資源,以及幫助檢測以後可能會發生的一些問題。前面我們學習使用了 Prometheus 來進行監控報警,但是如果我們使用 Loki 收集日誌是否可以根據採集的日誌來進行報警呢?答案是肯定的,而且有兩種方式可以來實現:Promtail 中的 metrics 階段和 Loki 的 ruler 組件。

測試應用

比如現在我們有一個如下所示的 nginx 應用用於 Loki 日誌報警:

# nginx-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - name: nginx
    port: 80
    protocol: TCP
  selector:
    app: nginx
  type: NodePort

爲方便測試,我們這裏使用 NodePort 類型的服務來暴露應用,直接安裝即可:

$ kubectl apply -f nginx-deploy.yaml
$  kubectl get pods
NAME                           READY   STATUS    RESTARTS      AGE
nginx-5d59d67564-ll9xf         1/1     Running   0             16s
$ kubectl get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP        91d
nginx        NodePort    10.99.153.32   <none>        80:31313/TCP   22s

我們可以通過如下命令來來模擬每隔 10s 訪問 Nginx 應用:

while true; do curl --silent --output /dev/null --write-out '%{http_code}' http://192.168.0.106:31313; sleep 10; echo; done
200
200

metrics 階段

前面我們提到在 Promtail 中通過一系列 Pipeline 來處理日誌,其中就包括一個 metrics 的階段,可以根據我們的需求來增加一個監控指標,這就是我們需要實現的基於日誌的監控報警的核心點,通過結構化日誌,增加監控指標,然後使用 Prometheus 結合 Alertmanager 完成之前我們非常熟悉的監控報警。

首先我們需要安裝 Prometheus 與 Alertmanager,可以手動安裝,也可以使用 Prometheus Operator 的方式,可以參考監控報警章節相關內容,比如這裏我們選擇使用 Prometheus Operator 的方式。

前面我們介紹了幾種 Loki 的部署方式,這裏我們就保留上節微服務模式的 Loki 集羣,接下來我們需要重新配置 Promtail,爲其添加一個 metrics 處理階段,使用如下所示的 values 文件重新安裝。

# ci/metrics-values.yaml
rbac:
  pspEnabled: false
config:
  clients:
    - url: http://loki-loki-distributed-gateway/loki/api/v1/push
  snippets:
    pipelineStages:
    - cri: {}
    - match:
        selector: '{app="nginx"}'
        stages:
        - regex:
            expression: '.*(?P<hits>GET /.*)'
        - metrics:
            nginx_hits:
              type: Counter
              description: "Total nginx requests"
              source: hits
              config:
                action: inc
serviceMonitor:
  enabled: true
  additionalLabels:
    app: prometheus-operator
    release: prometheus

上面最重要的部分就是爲 Promtail 添加了 pipelineStages 配置,用於對日誌行進行轉換,在這裏我們添加了一個 match 的階段,會去匹配具有 app=nginx 這樣的日誌流數據,然後下一個階段是利用正則表達式過濾出包含 GET 關鍵字的日誌行。

在 metrics 指標階段,我們定義了一個 nginx_hits 的指標,Promtail 通過其 /metrics 端點暴露這個自定義的指標數據。這裏我們定義的是一個 Counter 類型的指標,當從 regex 階段匹配上後,這個計數器就會遞增。

爲了在 Prometheus 中能夠這個指標,我們通過 promtail.serviceMonitor.enable=true 開啓了一個 ServiceMonitor。接下來重新更新 Loki 應用,使用如下所示的命令即可:

$ helm upgrade --install loki -n logging -f ci/metrics-values.yaml .

更新完成後會創建一個 ServiceMonitor 對象用於發現 Promtail 的指標數據:

$ kubectl get servicemonitor -n logging
NAME            AGE
loki-promtail   10s

如果你使用的 Prometheus-Operator 默認不能發現 logging 命名空間下面的數據,則需要創建如下所示的一個 Role 權限:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  labels:
    app.kubernetes.io/component: prometheus
    app.kubernetes.io/name: prometheus
    app.kubernetes.io/part-of: kube-prometheus
    app.kubernetes.io/version: 2.26.0
  name: prometheus-k8s
  namespace: logging
rules:
- apiGroups:
  - ""
  resources:
  - services
  - endpoints
  - pods
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - extensions
  resources:
  - ingresses
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - networking.k8s.io
  resources:
  - ingresses
  verbs:
  - get
  - list
  - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: prometheus-k8s
  namespace: logging
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: prometheus-k8s
subjects:
- kind: ServiceAccount
  name: prometheus-k8s
  namespace: monitoring

正常在 Prometheus 裏面就可以看到 Promtail 的抓取目標了:

如果你使用的是 Prometheus Operator 自帶的 Grafana,則需要手動添加上 Loki 的數據源,前面微服務模式中我們已經在 Grafana 中配置了 Loki 的數據源,現在當我們訪問測試應用的時候,在 Loki 中是可以查看到日誌數據的:

而且現在在 Prometheus 中還可以查詢到我們在  Promtail 中添加的 metrics 指標數據:

因爲現在已經有監控指標了,所以我們就可以根據需求來創建報警規則了,我們這裏使用的 Prometheus Operator,所以可以直接創建一個 PrometheusRule 資源對象即可:

# nginx-prometheus-rule.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  labels:
    prometheus: k8s
    role: alert-rules
  name: promtail-nginx-hits
  namespace: logging
spec:
  groups:
    - name: nginx-hits
      rules:
        - alert: LokiNginxHits
          annotations:
            summary: nginx hits counter
            description: 'nginx_hits total insufficient count ({{ $value }}).'
          expr: |
            sum(increase(promtail_custom_nginx_hits[1m])) > 2
          for: 2m
          labels:
            severity: critical

這裏我們配置了名爲 nginx_hits 的報警規則,這些規則在同一個分組中,每隔一定的時間間隔依次執行。觸發報警的閾值通過 expr 表達式進行配置。我們這裏表示的是 1 分鐘之內新增的總和是否大於 2,當 expor 表達式的條件持續了 2 分鐘時間後,報警就會真正被觸發,報警真正被觸發之前會保持爲 Pending 狀態。

然後具體想要把報警發送到什麼地方去,可以根據標籤去配置 receiver,比如可以通過 WebHook 來接收。我們在 AlertManager 中也是可以看到接收到的報警事件的。

Ruler 組件

上面的方式雖然可以實現我們的日誌報警功能,但是還是不夠直接,需要通過 Promtail 去進行處理,那麼我們能否直接通過 Loki 來實現報警功能呢?其實在 Loki2.0 版本就提供了報警功能,其中有一個 Ruler 組件可以持續查詢一個 rules 規則,並將超過閾值的事件推送給 AlertManager 或者其他 Webhook 服務,這也就是 Loki 自帶的報警功能了,而且是兼容 AlertManager 的。

首先我們需要開啓 Loki Ruler 組件,更新 loki-distributed 安裝的 Values 文件,在前面微服務模式的基礎上增加 ruler 組件配置:

# ci/alert-values.yaml
loki:
  structuredConfig:
    ingester:
      max_transfer_retries: 0
      chunk_idle_period: 1h
      chunk_target_size: 1536000
      max_chunk_age: 1h
    storage_config: # 存儲的配置,定義其他組件可能用到的存儲
      aws: # s3 / s3 兼容的對象存儲
        endpoint: minio.logging.svc.cluster.local:9000
        insecure: true
        bucketnames: loki-data
        access_key_id: myaccessKey
        secret_access_key: mysecretKey
        s3forcepathstyle: true
      boltdb_shipper:
        shared_store: s3
    schema_config:
      configs:
        - from: 2022-06-21
          store: boltdb-shipper # index
          object_store: s3  # chunks
          schema: v12
          index:
            prefix: loki_index_
            period: 24h
    ruler:
      storage:
        type: local
        local:
          directory: /etc/loki/rules
      ring:
        kvstore:
          store: memberlist
      rule_path: /tmp/loki/scratch
      alertmanager_url: http://alertmanager-main.monitoring.svc.cluster.local:9093
      external_url: http:/192.168.0.106:31918

distributor:
  replicas: 2

ingester: # WAL(replay)
  replicas: 2
  persistence:
    enabled: true
    size: 1Gi
    storageClass: local-path

querier:
  replicas: 2
  persistence:
    enabled: true
    size: 1Gi
    storageClass: local-path

queryFrontend:
  replicas: 2

gateway: # nginx容器 -> 路由日誌寫/讀的請求
  nginxConfig:
    httpSnippet: |-
      client_max_body_size 100M;
    serverSnippet: |-
      client_max_body_size 100M;

# Configuration for the ruler
ruler:
  enabled: true
  kind: Deployment
  replicas: 1
  persistence:
    enabled: true
    size: 1Gi
    storageClass: local-path
  # -- Directories containing rules files
  directories:
    tenant_no:
      rules1.txt: |
        groups:
          - name: nginx-rate
            rules:
            - alert: LokiNginxRate
              expr: sum(rate({app="nginx"} |= "error" [1m])) by (job)
                    /
                  sum(rate({app="nginx"}[1m])) by (job)
                    > 0.01
              for: 1m
              labels:
                severity: critical
              annotations:
                summary: loki nginx rate
                description: high request latency

我們首先通過 loki.structuredConfig.ruler 對 Ruler 組件進行配置,比如指定 Alertmanager 的地址,規則存儲方式等,然後通過 ruler 屬性配置了組件的相關信息以及報警規則,重新使用上面的 values 文件安裝 Loki:

$ helm upgrade --install loki -n logging -f ci/alert-values.yaml .
$ kubectl get pods -n logging
NAME                                                   READY   STATUS    RESTARTS      AGE
grafana-55d8779dc6-gkgpf                               1/1     Running   2 (66m ago)   3d21h
loki-loki-distributed-distributor-56959cc548-xpv6d     1/1     Running   0             3m36s
loki-loki-distributed-distributor-56959cc548-zjfsb     1/1     Running   0             2m52s
loki-loki-distributed-gateway-6f4cfd898c-p9xxf         1/1     Running   0             21m
loki-loki-distributed-ingester-0                       1/1     Running   0             2m32s
loki-loki-distributed-ingester-1                       1/1     Running   0             3m34s
loki-loki-distributed-querier-0                        1/1     Running   0             2m48s
loki-loki-distributed-querier-1                        1/1     Running   0             3m29s
loki-loki-distributed-query-frontend-5bcc7949d-brzg6   1/1     Running   0             3m30s
loki-loki-distributed-query-frontend-5bcc7949d-g2wwd   1/1     Running   0             3m35s
loki-loki-distributed-ruler-5d4b8cd889-m2vbd           1/1     Running   0             3m35s
minio-548656f786-mjd4c                                 1/1     Running   2 (66m ago)   3d21h
promtail-ddz27                                         1/1     Running   0             19m
promtail-lzr6v                                         1/1     Running   0             20m
promtail-nldqx                                         1/1     Running   0             20m

Loki 的 rulers 規則和結構與 Prometheus 是完全兼容,唯一的區別在於查詢語句(LogQL)不同,在 Loki 中我們用 LogQL 來查詢日誌,一個典型的 rules 配置文件如下所示:

groups:
  # 組名稱
  - name: xxxx
    rules:
      # Alert名稱
      - alert: xxxx
        # logQL查詢語句
        expr: xxxx
        # 產生告警的持續時間 pending.
        [ for:  | default = 0s ]
        # 自定義告警事件的label
        labels:
        [ :  ]
        # 告警時間的註釋
        annotations:
        [ :  ]

比如我們這裏配置的規則 sum(rate({app="nginx"} |= "error" [1m])) by (job) / sum(rate({app="nginx"}[1m])) by (job) > 0.01 表示通過日誌查到 nginx 日誌的錯誤率大於 1% 就觸發告警,同樣重新使用上面的 values 文件更新 Loki:

更新完成後我們查看 Ruler 組件的日誌可以看到一些關於上面我們配置的報警規則的信息:

$ kubectl logs -f loki-loki-distributed-ruler-5d4b8cd889-m2vbd -n logging
......
level=info ts=2022-06-25T10:10:07.445554993Z caller=metrics.go:122 component=ruler org_id=tenant_no latency=fast query="((sum by(job)(rate({app=\"nginx\"} |= \"error\"[1m])) / sum by(job)(rate({app=\"nginx\"}[1m]))) > 0.01)" query_type=metric range_type=instant length=0s step=0s duration=25.306079ms status=200 limit=returned_lines=throughput=0B total_bytes=0B queue_time=0s subqueries=1
level=info ts=2022-06-25T10:11:03.196836972Z caller=pool.go:171 msg="removing stale client" addr=10.244.2.165:9095
level=info ts=2022-06-25T10:11:07.423644116Z caller=metrics.go:122 component=ruler org_id=tenant_no latency=fast query="((sum by(job)(rate({app=\"nginx\"} |= \"error\"[1m])) / sum by(job)(rate({app=\"nginx\"}[1m]))) > 0.01)" query_type=metric range_type=instant length=0s step=0s duration=3.234499ms status=200 limit=returned_lines=throughput=0B total_bytes=0B queue_time=0s subqueries=1

同樣在 1m 之內如果持續超過閾值,則會真正觸發報警規則,觸發後我們在 Alertmanager 也可以看到對應的報警信息了:

到這裏我們就完成了使用 Loki 基於日誌的監控報警。

k8s 技術圈 專注容器、專注 kubernetes 技術......

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/8JAQxUQSfIggAIArokeL9g