Доброго, товарищи инженеры!
Меня зовут Алексей Зернов. В этом материале речь пойдёт о разделении метрик в VictoriaMetrics на два независимых потока. Статья не предполагает глубокого погружения в базовые сущности VictoriaMetrics и холивары по использованию того или иного инструмента для работы с метриками – в конце концов, инструмент остаётся всего лишь инструментом.
Проблема, которую мы решаем, формулируется следующим образом: на текущий момент нам необходимо хранить метрики за 180 дней, что создаёт сложности как с хранением данных, так и с обработкой запросов к ним.
Простое агрегирование всех метрик не является оптимальным решением, так как приводит к потере детализации для оперативного мониторинга. В то же время хранение сырых данных увеличивает объём storage до 3 ТБ.
Не то чтобы было сильно жалко дискового пространства, но зачем?
Multi Retention подходит для этой задачи, но работает только на VictoriaMetrics Enterprise. Поскольку мы используем open-source версию, нам потребовалось альтернативное решение — разделение метрик между разными хранилищами.
Решение выглядит так:
В первом хранилище метрики будут доступны 30 дней в первозданном виде, без изменений. Хранилище сырых метрик для наблюдаемости, мониторинга и реагирования на инциденты в моменте.
Второе хранилище на 180 дней, и метрики там будут доступны в агрегированном за 5 минут виде.
Введение
Прежде чем начать, стоит немного разобраться, с чем нам предстоит столкнуться. Как уже было сказано ранее, "статья не предполагает глубокого погружения в базовые сущности", поэтому описание будет кратким. Итак:
Использованные компоненты VictoriaMetrics
vmagent – Агент для сбора метрик. Может копать, может не копать.
vminsert – На нём приём и запись метрик в vmstorage.
vmstorage – Отвечает за хранение метрик.
vmselect – Выполняет запросы метрик из vmstorage.
Основная работа будет выполнена с этими сущностями, поэтому vmalert, vmauth, alertmanager и т.д. не описываем.
Целевая схема
Целевая схема решения выглядит вот так:

Чтобы удобнее описывать добавим на схему точки:

Описание целевой схемы
Vmaget в кластере k8s-cluster number-two собирает метрики с этого кластера и отправляет их VMAgent_gateway(4).
Node Exporters развёрнуты на серверах, виртуальных машинах и собирают с них метрики. Эти метрики забирает VMAgent_Gateway(4).
Метрики кластера, собранные через victoria-kube-state-metrics. Эти метрики также забирает VMAgent_Gateway(4).
VMagent_gateway. Забирает метрики со всех сущностей, которые есть в scrape конфигах, также принимает метрики от других агентов. Агент именно из victoria-metrics-k8s-stack выбран как gateway, потому что при развёртывании оператора, сущности оператора автоматически собирают метрики кластера, а также все таргеты в скрейпах по дефолту обрабатывает именно этот агент. VMagent_gateway через remoteWrite пушит все метрики в VMAgent_raw(6).
RAW-Cluster. Кластер для хранения сырых (необработанных) метрик. Компоненты:
VMinsert_raw: Вставляет сырые метрики полученные от VMAgent_raw(6) в хранилище.
VMStorage_raw: Хранилище сырых метрик.
VMSelect_raw: Позволяет выполнять запросы сырых метрик, хранящихся в VMStorage_raw
VMAgent_raw. Получает сырые метрики от VMAgent_gateway и через remoteWrite пушит их в VMInsert_raw и VMAgent_aggr. На VMAgent_raw полностью отключены все скрейпы.
VMAgent_aggr. Получает сырые метрики от VMAgent_raw, агрегирует и отправляет дальше в VMInsert_aggr. Скрейпы полностью отключены.
AGGR-Cluster. Кластер для хранения агрегированных метрик. Компоненты:
VMIsert_aggr: Получает агрегированные метрики от VMAgent_aggr и отправляет в хранилище. - VMStorage_aggr: Хранилище агрегированных метрик.
VMSelect_aggr: Позволяет выполнять запросы агрегированных метрик, хранящимся в VMStorage_aggr.
Grafana. Подключается к VMSelect_raw и VMSelect_aggr для выполнения запросов и получения данных для визуализации.
Если подвести итог, то мы получили следующие ключевые особенности архитектуры:
Разделение потоков данных (сырые/агрегированные).
Многоуровневая обработка метрик.
Централизованный сбор через gateway-узел.
Гибкость запросов через отдельные точки доступа.
Масштабируемость каждого компонента. Как можно заметить, такая схема отлично закрывает все наши боли.
Расчёт объёмов vmstorage
Прежде чем пойти дальше, нужно посчитать, какой storage и какое количество дискового пространства будут использовать.
Исходить будем из того, что:
На сегодня обёем текущего vmstorage – 3ТБ.
Храним сырые метрики 180 дней. Рассчитываем необходимый объём на сырые метрики 30 дней.
Сырые:
Берём 1/6 от 3 ТБ → 512 ГБ (месяц 1/6 от полугода).
Запас на отказоустойчивость (чтобы, если одна нода упадёт, всё не сгорело).
Формула: ((объём / кол-во нод) * (кол-во нод / 2)) + объём.
(6 нод): ((512 / 6) * 3) + 512 ≈ 768 ГБ.
Запас на рост (30%) (потому что метрики имеют привычку неожиданно расти).
512 + 30% ≈ 666 ГБ.
Агрегированные:
Сжатие в ~20 раз (300s / 15s) → 153 ГБ.
Объясню подробнее: у нас исходные метрики приходят каждые 15 секунд (стандартный scrape_interval), мы их агрегируем в 5-минутные интервалы (300 секунд).
300s / 15s = 20 (столько исходных точек "упаковываем" в одну агрегированную).
Отсюда и получаем примерное 20-кратное сжатие по объёму. Но это в теории, на самом деле значение усреднённое. Но, принимая его за константу, получаем:
3ТБ сырых → 3 ТБ / 20 ≈ 0.15 ТБ (153 ГБ) агрегированных.
Запас на отказоустойчивость**:**
(6 нод):((153 / 6) * 3) + 153 ≈ 230 ГБ
Запас на рост (30%)
230 + 30% ≈ 299 ГБ
Итого:
• RAW-кластер: 700-800 ГБ
• AGGR-кластер: 250-300 ГБ
В общем, на основе этих расчётов был использован 1ТБ на сырые, 1ТБ на агрегированные. М-математика.
Использованные Helm-чарты
Для решения задачи было принято решение использовать вот такие чарты:
victoria-metrics-k8s-stack-0.36.0 – версия 0.36 (appVersion: v1.110.0), покрывает части 4, 5, схема 2.
victoria-metrics-cluster – версия 0.18.1(appVersion: v1.110.0), покрывает часть 8, схема 2.
victoria-metrics-agent – версия 0.16.2(appVersion: v1.110.0), использован в 1, 6 и 7 пунктах, схема 2.
Про развёртывание
Для развёртывания чартов используется ArgoCD, но углубляться в него здесь нет необходимости. Краткой схемы, которая используется для развертывания, достаточно для контекста.
victoria-metrics(victoria-metrics-k8s-stack-0.36.0) – основной стек для сбора, хранения метрик. В нем же vmagent-gateway.
victoria-configs(Manifests) – конфиги для victoria-metrics(victoria-metrics-k8s-stack-0.36.0), (scrape, rules, recording rules).
victoria-secrets(Manifests) – секреты (SMTP, webhook, API-ключи и т. д.)
victoria-aggr(victoria-metrics-cluster) – хранение агрегированных метрик.
victoria-aggr-agent(victoria-metrics-agent) – агент, который принимает, агрегирует и отправляет в vminsert уже агрегированные метрики.
victoria-raw-agent(victoria-metrics-agent) – агент, который принимает от vmagent-gateway метрики и отправляет их в vminsert-raw и vmagent-aggr.
victoria-agent(victoria-metrics-agent) – на кластере k8s-cluster number-two.
Values для всего
Итак, с теоретической частью закончили, можно переходить к values файлам. Опять же, не хочется превращать короткую статью в посимвольное описание типовых values, поэтому добавим только то, что действительно нужно.
Дедупликация, репликация, шардирование, количество метрик в очереди и иже с ними – это тоже не тема статьи, а личный выбор каждого. Поэтому по ним только поверхностно.
С первого values.
Victoria-metrics(victoria-metrics-k8s-stack-0.36.0)
# Секция vmcluster
vmcluster:
enabled: true # Включаем установку кластера VictoriaMetrics
annotations: {}
spec:
retentionPeriod: "30d" # Храним данные 30 дней
replicationFactor: 1 # Репликация данных отключена (1 = без репликации)
# Отказоустойчивость отдана на откуп цефу, с нас шаридирование.
# Настройки vmstorage
vmstorage:
image:
repository: artifactory.com/vmstorage
tag: v1.110.0-cluster
replicaCount: 6 # 6 реплик vmstorage для шардирования(6 шардов, быстро, модно)
storageDataPath: /vm-data # Путь для данных внутри контейнера
storage:
volumeClaimTemplate:
spec:
resources:
requests:
storage: 165Gi # По 165GB на каждый pod (6*165GB = ~1TB общего хранилища)
storageClassName: metrics-csi-sc # Используем Ceph RBD
volumeMode: Filesystem
resources:
limits:
cpu: "12"
memory: "80Gi"
requests:
cpu: "3"
memory: "30Gi"
extraArgs:
search.maxUniqueTimeseries: "1000000" # Лимит уникальных временных рядов
# Настройки vmselect
vmselect:
image:
repository: artifactory.com/vmselect
tag: v1.110.0-cluster
port: "8481"
replicaCount: 4 # 4 реплики для одновременной обработки запросов
cacheMountPath: /select-cache # Путь для кеша
extraArgs:
search.maxConcurrentRequests: "100" # Увеличено с дефолтных 16. Ограничивает максимальное количество запросов, которые vmselect может обрабатывать одновременно.
search.maxQueryDuration: 120s # Увеличено с 60s. Максимальное время выполнения одного запроса. Если запрос выполняется дольше — он принудительно завершается с ошибкой.
search.maxQueryLen: "510000" # Лимит длины запроса. Ограничивает длину запроса в байтах (например, длинный PromQL или URL с фильтрами). Дефолтный 16КБ, у нас 500КБ
search.maxSeries: "1000000" # Макс кол-во series в ответе. Максимальное количество time series, которые могут быть возвращены в одном ответе.
search.maxUniqueTimeseries: "900000" # Увеличено с 300k. Ограничивает количество уникальных рядов, которые vmselect будет обрабатывать в течение 5 минут (кэш запросов).
search.maxQueueDuration: "5m" # Макс время в очереди. Максимальное время, которое запрос может ждать в очереди, если все слоты (maxConcurrentRequests) заняты.
search.logSlowQueryDuration: 180s # Логирование медленных запросов >180s. Логирует медленные запросы (duration > 180s) в деталях.
storage:
volumeClaimTemplate:
spec:
resources:
requests:
storage: 5Gi # 5GB на кеш для каждого vmselect
storageClassName: metrics-csi-sc
volumeMode: Filesystem
resources:
limits:
cpu: "2"
memory: "8Gi"
requests:
cpu: "1"
memory: "4Gi"
# Настройки vminsert
vminsert:
image:
repository: artifactory.com/vminsert
tag: v1.110.0-cluster
port: "8480"
replicaCount: 2 # 2 реплики для входящего трафика
resources:
limits:
cpu: "4"
memory: 4Gi
requests:
cpu: "2"
memory: "2Gi"
extraArgs:
insert.maxQueueDuration: 5m0s # Макс время в очереди на вставку
maxConcurrentInserts: "128" # Макс параллельных вставок
maxLabelsPerTimeseries: "36" # Лимит лейблов на временной ряд
# Секция vmagent (4) на схеме 2, он же gateway
vmagent:
enabled: true
spec:
selectAllByDefault: true
scrapeInterval: 25s
# -- Глобальные метки, добавляемые ко всем метрикам
externalLabels:
source_metrics: k8s-cluster-number-one # Идентификатор кластера
# -- Основные эндпоинты для отправки метрик (remote-write)
remoteWrite:
# -- Отправка на raw agent точка (6) на схеме 2
- url: "http://victoria-raw-agent-victoria-metrics-agent.victoriametrics:8429/api/v1/write"
extraArgs:
promscrape.streamParse: "true" # -- Парсить метрики в потоковом режиме (экономит память)
promscrape.dropOriginalLabels: "true" # -- Удалять оригинальные labels после обработки
promscrape.maxScrapeSize: "671088640" # -- Максимальный размер одного scrape (64MB)
remoteWrite.queues: "10" # -- Количество очередей для отправки метрик
Этого достаточно для первичной настройки, node-exporters, kube-state-metrics и т.д. включены, но не обсуждаем.
Victoria-aggr(victoria-metrics-cluster)
Просто включили кластерные компоненты (vminsert, vmselect, vmstorage), настроили ресурсы, добавили период хранения для vmstorage, и всё на этом, остальное не нужно.
vmselect:
enabled: true
vminsert:
enabled: true
vmstorage:
enabled: true
retentionPeriod: "180d"
Victoria-aggr-agent(victoria-metrics-agent)
Здесь добавили remoteWrite до Victoria-aggr(victoria-metrics-cluster) (8), схема 2, конфигурацию агрегации (все метрики за 5 минут), а также отключили скрейп.
remoteWrite:
- url: "http://victoria-aggr-victoria-metrics-cluster-vminsert.victoriametrics:8480/insert/0/prometheus/api/v1/write"
extraArgs:
envflag.enable: true
envflag.prefix: VM_
loggerFormat: json
httpListenAddr: :8429
remoteWrite.streamAggr.config: /etc/vmagent/extra/remotewrite_streamAggr_config.yml
promscrape.config: /etc/vmagent/extra/promscrape_config.yml
remoteWrite.streamAggr.keepInput: "false"
streamAggr.dedupInterval: "5m" # Дедупликация перед агрегацией
remoteWrite.streamAggr.dedupInterval: "5m"
# -- Extra Volumes for the pod
extraVolumes:
- name: config-volume
configMap:
name: victoriametrics-agent-streamaggr-config
# -- Extra Volume Mounts for the container
extraVolumeMounts:
- name: config-volume
mountPath: /etc/vmagent/extra
extraObjects:
- apiVersion: v1
kind: ConfigMap
metadata:
name: victoriametrics-agent-streamaggr-config
data:
remotewrite_streamAggr_config.yml: |
- match: '{__name__=~".+"}'
interval: 5m
outputs: [count_samples, sum_samples, min, max, avg]
promscrape_config.yml: |
global:
scrape_interval: 5m
scrape_configs: []
Victoria-raw-agent(victoria-metrics-agent)
Здесь мы добавляем два remoteWrite в соответствии со схемой, точки 6 - > 5, 6 -> 7. А также отключаем скрейп этим агентом.
remoteWrite:
- url: "http://victoria-aggr-agent-victoria-metrics-agent.victoriametrics:8429/api/v1/write" # в след. агент на агрегацию
- url: "http://vminsert-victoria-victoria-metrics-k8s-stack.victoriametrics:8480/insert/0/prometheus/api/v1/write" # сырые метрики
extraObjects:
- apiVersion: v1 # Для отключения используем конфигмап с пустым скрейпом.
kind: ConfigMap
metadata:
name: victoriametrics-agent-promscrape-config-raw
data:
promscrape_config.yml: |
global:
scrape_interval: 15s
scrape_configs: []
# -- VMAgent extra command line arguments
extraArgs:
envflag.enable: true
envflag.prefix: VM_
loggerFormat: json
httpListenAddr: :8429
promscrape.config: /etc/vmagent/extra/promscrape_config.yml
promscrape.dropOriginalLabels: "true"
Victoria-agent(victoria-metrics-agent)
В этом values нам достаточно указать агенту, куда отправлять метрики кластера и добавить ему лейбл.
vmagent:
enabled: true
spec:
image:
repository: artifactory.com/vmagent
selectAllByDefault: true
scrapeInterval: 20s
externalLabels:
source_metrics: k8s-cluster-number-two
extraArgs:
promscrape.streamParse: "true"
promscrape.dropOriginalLabels: "true"
remoteWrite.queues: "10"
remoteWrite.url: "https://vmagent-gateway.com/api/v1/write"
Картинки результата.
Сейчас настало время посмотреть, что же вышло по итогу.
Вот так сейчас выглядят агрегированные метрики на тестовом приложении:

И так выглядят сырые метрики:

Все вышло так, как и планировалось. Но если смотреть в Grafana, то могут возникнуть сомнения, ведь точки на дашборде с агрегированными метриками есть не только на 5 минутах, а также на промежутках, вот так:

Поэтому проверим через vmui c raw-query, чтобы убедиться наверняка:

Вот теперь точно всё по плану.
Вывод.
Наше решение с разделением метрик на сырые (30 дней) и агрегированные (180 дней) потоки доказало свою жизнеспособность, но, как и любая архитектура, имеет свои сильные и слабые стороны.
Теперь преимущества:
Экономия ресурсов. Сокращение объёма хранилища с 3ТБ до 2ТБ без потери критичных данных.
Сохранение гибкости.
Сырые метрики доступны для детального анализа инцидентов, т.е. мониторинг и наблюдаемость в моменте никуда не делись.
Агрегированные данные позволяют строить долгосрочные тренды без «раздувания» по объёму.
Масштабируемость. Каждый компонент (RAW/AGGR) можно масштабировать независимо.
Реализовали аналог Multi Retention без VictoriaMetrics Enterprise.
Появилось два независимых источника данных, каждый из которых работает сам по себе. Можно, конечно, смотреть на это как на плюс, так и на минус. Но фактически сложность построения дашбордов не изменилась, т.к. нет никаких проблем на двух разных панелях в одном дашборде использовать разные источники.
Ну и минусы, соответственно:
Усложнение инфраструктуры.
Дополнительные компоненты (vmagent-raw, vmagent-aggr) требуют мониторинга и поддержки.
Риск «разрыва» цепочки передачи данных между агентами.
Потеря детализации в долгосрочных данных. Агрегация 5-минутных интервалов может скрыть аномалии, длящиеся 1–2 минуты. Но это минус больше перестраховка, у нас же есть сырые метрики.
Два разных источника. Неудобство. Но, как и писал выше, это – как минус, так и плюс.
Итог.
Решение подходит для нашей задачи, где оперативный мониторинг важнее долгосрочной детализации, но долгосрочное хранение быть должно. Главное — баланс между:
экономией (2 ТБ вместо 3 ТБ)
гибкостью (сырые + агрегированные данные),
производительностью (разделение нагрузки).
Однако за это приходится платить усложнением инфраструктуры. Но, к этому мы были готовы на этапе планирования.
Это статья планировалась, скорее, как короткий очерк, без глубоких рассуждений и технических тонкостей. Прежде чем дойти до такой архитектуры была проверены несколько концепций, но они оказались непригодны. Адекватно принимается любая конструктивная обратная связь)))