Grafana系列-Loki-基於紀錄檔實現告警

2023-12-11 06:00:50

系列文章

前言

實際應用中除了基於 Metrics 告警, 往往還有基於紀錄檔的告警需求, 可以作為基於 Metrics 告警之外的一個補充. 典型如基於 NGINX 紀錄檔的錯誤率告警.本文將介紹如何基於 Loki 實現基於紀錄檔的告警.

本文我們基於以下 2 類實際場景進行實戰演練:

基於紀錄檔告警的應用場景

基於紀錄檔告警的廣泛應用於如下場景:

黑盒監控

對於不是我們開發的元件, 如雲廠商/第三方的負載均衡器和無數其他元件(包括開源元件和封閉第三方元件)支援我們的應用程式,但不會公開我們想要的指標。有些根本不公開任何指標。 Loki 的警報和記錄規則可以生成有關係統狀態的指標和警報,並通過使用紀錄檔將元件帶入我們的可觀察性堆疊中。這是一種將高階可觀察性引入遺留架構的極其強大的方法。

事件告警

有時,您想知道某件事情是否已經發生。根據紀錄檔發出警報可以很好地解決這個問題,例如查詢身份驗證憑據洩露的範例:

- name: credentials_leak
  rules: 
    - alert: http-credentials-leaked
      annotations: 
        message: "{{ $labels.job }} is leaking http basic auth credentials."
      expr: 'sum by (cluster, job, pod) (count_over_time({namespace="prod"} |~ "http(s?)://(\\w+):(\\w+)@" [5m]) > 0)'
      for: 10m
      labels: 
        severity: critical

關於 Nomad 的就屬於這類場景.

技術儲備

Loki 告警

Grafana Loki 包含一個名為 ruler 的元件。Ruler 負責持續評估一組可設定查詢並根據結果執行操作。其支援兩種規則:alerting 規則和 recording 規則。

Loki Alering 規則

Loki 的告警規則格式幾乎與 Prometheus 一樣. 這裡舉一個完整的例子:

groups:
  - name: should_fire
    rules:
      - alert: HighPercentageError
        expr: |
          sum(rate({app="foo", env="production"} |= "error" [5m])) by (job)
            /
          sum(rate({app="foo", env="production"}[5m])) by (job)
            > 0.05
        for: 10m
        labels:
            severity: page
        annotations:
            summary: High request latency
  - name: credentials_leak
    rules: 
      - alert: http-credentials-leaked
        annotations: 
          message: "{{ $labels.job }} is leaking http basic auth credentials."
        expr: 'sum by (cluster, job, pod) (count_over_time({namespace="prod"} |~ "http(s?)://(\\w+):(\\w+)@" [5m]) > 0)'
        for: 10m
        labels: 
          severity: critical

Loki LogQL 查詢

Loki 紀錄檔查詢語言 (LogQL) 是一種查詢語言,用於從 Loki 中檢索紀錄檔。LogQL 與 Prometheus 非常相似,但有一些重要的區別。

LogQL 快速上手

所有 LogQL 查詢都包含紀錄檔流選擇器(log stream selector)。如下圖:

可選擇在紀錄檔流選擇器後新增紀錄檔管道(log pipeline)。紀錄檔管道是一組階段表示式,它們串聯在一起並應用於選定的紀錄檔流。每個表示式都可以過濾、解析或更改紀錄檔行及其各自的標籤。

以下範例顯示了正在執行的完整紀錄檔查詢:

{container="query-frontend",namespace="loki-dev"} 
  |= "metrics.go" 
  | logfmt 
  | duration > 10s 
  and throughput_mb < 500

該查詢由以下部分組成:

  • 紀錄檔流選擇器 {container="query-frontend",namespace="loki-dev"} ,其目標是 loki-dev 名稱空間中的 query-frontend 容器。
  • 紀錄檔管道 |= "metrics.go" | logfmt | duration > 10s and throughput_mb < 500 它將過濾掉包含單詞 metrics.go 的紀錄檔,然後解析每個紀錄檔行以提取更多標籤並使用它們進行過濾。

解析器表示式

為了進行告警, 我們往往需要在告警之前對非結構化紀錄檔進行解析, 解析後會獲得更精確的欄位資訊(稱為label), 這就是為什麼我們需要使用解析器表示式.

解析器表示式可從紀錄檔內容中解析和提取標籤(label)。這些提取的標籤可用於使用標籤過濾表示式進行過濾,或用於 metrics 彙總。

如果原始紀錄檔流中已經存在提取的標籤 key名稱(典型如: level),提取的標籤 key 將以 _extracted 關鍵字為字尾,以區分兩個標籤。你也可以使用標籤格式表示式強行覆蓋原始標籤。不過,如果提取的鍵出現兩次,則只保留第一個標籤值。

Loki 支援 JSONlogfmtpatternregexpunpack 解析器。

今天我們重點介紹下 logfmt, pattern 和 regexp 解析器。

logfmt 解析器

logfmt 解析器可以以兩種模式執行:

不帶引數

可以使用 | logfmt 新增 logfmt 解析器,並將從 logfmt 格式的紀錄檔行中提取所有鍵和值。

例如以下紀錄檔行:

at=info method=GET path=/ host=grafana.net fwd="124.133.124.161" service=8ms status=200

將提取到以下標籤:

"at" => "info"
"method" => "GET"
"path" => "/"
"host" => "grafana.net"
"fwd" => "124.133.124.161"
"service" => "8ms"
"status" => "200"
帶引數

與 JSON 解析器類似,在管道中使用 | logfmt label="expression", another="expression" 將導致只提取標籤指定的欄位。

例如, | logfmt host, fwd_ip="fwd" 將從以下紀錄檔行中提取標籤 hostfwd

at=info method=GET path=/ host=grafana.net fwd="124.133.124.161" service=8ms status=200

並將 fwd 重新命名為 fwd_ip:

"host" => "grafana.net"
"fwd_ip" => "124.133.124.161"
Pattern 解析器

Pattern 解析器允許通過定義模式表示式(| pattern "<pattern-expression>")從紀錄檔行中明確提取欄位。該表示式與紀錄檔行的結構相匹配。

典型如 NGINX 紀錄檔:

0.191.12.2 - - [10/Jun/2021:09:14:29 +0000] "GET /api/plugins/versioncheck HTTP/1.1" 200 2 "-" "Go-http-client/2.0" "13.76.247.102, 34.120.177.193" "TLSv1.2" "US" ""

該紀錄檔行可以用表示式解析:

<ip> - - <_> "<method> <uri> <_>" <status> <size> <_> "<agent>" <_>

提取出這些欄位:

"ip" => "0.191.12.2"
"method" => "GET"
"uri" => "/api/plugins/versioncheck"
"status" => "200"
"size" => "2"
"agent" => "Go-http-client/2.0"

Pattern 表示式由捕獲(captures )和文字組成。

捕獲是以 <> 字元分隔的欄位名。<example> 定義欄位名 example。未命名的捕獲顯示為 <_>。未命名的捕獲會跳過匹配的內容。

Regular Expression 解析器

logfmt 和 json 會隱式提取所有值且不需要引數,而 regexp 解析器則不同,它只需要一個引數 | regexp "<re>",即使用 Golang RE2 語法的正規表示式。

正規表示式必須包含至少一個命名子匹配(例如 (?P<name>re) ),每個子匹配將提取不同的標籤。

例如,解析器 | regexp "(?P<method>\\w+) (?P<path>[\\w|/]+) \\((?P<status>\\d+?)\\) (?P<duration>.*)" 將從以下行中提取:

POST /api/prom/api/v1/query_range (200) 1.5s

到這些標籤:

"method" => "POST"
"path" => "/api/prom/api/v1/query_range"
"status" => "200"
"duration" => "1.5s"

實戰演練