實(shí)現(xiàn)基于 Grafana Loki 的日志報(bào)警
對(duì)于生產(chǎn)環(huán)境以及一個(gè)有追求的運(yùn)維人員來說,哪怕是毫秒級(jí)別的宕機(jī)也是不能容忍的。對(duì)基礎(chǔ)設(shè)施及應(yīng)用進(jìn)行適當(dāng)?shù)娜罩居涗浐捅O(jiān)控非常有助于解決問題,還可以幫助優(yōu)化成本和資源,以及幫助檢測以后可能會(huì)發(fā)生的一些問題。前面我們學(xué)習(xí)使用了 Prometheus 來進(jìn)行監(jiān)控報(bào)警,但是如果我們使用 Loki 收集日志是否可以根據(jù)采集的日志來進(jìn)行報(bào)警呢?答案是肯定的,而且有兩種方式可以來實(shí)現(xiàn):Promtail 中的 metrics 階段和 Loki 的 ruler 組件。
測試應(yīng)用
比如現(xiàn)在我們有一個(gè)如下所示的 nginx 應(yīng)用用于 Loki 日志報(bào)警:
# 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 類型的服務(wù)來暴露應(yīng)用,直接安裝即可:
$ 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 應(yīng)用:
$ 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 來處理日志,其中就包括一個(gè) metrics 的階段,可以根據(jù)我們的需求來增加一個(gè)監(jiān)控指標(biāo),這就是我們需要實(shí)現(xiàn)的基于日志的監(jiān)控報(bào)警的核心點(diǎn),通過結(jié)構(gòu)化日志,增加監(jiān)控指標(biāo),然后使用 Prometheus 結(jié)合 Alertmanager 完成之前我們非常熟悉的監(jiān)控報(bào)警。
首先我們需要安裝 Prometheus 與 Alertmanager,可以手動(dòng)安裝,也可以使用 Prometheus Operator 的方式,可以參考監(jiān)控報(bào)警章節(jié)相關(guān)內(nèi)容,比如這里我們選擇使用 Prometheus Operator 的方式。
前面我們介紹了幾種 Loki 的部署方式,這里我們就保留上節(jié)微服務(wù)模式的 Loki 集群,接下來我們需要重新配置 Promtail,為其添加一個(gè) 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 配置,用于對(duì)日志行進(jìn)行轉(zhuǎn)換,在這里我們添加了一個(gè) match 的階段,會(huì)去匹配具有 app=nginx 這樣的日志流數(shù)據(jù),然后下一個(gè)階段是利用正則表達(dá)式過濾出包含 GET 關(guān)鍵字的日志行。
在 metrics 指標(biāo)階段,我們定義了一個(gè) nginx_hits 的指標(biāo),Promtail 通過其 /metrics 端點(diǎn)暴露這個(gè)自定義的指標(biāo)數(shù)據(jù)。這里我們定義的是一個(gè) Counter 類型的指標(biāo),當(dāng)從 regex 階段匹配上后,這個(gè)計(jì)數(shù)器就會(huì)遞增。
為了在 Prometheus 中能夠這個(gè)指標(biāo),我們通過 promtail.serviceMonitor.enable=true 開啟了一個(gè) ServiceMonitor。接下來重新更新 Loki 應(yīng)用,使用如下所示的命令即可:
$ helm upgrade --install loki -n logging -f ci/metrics-values.yaml .
更新完成后會(huì)創(chuàng)建一個(gè) ServiceMonitor 對(duì)象用于發(fā)現(xiàn) Promtail 的指標(biāo)數(shù)據(jù):
$ kubectl get servicemonitor -n logging
NAME AGE
loki-promtail 10s
如果你使用的 Prometheus-Operator 默認(rèn)不能發(fā)現(xiàn) logging 命名空間下面的數(shù)據(jù),則需要?jiǎng)?chuàng)建如下所示的一個(gè) Role 權(quán)限:
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 的抓取目標(biāo)了:

如果你使用的是 Prometheus Operator 自帶的 Grafana,則需要手動(dòng)添加上 Loki 的數(shù)據(jù)源,前面微服務(wù)模式中我們已經(jīng)在 Grafana 中配置了 Loki 的數(shù)據(jù)源,現(xiàn)在當(dāng)我們訪問測試應(yīng)用的時(shí)候,在 Loki 中是可以查看到日志數(shù)據(jù)的:

而且現(xiàn)在在 Prometheus 中還可以查詢到我們在 Promtail 中添加的 metrics 指標(biāo)數(shù)據(jù):

因?yàn)楝F(xiàn)在已經(jīng)有監(jiān)控指標(biāo)了,所以我們就可以根據(jù)需求來創(chuàng)建報(bào)警規(guī)則了,我們這里使用的 Prometheus Operator,所以可以直接創(chuàng)建一個(gè) PrometheusRule 資源對(duì)象即可:
# 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 的報(bào)警規(guī)則,這些規(guī)則在同一個(gè)分組中,每隔一定的時(shí)間間隔依次執(zhí)行。觸發(fā)報(bào)警的閾值通過 expr 表達(dá)式進(jìn)行配置。我們這里表示的是1分鐘之內(nèi)新增的總和是否大于2,當(dāng) expor 表達(dá)式的條件持續(xù)了2分鐘時(shí)間后,報(bào)警就會(huì)真正被觸發(fā),報(bào)警真正被觸發(fā)之前會(huì)保持為 Pending 狀態(tài)。

然后具體想要把報(bào)警發(fā)送到什么地方去,可以根據(jù)標(biāo)簽去配置 receiver,比如可以通過 WebHook 來接收。我們在 AlertManager 中也是可以看到接收到的報(bào)警事件的。

Ruler 組件
上面的方式雖然可以實(shí)現(xiàn)我們的日志報(bào)警功能,但是還是不夠直接,需要通過 Promtail 去進(jìn)行處理,那么我們能否直接通過 Loki 來實(shí)現(xiàn)報(bào)警功能呢?其實(shí)在 Loki2.0 版本就提供了報(bào)警功能,其中有一個(gè) Ruler 組件可以持續(xù)查詢一個(gè) rules 規(guī)則,并將超過閾值的事件推送給 AlertManager 或者其他 Webhook 服務(wù),這也就是 Loki 自帶的報(bào)警功能了,而且是兼容 AlertManager 的。
首先我們需要開啟 Loki Ruler 組件,更新 loki-distributed 安裝的 Values 文件,在前面微服務(wù)模式的基礎(chǔ)上增加 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: # 存儲(chǔ)的配置,定義其他組件可能用到的存儲(chǔ)
aws: # s3 / s3 兼容的對(duì)象存儲(chǔ)
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容器 -> 路由日志寫/讀的請(qǐng)求
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 對(duì) Ruler 組件進(jìn)行配置,比如指定 Alertmanager 的地址,規(guī)則存儲(chǔ)方式等,然后通過 ruler 屬性配置了組件的相關(guān)信息以及報(bào)警規(guī)則,重新使用上面的 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 規(guī)則和結(jié)構(gòu)與 Prometheus 是完全兼容,唯一的區(qū)別在于查詢語句(LogQL)不同,在 Loki 中我們用 LogQL 來查詢?nèi)罩荆粋€(gè)典型的 rules 配置文件如下所示:
groups:
# 組名稱
- name: xxxx
rules:
# Alert名稱
- alert: xxxx
# logQL查詢語句
expr: xxxx
# 產(chǎn)生告警的持續(xù)時(shí)間 pending.
[ for: | default = 0s ]
# 自定義告警事件的label
labels:
[ : ]
# 告警時(shí)間的注釋
annotations:
[ : ]
比如我們這里配置的規(guī)則 sum(rate({app="nginx"} |= "error" [1m])) by (job) / sum(rate({app="nginx"}[1m])) by (job) > 0.01 表示通過日志查到 nginx 日志的錯(cuò)誤率大于1%就觸發(fā)告警,同樣重新使用上面的 values 文件更新 Loki:

更新完成后我們查看 Ruler 組件的日志可以看到一些關(guān)于上面我們配置的報(bào)警規(guī)則的信息:
$ 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=0 returned_lines=0 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=0 returned_lines=0 throughput=0B total_bytes=0B queue_time=0s subqueries=1
同樣在 1m 之內(nèi)如果持續(xù)超過閾值,則會(huì)真正觸發(fā)報(bào)警規(guī)則,觸發(fā)后我們在 Alertmanager 也可以看到對(duì)應(yīng)的報(bào)警信息了:

到這里我們就完成了使用 Loki 基于日志的監(jiān)控報(bào)警。
