Загальну інформацію по 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