Продовжуємо міграцію з Grafana Loki на VictoriaLogs, і наступна задача – це перенести Recording Rules з Loki до VictoriaLogs, і оновити алерти.
Recording Rules та інтеграцію з VMAlert до VictoriaLogs завезли відносно недавно, і цю схему ще не тестував.
Тому спершу все зробимо руками, подивимось як це працює, які є нюанси, а потім будемо оновлювати Helm chart, яким деплоїться мій Monitoring Stack, і додавати туди нові Recording Rules.
Тож, що сьогодні:
- встановимо VMAlert з Helm чарту в Kubernetes
- перепишемо запит Loki LogQL на VictoriaLogs LogsQL
- створимо VMAlert Recording Rule для генерації метрик з логів
- протестуємо, як робити алерти з логів та Recording Rules
- і подивимось, як цю схему можна інтегрувати в існуючий стек VictoriaMetrics
Попередні пости по VictoriaLogs:
- VictoriaLogs: знайомство, запуск в Kubernetes, LogsQL та Grafana
- VictoriaLogs: дашборда в Grafana з AWS VPC Flow Logs – мігруємо з Grafana Loki
- Vector.dev: знайомство, логи з AWS S3 та інтеграція з VictoriaLogs
Також див:
- про Recording Rules для Loki – Grafana Loki: оптимізація роботи – Recording Rules, кешування та паралельні запити та Grafana Loki: LogQL та Recoding Rules для метрик з логів AWS Load Balancer
- про мій Helm chart для моніторингу – VictoriaMetrics: створення Kubernetes monitoring stack з власним Helm-чартом
Зміст
VictoriaLogs, Recording Rules та VMAlert
Отже, в чому полягає ідея:
- VMAlert може робити запити до VictoriaLogs
- в цих запитах він виконує якісь
expr
– як в звичайних алертах - по результатам цих запитів VMAlert або генерує метрику – якщо це Recording Rule – і записує її в VictoriaMetrics чи Prometheus, або генерує алерт – якщо це Alert
Тобто тут та ж сама схема, як і в Loki, і метрики з Recording Rule ми можемо використовувати не тільки для алертів, а і в Grafana dashboards.
Як завжди – у VictoriaMetrics є чудова документація:
- інтеграція VMAlert з VictoriaLogs – VictoriaMetrics / VictoriaLogs / vmalert
- про VMAlert та VMRules – Rules
Запуск VMAlert в Kubernetes з Helm чарту
В мене вже є повністю задеплоєний стек VictoriaMetrics і решта всього моніторингу власним чартом, але зараз VMAlert запустимо окремо від нього, бо є момент з тим, як VMAlert робить запити до VictoriaMetrics та VictoriaLogs – далі з цим розберемось.
Сам чарт тут – victoria-metrics-alert.
Для деплою нам знадобляться такі параметри:
datasource.url
: адреса VictoriaLogs – до кого виконувати запитиnotifier.url
: адреса Alertmanager – куди слати алертиremoteWrite.url
: адреса VictoriaMetrics/Prometheus – куди записуємо метрики і стан алертівremoteRead.url
: адреса VictoriaMetrics/Prometheus – звідки читаємо стан алертів при рестарті VMAlert
Генеруємо values.yaml
:
$ helm show values vm/victoria-metrics-alert > vmalert-test-values.yaml
Знаходимо потрібні Kubernetes Services:
$ kk -n ops-monitoring-ns get svc | grep 'alertmanager\|logs\|vmsingle' atlas-victoriametrics-victoria-logs-single-server ClusterIP None <none> 9428/TCP 116d vmalertmanager-vm-k8s-stack ClusterIP None <none> 9093/TCP,9094/TCP,9094/UDP 138d vmsingle-vm-k8s-stack ClusterIP 172.20.89.111 <none> 8429/TCP 138d
Редагуємо vmalert-test-values.yaml
:
... # VictoriaLogs Svc datasource: url: "http://atlas-victoriametrics-victoria-logs-single-server:9428" ... # Alertmanager Svc notifier: alertmanager: url: "http://vmalertmanager-vm-k8s-stack:9093" ... # VictoriaMetrics/Prometheus Svc remote: write: url: "http://vmsingle-vm-k8s-stack:8429" ... read: url: "http://vmsingle-vm-k8s-stack:8429" ...
Деплоїмо:
$ helm -n ops-monitoring-ns upgrade --install vmalert-test vm/victoria-metrics-alert -f vmalert-test-values.yaml
Перевіряємо Kubernetes Pod з VMalert:
$ kk -n ops-monitoring-ns get pod | grep vmalert- vmalert-test-victoria-metrics-alert-server-6f485dc8b-tgcfd 1/1 Running 0 36s vmalert-vm-k8s-stack-7d5bd6f955-dgx2r 2/2 Running 0 47h
Тут vmalert-vm-k8s-stack-7d5bd6f955-dgx2r – це мій “дефолтний” VMAlert, а vmalert-test-victoria-metrics-alert-server – наш новий тестовий VMAlert.
Grafana Loki LogQL query => VictoriaLogs LogsQL query
В Grafana Loki в мене є такий Recording Rule:
kind: ConfigMap apiVersion: v1 metadata: name: loki-alert-rules data: rules.yaml: |- groups: ... - name: EKS-Pods-Metrics rules: - record: eks:pod:backend:api:path_duration:avg expr: | topk (10, avg_over_time ( {app="backend-api"} | json | regexp "https?://(?P<domain>([^/]+))" | line_format "{{.path}}: {{.duration}}" | unwrap duration [5m] ) by (domain, path, node_name) ) ...
Тут вичитуються логи з Kubernetes Pods нашого Backend API, з кожного запису створюється нове поле domain
, і використовуються існуючі в логах поля path
та duration
.
А потім для кожного domain
, path
, node_name
обчислюється average duration на виконання запиту.
Аби зробити аналогічний запит з VictoriaLogs LogsQL, нам потрібно:
- вибрати логи з
app:="backend-api"
- створити поле
domain
- отримати значення
path
таduration
- обчислити mean (average) за 5 хвилин по полю
duration
- згрупувати результат по полям
domain
,path
,node_name
Знайдемо логи з VMLogs:
Далі:
- додамо
unpack_json
, бо логи пишуться в JSON – парсимо його, і створюємо нові поля - додамо фільтр по полю
http.url
, бо частина записів в логах або не мають URL взагалі, або там адреса Kubernetes Pods у вигляді http://10.0.32.14:8080/ping – всякі Liveness && Readiness Probes, які нам не цікаві - використовуємо
extract_regexp
, аби з поля_msg
створити нове полеdomain
- полів у нас тут забагато, всі вони нам не потрібні – використаємо
fields
pipe, і залишимо тільки ті, які будемо використовувати - можемо додати фільтр
path:~".+"
, аби скіпнути всі записи з пустимpath
app:="backend-api" | unpack_json | http.url:~"example.co" | extract_regexp "https?://(?P<domain>([^/]+))" | fields _time, path, duration, node_name, domain | path:~".+"
Замість фільтра http.url:~"example.co"
можемо використати Sequence filter у формі http.url:seq("example.co")
– але різниці у швидкості виконання запита не побачив:
Насправді для перформансу фільтр http.url:~"example.co"
краще перенести на початок запиту, відразу за stream selector app:="backend-api"
, і спростити просто до Word filter "example.co"
– але вже поробив скріни, тому ОК, тут нехай буде так, потім зробимо, як треба.
Тепер маємо потрібні записи, маємо потрібні поля – йдемо далі.
Далі нам потрібен stats
pipe зі stats pipe function avg()
за 5 хвилин зі значення в полі duration
.
Додаємо в запит | stats by (_time:5m, path, node_name, domain) avg(duration) avg_duration
.
Тут вже краще використати Time series візуалізацію в Grafana dashboard:
І давайте порівняємо результат з Loki.
Візьмемо якийсь домен, ноду, та URI, наприклад в Loki результат буде таким:
avg_over_time ( {app="backend-api"} | json | regexp "https?://(?P<domain>([^/]+))" | line_format "{{.path}}: {{.duration}}" | domain="api.challenge.example.co" | path="/coach/clients/{client_id}/accountability/groups" | node_name="ip-10-0-34-247.ec2.internal" | unwrap duration [5m] ) by (domain, path, node_name)
І в VictoriaLogs:
Значення “393” в обох випадках.
Гуд!
Тепер можемо власне переходити до Recording Rules.
Створення VictoriaLogs Recording Rules та Alerts
Для додавання Recording Rules в values чарту VMAlert є блок config.alerts.groups
, в якому ми можемо з типом record
описати або власне Recording Rule, або з типом alert
– описати алерт.
Створення Recording Rule
Спочатку спробуємо Recording Rule.
Додаємо record: vmlogs:eks:pod:backend:api:path_duration:avg
в наш файл vmalert-test-values.yaml
:
... # -- VMAlert alert rules configuration. # Use existing configmap if specified configMap: "" # -- VMAlert configuration config: alerts: groups: - name: VmLogsEksPodsMetrics type: vlogs interval: 15s rules: - record: vmlogs:eks:pods:backend:api:path_duration:avg expr: | app:="backend-api" | unpack_json | http.url:~"example.co" | extract_regexp "https?://(?P<domain>([^/]+))" | fields _time, path, duration, node_name, domain | path:~".+" | stats by (_time:5m, path, node_name, domain) avg(duration) avg_duration ...
Деплоїмо, глянемо логи тестового VMAlert:
$ ktail -n ops-monitoring-ns -l app.kubernetes.io/instance=vmalert-test ... vmalert-test-victoria-metrics-alert-server-6469894c78-cmktk:vmalert {"ts":"2024-12-30T14:21:43.815Z","level":"info","caller":"VictoriaMetrics/app/vmalert/rule/group.go:486","msg":"group \"VmLogsEksPodsMetrics\" started; interval=15s; eval_offset=<nil>; concurrency=1"} ...
group \"VmLogsEksPodsMetrics\" started;
– ОК.
Перевіряємо метрику vmlogs:eks:pods:backend:api:path_duration:avg
в VMSingle:
Yay!
It works!
Створення Alert
Алерти можемо додати двома шляхами:
- можемо описати новий алерт прямо в values чарту нового VMAlert, який буде виконувати запити напряму до VictoriaLogs
- або, оскільки у нас є Recording Rule, який створює метрику – то ми можемо створити звичайний VMRule, який буде опрацьований оператором, і переданий до “дефолтного” VMAlert
Давайте спробуємо і так, і так.
Спочатку додамо алерт до файлу vmalert-test-values.yaml,
поруч з нашим Recording Rule, в імені алерту вкажемо “Raw“:
... config: alerts: groups: - name: VmLogsEksPodsMetrics type: vlogs interval: 5s rules: - record: vmlogs:eks:pods:backend:api:path_duration:avg expr: | app:="backend-api" | unpack_json | http.url:~"example.co" | extract_regexp "https?://(?P<domain>([^/]+))" | fields _time, path, duration, node_name, domain | path:~".+" | stats by (_time:5m, path, node_name, domain) avg(duration) avg_duration - alert: Test API Path duration Raw expr: | app:="backend-api" | unpack_json | http.url:~"example.co" | extract_regexp "https?://(?P<domain>([^/]+))" | fields _time, path, duration, node_name, domain | path:~".+" | stats by (_time:5m, path, node_name, domain) avg(duration) as avg_duration for: 1s labels: severity: warning component: backend environment: dev annotations: summary: 'Test API Path duration Raw' description: |- Request duration is too slow *Domain Name*: `{{ $labels.domain }}` *URI*: `{{ $labels.path }}` *Duration*: `{{ $value | humanize }}` grafana_alb_overview_url: 'https://monitoring.ops.example.co/d/aws-alb-oveview/aws-alb-oveview?from=now-1h&to=now&var-domain={{ $labels.domain }}' tags: backend ...
Деплоїмо Helm з цим новим алертом:
$ helm -n ops-monitoring-ns upgrade --install vmalert-test vm/victoria-metrics-alert -f vmalert-test-values.yaml
Тепер створимо файл з VMRule з аналогічним алертом, але з метрики, яка створюється нашим Recording Rule – в ім’я алерту додаємо “VMSingle“:
apiVersion: operator.victoriametrics.com/v1beta1 kind: VMRule metadata: name: alerts-vmlogs-test spec: groups: - name: VMAlertVMlogsTest rules: - alert: Test API Path duration VMSingle expr: vmlogs:eks:pods:backend:api:path_duration:avg > 0 for: 1s labels: severity: warning component: backend environment: dev annotations: summary: 'Test API Path duration VMSigle' description: |- Request duration is too slow *Domain Name*: `{{ $labels.domain }}` *URI*: `{{ $labels.path }}` *Duration*: `{{ $value | humanize }}` grafana_alb_overview_url: 'https://monitoring.ops.example.co/d/aws-alb-oveview/aws-alb-oveview?from=now-1h&to=now&var-domain={{ $labels.domain }}' tags: backend
Деплоїмо його:
$ kk -n ops-monitoring-ns apply -f test-alert.yaml vmrule.operator.victoriametrics.com/alerts-vmlogs-test created
І чекаємо повідомлення від Alertmanager в Slack:
Гуд!
Працює.
Тепер можемо переносити цей конфіг до загального Helm-чарту нашого моніторингу.
VictoriaLogs, VMAlert, та чарт victoria-metrics-k8s-stack
Отже, в моєму проекті є наш власний чарт, в якому через Helm dependencies встановлюються такі чарти:
apiVersion: v2 name: atlas-victoriametrics description: A Helm chart for Atlas Victoria Metrics Kubernetes monitoring stack type: application version: 0.1.1 appVersion: "1.17.0" dependencies: - name: victoria-metrics-k8s-stack version: ~0.31.0 repository: https://victoriametrics.github.io/helm-charts - name: victoria-metrics-auth version: ~0.8.0 repository: https://victoriametrics.github.io/helm-charts condition: victoria-metrics-auth.enabled - name: victoria-logs-single version: ~0.8.0 repository: https://victoriametrics.github.io/helm-charts ...
А далі в values.yaml
для кожного сабчарту задаються параметри.
VMAlert: datasource.url
, VictoriaMetrics та VictoriaLogs
Що нам треба – це додати інтеграцію VMAlert з VictoriaLogs сюди, але є нюанс: VMAlert може мати тільки один параметр datasource.url
, в якому зараз заданий Kubernetes Service з VMSingle – звідки VMAlert бере метрики для обчислення умов існуючих алертів:
$ kk -n ops-monitoring-ns describe pod vmalert-vm-k8s-stack-7d5bd6f955-m6mz4 ... Containers: vmalert: ... Args: -datasource.url=http://vmsingle-vm-k8s-stack.ops-monitoring-ns.svc.cluster.local.:8429 ...
Але ж нам треба задати адресу VictoriaLogs, і при цьому залишити можливість запитів до VMSingle.
В документації VictoriaLogs How to use one vmalert for VictoriaLogs and VictoriaMetrics rules in the same time? описуються два варіанти рішення:
- або просто мати два окремих інстанси VMAlert – один для метрик з VictoriaLogs, другий – для роботи з VictoriaLogs
- або використати VMAuth, і в залежності від URI запиту від VMAlert роутити запити на потрібний бекенд – або VictoriaMetrics/VMSingle, або VictoriaLogs
Опція 1: два інстанси VMAlert
Перший варіант – запускати два VMAlert, і кожному передати власний datasource.url
.
Але є питання – як в різні VMAlert передавати Recording Rules та власне Алерти?
Бо в мене Алерти описуються через ресурси VMRules, які з VictoriaMetrics Operator записуються в ConfigMap, який потім підключається до мого “дефолтного” VMAlert:
$ kk -n ops-monitoring-ns describe pod vmalert-vm-k8s-stack-7d5bd6f955-m6mz4 ... Volumes: ... vm-vm-k8s-stack-rulefiles-0: Type: ConfigMap (a volume populated by a ConfigMap) Name: vm-vm-k8s-stack-rulefiles-0 ...
І цей ConfigMap містить в собі всі алерти:
$ kk get cm vm-vm-k8s-stack-rulefiles-0 -o yaml | head -n 30 apiVersion: v1 data: ops-monitoring-ns-alerts-alertmanager.yaml: | groups: - name: VM.Alertmanager.rules rules: - alert: Alertmanager Failed To Send Alerts annotations: description: |- Alertmanager failed to send {{ $value | humanizePercentage }} of notifications *Kubernetes cluster*: `{{ $labels.cluster }}` *Pod*: `{{ $labels.pod }}` *Integration*: `{{ $labels.integration }}` summary: Alertmanager Failed To Send Alerts tags: devops expr: |- sum( rate(alertmanager_notifications_failed_total [5m]) / rate(alertmanager_notifications_total [5m]) ) by (cluster, integration, pod) > 0.01 for: 1m labels: component: devops environment: ops severity: warning ops-monitoring-ns-alerts-aws-alb.yaml: | groups: - name: AWS.ALB.Logs.rules
Якщо робити схему з двома інстансами VMAlert з різними datasource.url
– то для інстансу, який буде робити запити до VictoriaLogs нам потрібно створювати власний ConfigMap, і маунтити його з вальюсів цього інстансу VMAlert, без VMRules і участі VM Operator.
Хоча технічно, мабуть, можливо мати VMRules з Recording Rules та Alerts і два інстанси VMAlert, де в кожен інстанс будуть мапитись один і той самий ConfigMap і з RecordingRules, і з Alerts – але тоді один VMAlert буде постійно писати про помилки запитів до VictroriaMetrcis, а другий – про помилки запитів до VictoriaLogs.
Тому тут бачу тільки варіант з окремим ConfgiMap для RecordingRules, і окремо мати VMRules для алертів, як воно є зараз.
Мені така схема якось не дуже подобається, бо я хотів би і RecordingRules, і Алерти описувати через VMRules.
ОК, тоді розглянемо інший варіант – з VMAuth.
Опція 2: VMAuth і src_paths
Другий варіант – редіректити запити від єдиного інстансу VMAlert до VictoriaLogs та VictoriaMetrics/VMSingle через VMAuth.
В мене VMAuth вже є, писав про нього в пості VictoriaMetrics: VMAuth – проксі, аутентифікація та авторизація, де налаштована аутентифікація і вже є роути – я ним користуюсь для доступу до деяких внутрішніх ресурсів, коли мені ліньки робити kubectl port-forward
.
Що нам треба – це додати ще пару src_paths
:
/api/v1/query.*
– для запитів до VictoriaMetrics/VMSingle/select/logsql/.*
– для запитів до VictoriaLogs
Тоді в моєму випадку все разом буде виглядати так:
apiVersion: v1 kind: Secret metadata: name: vmauth-config-secret stringData: auth.yml: |- users: - username: vmadmin password: {{ .Values.vmauth_password }} url_map: - src_paths: - /alertmanager.* url_prefix: http://vmalertmanager-vm-k8s-stack.ops-monitoring-ns.svc:9093 - src_paths: - /vmui.* url_prefix: http://vmsingle-vm-k8s-stack.ops-monitoring-ns.svc:8429 - src_paths: - /prometheus.* url_prefix: http://vmsingle-vm-k8s-stack.ops-monitoring-ns.svc:8429 - src_paths: - /api/v1/query.* url_prefix: http://vmsingle-vm-k8s-stack:8429 - src_paths: - /select/logsql/.* url_prefix: http://atlas-victoriametrics-victoria-logs-single-server:9428 default_url: - http://vmalertmanager-vm-k8s-stack.ops-monitoring-ns.svc:9093
Цей Secret передається в values для VMAuth:
... victoria-metrics-auth: ingress: enabled: true ... secretName: vmauth-config-secret ...
Якщо у вас VMAuth не використовується, або працює без паролю – то простіше, бо для VMAlert просто можна задати datasource.url
.
Якщо ж потрібна аутентифікація – то додамо ще один Kubernetes Secret з логіном та паролем:
apiVersion: v1 kind: Secret metadata: name: vmauth-password stringData: username: vmadmin password: {{ .Values.vmauth_password }}
Далі в вальюсах для VMAlert додаємо datasource.url
та datasource.basicAuth
:
... vmalert: annotations: {} enabled: true spec: datasource: basicAuth: username: name: vmauth-password key: username password: name: vmauth-password key: password url: http://atlas-victoriametrics-victoria-metrics-auth:8427 ...
Тут:
- поле
spec
для VMAlert описується вVMAlertSpec
і має полеdatasource
- поле
datasource
описується вVMAlertDatasourceSpec
і має поляbasicAuth
таurl
- поле
basicAuth
описується вbasicauth
і має два поля –password
таusername
- поля
password
таusername
описуються в SecretKeySelector, і мають два поля –name
таkey
- поле
name
: ім’я Kubernetes Secret - поле
key
: ключ в цьому сікреті
- поле
- поля
- поле
- поле
Деплоїмо, і тепер наш VMAlert відправляє запити для алертів на VMAuth, а VMAuth редіректить їх до url_prefix: http://vmsingle-vm-k8s-stack:8429
.
Додавання VMRule з RecordingRule
Тепер додамо новий VMRule, в якому опишемо RecordingRule, в якому будемо генерити метрику vmlogs:eks:pods:backend:api:path_duration:avg
:
apiVersion: operator.victoriametrics.com/v1beta1 kind: VMRule metadata: name: vmlogs-alert-rules spec: groups: - name: VM-Logs-Backend-Pods-Logs # an expressions for the VictoriaLogs datasource type: vlogs rules: - record: vmlogs:eks:pods:backend:api:path_duration:avg expr: | app:="backend-api" "example.co" | unpack_json | extract_regexp "https?://(?P<domain>([^/]+))" | fields _time, path, duration, node_name, domain | path:~".+" | stats by (_time:5m, path, node_name, domain) avg(duration) avg_duration
Деплоїмо, перевіряємо новий VMRule:
$ kk get vmrule | grep vmlogs vmlogs-alert-rules 4s
Логи VMAlert – нова група створена:
$ ktail -l app.kubernetes.io/name=vmalert ... vmalert-vm-k8s-stack-6c5cb6d76d-dxpbf:vmalert 2025-01-08T13:30:43.609Z info VictoriaMetrics/app/vmalert/rule/group.go:486 group "VM-Logs-Backend-Pods-Logs" will start in 1.540718685s; interval=15s; eval_offset=<nil>; concurrency=1 vmalert-vm-k8s-stack-6c5cb6d76d-dxpbf:vmalert 2025-01-08T13:30:45.151Z info VictoriaMetrics/app/vmalert/rule/group.go:486 group "VM-Logs-Backend-Pods-Logs" started; interval=15s; eval_offset=<nil>; concurrency=1 ...
І перевіряємо нову метрику в VMSingle:
Готово.
Тепер можна мігрувати решту Recording Rules з Loki до VictoriaLogs.