Thank you for reading this post, don't forget to subscribe!
Документация по алертам в Loki – Rules and the Ruler.
- создаём файл с алертами в Prometheus-like формате
- подключаем его к
ruler
ruler
парсит логи по заданным в конфиге выражениям, и пушит Alertmanager, передавая ему алерт
Алерты будем описывать в ConfigMap, который потом подключим к поду с Ruler.
Тестовый под для OOM-Killed
Потестим на срабатывание OOM Killed, поэтому создадим под с явно заниженными лимитами, который будет убиваться “на взлёте”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
--- 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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 |
Запускаем наш под:
1 2 3 4 5 6 7 8 |
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 |
И проверяем логи Локи:
теперь у нас есть oom-killed под для тестов – давайте формировать запрос для будущего алерта.
Формирование запроса в Loki
В логах мы смотрели по {hostname="eks-node-dev_data_services-i-081719890438d467f"} |~ ".*OOM-killed.*"
– используем его же для тестового алерта.
Сначала проверим что нам нарисует сама Локи – используем rate()
и sum()
, см. Log range aggregations:
1 |
sum(rate({hostname="eks-node-dev_data_services-i-081719890438d467f"} |~ ".*OOM-killed.*" [5m])) by (hostname) |
Создание алерта для Loki Ruler
Создаём файл с ConfigMap:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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 |
Деплоим его:
Ruler и ConfigMap volume
Далее, нам надо подключить этот ConfigMap в под с ruler
в каталог, который указан в конфиге Loki для компонента ruler
:
1 2 3 4 5 6 |
... ruler: storage: local: directory: /var/loki/rules ... |
Ruler у нас работает в loki-read подах – открываем их StatefulSet:
1 2 3 4 5 6 7 8 9 10 11 12 |
... 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:
1 2 3 4 5 |
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 ... |
В ConfigMap Loki для ruler
указываем этот адрес:
1 2 3 4 5 6 7 8 |
... ruler: storage: local: directory: /var/loki/rules type: local alertmanager_url: http://prometheus-kube-prometheus-alertmanager:9093 ... |
Все параметры для
ruler
– тут>>>.
Открываем себе доступ к Alertmanager, что бы проверять алерты:
kk -n monitoring port-forward svc/prometheus-kube-prometheus-alertmanager 9093:9093
Рестартим поды loki-read, можно просто через kubectl delete pod
, и проверяем их логи:
1 2 3 |
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 |
Проверяем Алерты в Алертменеджере – http://localhost:9093:
Loki и дополнительные labels
В алертах хочется выводить немного больше информации, чем просто сообщение “Test Loki OOM Killer Alert”, к примеру – отобразить имя пода, который был убит.
Добавление labels в Promtail
Первый вариант – это создавать новые лейблы ещё на этапе сбора логов, в самом Promtail через pipeline_stages
, например так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
- 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, где за каждый запрос приходится платить деньги.
Добавление labels из запросов в Loki
Вместо этого, мы можем создавать новый лейблы прямо из запроса с помощью самой Loki.
Возьмём запись из лога, в которой говорится о срабатывании OOM Killer:
1 |
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:
1 |
{hostname="eks-node-dev_data_services-i-081719890438d467f"} |~ ".*OOM-killed.*" | regexp `pod=".*/(?P<pod>[a-zA-Z].*)".*` |
И в логе получаем лейблу
pod
со значением “oom-test“:
Проверяем запрос алерта с sum()
и rate()
:
1 |
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 }}
:
1 2 3 4 5 6 7 8 9 |
- 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:
1 2 3 4 5 |
... server: http_server_read_timeout: 600s http_server_write_timeout: 600s ... |