Загальну інформацію по Grafana Loki див. у Grafana Loki: архітектура та запуск в Kubernetes з AWS S3 storage та boltdb-shipper.
Серед інших сервісів, які складають собою Loki, є окремий сервіс ruler, який відповідає за роботу з алертами, які можно генерити прямо з логів.
Ідея дуже проста:
- створюємо файл з алертами в Prometheus-like форматі
- підключаємо його до поду
ruler(loki-read у випадку simple-scalable deployment) rulerпарсить логи по заданним в файлі конфігурації правилам, і якщо якийсь expression спрацьовує – то Ruler пушить Alertmanager, передаючи йому алерт
Алерти будемо описувати в ConfigMap, який потім підключимо до подів з Ruler.
Документація – Rules and the Ruler.
Зміст
Тестовый под для OOM-Killed
Мені хочеться потестити на спрацювання OOM Killed, тому створимо под явно заниженими лімітами, який буде вбиватися “на зльоті”:
---
apiVersion: v1
kind: Pod
metadata:
name: oom-test
labels:
test: "true"
spec:
containers:
- name: oom-test
image: openjdk
command: [ "/bin/bash", "-c", "--" ]
args: [ "while true; do sleep 30; done;" ]
resources:
limits:
memory: "1Mi"
nodeSelector:
kubernetes.io/hostname: eks-node-dev_data_services-i-081719890438d467f
Задаємо в nodeSelector ім’я ноди, щоб було простіше шукати в Локі.
При старті цього поду Kubernetes його вбиватиме через перевищення лімітів, а journald на WorkerNode записуватиме подію в системний журнал, який збирається promtail:
[simterm]
$ kk -n monitoring get cm logs-promtail -o yaml
...
- job_name: journal
journal:
labels:
job: systemd-journal
max_age: 12h
path: /var/log/journal
relabel_configs:
- source_labels:
- __journal__systemd_unit
target_label: unit
- source_labels:
- __journal__hostname
target_label: hostname
[/simterm]
Запускаємо наш под:
[simterm]
$ kk apply -f test-oom.yaml pod/oom-test created
[/simterm]
Перевіряємо:
[simterm]
$ kk describe pod oom-test ... Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 91s default-scheduler Successfully assigned default/oom-test to ip-10-0-0-27.us-west-2.compute.internal Normal SandboxChanged 79s (x12 over 90s) kubelet Pod sandbox changed, it will be killed and re-created. Warning FailedCreatePodSandBox 78s (x13 over 90s) kubelet Failed to create pod sandbox: rpc error: code = Unknown desc = failed to start sandbox container for pod "oom-test": Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: container init was OOM-killed (memory limit too low?): unknown
[/simterm]
І перевіряємо логі Loki:
Окей, тепер у нас є oom-killed под для тестів – давайте формувати запит для майбутнього алерту.
Формування запиту в Loki
В логах ми дивилися по запиту {hostname="eks-node-dev_data_services-i-081719890438d467f"} |~ ".*OOM-killed.*" – використовуємо його ж для тестового алерту.
Спочатку перевіримо що нам намалює сама Локі – використовуємо rate() та sum(), див. Log range aggregations:
sum(rate({hostname="eks-node-dev_data_services-i-081719890438d467f"} |~ ".*OOM-killed.*" [5m])) by (hostname)
Гуд!
З цим вже можна працювати – створювати тестовий алерт.
Створення алерту для Loki Ruler
Створюємо файл з ConfigMap:
kind: ConfigMap
apiVersion: v1
metadata:
name: rules-alerts
namespace: monitoring
data:
rules.yaml: |-
groups:
- name: systemd-alerts
rules:
- alert: TESTLokiRuler Systemd journal
expr: |
sum(rate({hostname="eks-node-dev_data_services-i-081719890438d467f"} |~ ".*OOM-killed.*" [5m])) by (hostname) > 1
for: 1s
labels:
severity: info
annotations:
summary: Test Loki OOM Killer Alert
Деплоїмо його:
[simterm]
$ kk apply -f rule-cm.yaml configmap/rules-alerts created
[/simterm]
Ruler та ConfigMap volume
Далі, нам треба підключити цей ConfigMap в под з ruler в каталог, який вказаний в конфізі Loki для компонента ruler:
...
ruler:
storage:
local:
directory: /var/loki/rules
...
Ruler у нас працює в loki-read подах – відкриваємо їх StatefulSet:
[simterm]
$ kk -n monitoring edit sts loki-read
[/simterm]
Описуємо новий volume:
...
volumes:
- configMap:
defaultMode: 420
name: rules-alerts
name: rules
...
І його мапінг у под як /var/loki/rules/fake/rules.yaml, де fake – це tenant_id, якщо використовується:
...
volumeMounts:
- mountPath: /etc/loki/config
name: config
- mountPath: /tmp
name: tmp
- mountPath: /var/loki
name: data
- mountPath: /var/loki/rules/fake/rules.yaml
name: rules
subPath: rules.yaml
...
В subPath вказуємо key з ConfigMap, щоб підключити саме як файл.
Налаштування Ruler alerting
Знаходимо Alertmanager URL:
[simterm]
$ kk -n monitoring get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ... prometheus-kube-prometheus-alertmanager ClusterIP 172.20.240.159 <none> 9093/TCP 110d ...
[/simterm]
У ConfigMap Loki для ruler вказуємо цю адресу:
...
ruler:
storage:
local:
directory: /var/loki/rules
type: local
alertmanager_url: http://prometheus-kube-prometheus-alertmanager:9093
...
Усі параметри для ruler – тут>>> .
Відкриваємо собі доступ до Alertmanager, щоб перевіряти алерти:
[simterm]
$ kk -n monitoring port-forward svc/prometheus-kube-prometheus-alertmanager 9093:9093
[/simterm]
Рестартим поди loki-read, можна просто через kubectl delete pod, і перевіряємо їх логи:
[simterm]
$ kk -n monitoring logs -f loki-read-0
...
level=info ts=2022-12-13T16:37:33.837173256Z caller=metrics.go:133 component=ruler org_id=fake latency=fast query="(sum by(hostname)(rate({hostname=\"eks-node-dev_data_services-i-081719890438d467f\"} |~ \".*OOM-killed.*\"[5m])) > 1)" query_type=metric range_type=instant length=0s step=0s duration=120.505858ms status=200 limit=0 returned_lines=0 throughput=48MB total_bytes=5.8MB total_entries=1 queue_time=0s subqueries=1
...
[/simterm]
Перевіряємо Алерти в Алертменеджері – http://localhost:9093:
Loki та додаткові labels
В алертах хочеться виводити трохи більше інформації, ніж просто повідомлення “Test Loki OOM Killer Alert”, наприклад – відобразити ім’я пода, який був вбитий.
Додавання labels в Promtail
Перший варіант – це створювати нові лейбли ще на етапі збору логів, в самому Promtail через pipeline_stages, див. Grafana: Loki – Prometheus-like счётчики и функции агрегации в LogQL и графики DNS запросов к dnsmasq, наприклад так:
- job_name: journal
pipeline_stages:
- match:
selector: '{job="systemd-journal"}'
stages:
- regex:
expression: '.*level=(?P<level>[a-zA-Z]+).*'
- labels:
level:
- regex:
expression: '.*source="(?P<source>[a-zA-Z]+)".*'
- labels:
source:
journal:
labels:
job: systemd-journal
max_age: 12h
path: /var/log/journal
relabel_configs:
- source_labels:
- __journal__systemd_unit
target_label: unit
- source_labels:
- __journal__hostname
target_label: hostname
Тут я для тестів створював нові лейбли, які підключалися до логів – source і level.
Інший варіант із Promtail – використовуючи static_labels.
Але тут є проблема: оскільки Loki на кожний набір лейбл створює окремий лог-стрім, для якого створюються окремі індекси та блоки даних, то в результаті отримаємо по-перше проблеми з продуктивністю, по-друге – з вартістю, т.к. на кожен індекс і блок даних будуть виконуватися запити читання-запису в shared store, у нашому випадку це AWS S3, де за кожен запит доводиться платити гроші.
Дивись чудовий пост на цю тему тут – Grafana Loki and what can go wrong with label cardinality.
Додавання labels із запитів в Loki
Натомість, ми можемо створювати нові лейбли прямо із запиту за допомогою самої Loki.
Візьмемо запис із лога, в якому йдеться про спрацювання OOM Killer:
E1213 16:52:25.879626 3382 pod_workers.go:951] “Error syncing pod, skipping” err=”failed to \”CreatePodSandbox\” for \”oom-test_default(f02523a9-43a7-4370-85dd-1da7554496e6)\” with CreatePodSandboxError: \”Failed to create sandbox for pod \\\”oom-test_default(f02523a9-43a7-4370-85dd-1da7554496e6)\\\”: rpc error: code = Unknown desc = failed to start sandbox container for pod \\\”oom-test\\\”: Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: container init was OOM-killed (memory limit too low?): unknown\”” pod=”default/oom-test” podUID=f02523a9-43a7-4370-85dd-1da7554496e6
Тут ми маємо поле pod з ім’ям пода, який був вбитий – pod="default/oom-test".
Використовуємо regex у вигляді pod=".*/(?P<pod>[a-zA-Z].*)".* щоб створити Named Capturing Group , перевіряємо наприклад на https://regex101.com :
Доповнюємо вибірку в Loki:
{hostname="eks-node-dev_data_services-i-081719890438d467f"} |~ ".*OOM-killed.*" | regexp `pod=".*/(?P<pod>[a-zA-Z].*)".*`
І в результаті отримуємо лейблу pod зі значенням ” oom-test “:
Перевіряємо запит алерту з sum() і rate():
sum(rate({hostname="eks-node-dev_data_services-i-081719890438d467f"} |~ ".*OOM-killed.*" | regexp `pod=".*/(?P<pod>[a-zA-Z].*)".*` | pod!="" [5m])) by (pod)
Результат:
Оновлюємо алерт – додамо description в якому використовуємо {{ $labels.pod }}:
- alert: TESTLokiRuler Systemd journal
expr: |
sum(rate({hostname="eks-node-dev_data_services-i-081719890438d467f"} |~ ".*OOM-killed.*" | regexp `.*pod=".*/(?P<pod>[a-zA-Z].*)".*` | pod!="" [15m])) by (pod) > 1
for: 1s
labels:
severity: info
annotations:
summary: Test Loki OOM Killer Alert
description: "Killed pod: `{{ $labels.pod }}`"
Чекаємо на його спрацювання:
І в Слаку:
Grafana Loki и ошибки 502 и 504
Зараз не вдається зарепрод’юсити, але іноді Grafana не може дочекатися відповіді від Loki виконання запиту і падає з помилками 502 або 504.
Є тред в Girthub , мені допомогло збільшення тайм-аутів HTTP в Loki ConfigMap:
... server: http_server_read_timeout: 600s http_server_write_timeout: 600s ...
Загалом, на цьому поки що все.
Посилання по темі
- How to Setup Alerting With Loki
- Loki 2.0 released: Transform logs as you’re querying them, and set up alerts within Loki
- Labels from Logs
- The concise guide to labels in Loki
- How labels in Loki can make log queries faster and easier
- Grafana Loki and what can go wrong with label cardinality
- How to use LogQL range aggregations in Loki







