Продовжуємо міграцію з 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 - полів у нас тут забагато, всі вони нам не потрібні – використаємо
fieldspipe, і залишимо тільки ті, які будемо використовувати - можемо додати фільтр
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.














