Пока пост по архитектуре и запуску Loki ещё в черновиках – посмотрим, как в Loki создавать алерты, за которые отвечает сервис ruler
.
Документация по алертам в Loki – Rules and the Ruler.
Идея простая:
- создаём файл с алертами в Prometheus-like формате
- подключаем его к
ruler
ruler
парсит логи по заданным в конфиге выражениям, и пушит Alertmanager, передавая ему алерт
Алерты будем описывать в ConfigMap, который потом подключим к поду с 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]
И проверяем логи Локи:
Окей, теперь у нас есть 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
, если используется: (расскажу в посте про запуск Loki):
... 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