
Привет, я Сергей Истомин, DevOps-инженер в KTS. А ниже моя история про построение мультитенантного скоупа кластеров VictoriaMetrics с разными периодами хранения метрик.
Статья будет о том, как собрать систему одновременно и простую, и сложную. Простую потому, что каждый поток данных в ней лаконичный и линейный, и сложную потому, что совокупности этих потоков комбинируются и интегрируются в общие компоненты. Система будет построена на редакции Community Edition.
Надеюсь, что я вас хорошенько запутал и при этом заинтриговал.
Оглавление
Задача
-
Получать, накапливать и предоставлять метрики из нескольких источников различных тенантов (клиентов мониторинга) с различными требованиями к глубине хранения метрик. Для части тенантов срок хранения – 7 дней, для части – 30 дней.
-
Предоставлять единую точку входа для агентов.
-
Предоставлять единую точку для доступа к собранным метрикам.
Решение (коротко)
-
VictoriaMetrics operator.
-
Кластерный режим VictoriaMetrics.
-
Два мультитенантных хранилища, одно со сроком хранения 7 дней, второе — 30 дней.
-
Доступ к данным на основе балансировки и прохождения авторизации в VMAuth.
-
Выделенный VMAlert для каждого тенанта.
-
Хранение и расчет правил VMRules в том же Kubernetes.
-
Общий алерт-менеджер.
-
Общая Grafana.
Среда (на момент установки)
-
Managed Kubernetes в Yandex Cloud (версия 1.31).
-
VictoriaMetrics operator (версия v0.66.1, далее — Оператор).
Проектируем кластер
Чтобы вам было проще погрузиться в проект, я буду описывать систему по частям.
Ниже я привожу несколько схем, так что сразу поясню, как их читать:
-
сплошные стрелки — поток данных на запись;
-
пунктирные стрелки — поток данных на чтение;
-
синие стрелки — данные, хранящиеся 7 дней;
-
зеленые стрелки — данные, хранящиеся 30 дней.
Варианты
Ни один из вариантов не ограничивает нас в записи, хранении и чтении указателей на тенанты. Нужно лишь выбрать, как будет построена композиция.
В полной цепочке кластера в теории участвуют 3 компонента:
-
VMInsert — обеспечивает прием данных и дальнейшую передачу по цепочке в VMStorage;
-
VMStorage — запишет в хранилище данные и будет ими управлять;
-
VMSelect — обеспечит получение данных из хранилищ VMStorage.
Для удобства присвоим группам хранения имена:
|
Имя |
Период хранения метрик |
|
short |
7 дней |
|
long |
30 дней |
Вариант 1. Один общий кластер
Документация VictoriaMetrics описывает сценарий, когда для создания мультиретеншена предлагается сформировать количество групп хранилищ по количеству видов ретеншенов. В нашем случае это две группы хранилищ — short и long.
Для каждой группы указывается свой период хранения и фактор репликации. Также разграничиваются VMInsert и VMStorage, которые будут принимать трафик тенантов, входящих в разные storage-группы. VMSelect может быть как один, так и несколько, нацеленные каждый на свою storage-группу (раз у нас тенанты одновременно в разных группах не живут).

Это сложный в эксплуатации и в сопровождении вариант: такое решение не поддерживается Оператором, поэтому придется строить отношения между элементами своими силами.
Вариант 2. Два кластера (по кластеру на период)
Пусть вас не страшит, что кластеров теперь два. Это всего лишь новое название для тех же логических групп, что и в варианте 1, но в такой логике Оператор понимает задачу и может выстроить связи. А значит, для нас этот вариант проще.
Здесь будут все те же независимые от другой группы (теперь другого кластера) слои VMInsert и VMStorage. Отличие в том, что в рамках объекта VMCluster удобно заказать VMSelect для каждого кластера:

Даунсемплинг
Небольшое отступление. Каждый ценитель VictoriaMetrics с сожалением понимает, что из коробки даунсемплинг работает только в платной enterprise-версии. Если же вам (как и мне) интересно и рыбку съесть, и косточкой не подавиться, всегда можно соорудить костыль.
Чтобы организовать подобие даунсемплинга в Community Edition, нам придется создать отдельный кластер (или storage-группу) на необходимый длительный период хранения и писать туда сразу разреженные и/или агрегированные данные.
То есть собирать метрики и пушить их в хранилище мы будем уже не раз в 20 секунд, а раз в 5 минут, например. Или на стороне агента все так же собирать значение раз в 20 секунд, а затем там же раз в 5 минут вычислять агрегированную метрику и уже ее писать в хранилище.
Что так, что эдак — нужно будет подстраивать ваши дашборды под новую разреженную/агрегированную реальность. Ну и VMSelect вам, возможно, придется вручную сориентировать на чтение из двух кластеров — основного и разреженного/агрегированного. Но это уже совсем другая история.
Решение (не коротко)
Итак, в расчете на возможности Оператора выберем вариант с двумя кластерами.
Обсудим спецификацию для ресурса VMCluster.
retentionPeriod
Это период хранения метрик. Указываем число и единицу измерения: 7d для одного и 30d для другого.
replicationFactor
Количество реплик хранилища. Сама суть replicationFactor сводится только к множественности хранения, он не влияет на скорость чтения напрямую (точно не увеличивает). При этом при смене фактора кластер не будет реплицировать данные и не будет освобождать место. Это просто механизм доступности данных, когда одна из реплик VMStorage по какой-либо причине не отвечает.
Как выбрать его значение? Оно будет зависеть от ответов на вопросы:
-
как выстроена отказоустойчивость вашего Kubernetes, в котором будет работать кластер?
-
какие диски вы будете задействовать под хранилище?
Простой вариант: у вас зональный кластер с одним мастер-узлом и вы выбираете реплицируемые облачным провайдером диски, нивелируя риск отказа хранилища. Тогда можно указать replicationFactor: 1. Это вполне рабочий вариант, в котором вы принимаете риск отказа зоны (из практики необходимо отметить, что отказы бывают).
Более сложный вариант: у вас, например, мультизональный (региональный) кластер с тремя мастер-узлами и вы выбираете нереплицируемые, но самые быстрые диски. Тогда логично указывать replicationFactor: 2 или даже 3.
В нашем случае я ограничился вариантом попроще.
VMInsert, VMStorage, VMSelect
Необходимо указать каждый элемент, иначе он не будет включен в кластер. Для каждого как минимум стоит переопределить количество реплик подов и ресурсы подов. Ресурсы нужно выставлять по факту и прогнозу нагрузки.
VMInsert. В зависимости от нагрузки стоит указать 2 или более реплик. Так вы не будете создавать пробки на трафике агентов.
VMStorage. Сама VictoriaMetrics рекомендует использовать несколько небольших хранилищ вместо одного суммарного. Это связано с задачей обслуживания хранимых данных, их индексации, слияния в куски для долговременного хранения, удаления данных, срок хранения которых истек. Поэтому для начала возьмем, например, три реплики. Важно, что не одну.
Напомню, тут число реплик — это не про репликацию, а про шардирование. А если выставить replicationFactor: 2, то тогда… тоже не будет репликации, но будет отказоустойчивость на уровне архитектуры VictoriaMetrics. Но нам это не нужно, мы используем реплицируемые диски Yandex Cloud.
Итоговое число реплик VMStorage вы поймете только в период эксплуатации. И важно помнить, что изменение числа реплик не приводит к перебалансировке.
Размеры дисков вам нужно будет определить самостоятельно, VictoriaMetrics публикует методику расчета. Как правило, диски можно легко увеличить при необходимости. Важно, что минимум 20 % от дисков должны быть пустыми для должной производительности.
VMSelect. Вот его можно стартовать с одной реплики и наблюдать за нагрузкой на чтение данных.
Получившийся манифест VMCluster
На примере кластера short:
apiVersion: operator.victoriametrics.com/v1beta1kind: VMClustermetadata: name: short namespace: monitoringspec: replicationFactor: 1 retentionPeriod: 7d vminsert: replicaCount: 2 resources: {} vmselect: replicaCount: 1 resources: {} vmstorage: replicaCount: 3 resources: {} storage: volumeClaimTemplate: spec: resources: requests: storage: 10Gi storageClassName: yc-network-ssd
Все параметры спецификации VMCluster можно найти в документации API.
Проектируем потоки тенантов
Суть использования мультитенантной архитектуры сводится к разделению и управлению потоками данных — как на запись, так и на чтение. Каждому тенанту вы можете назначить разные независимые пары логинов и паролей для записи данных и их последующего чтения. На основе кредов будут предоставлены соответствующие возможности. Более того, вы можете в рамках одного тенанта начать делить потоки по проектам — как группы и подгруппы данных.
VictoriaMetrics всегда использует идентификаторы тенанта и его проекта, по умолчанию это «0» и «0». Идентификаторы хранятся в лейблах vm_account_id и vm_project_id для каждого временного ряда. Если вы не используете специальные мультитенантные URL для извлечения данных, то эти лейблы просто срезаются и вы их не видите, но в хранилище они есть всегда.
Итого, нам необходимо присвоить каждому тенанту его номер и имя для удобства. Ноль пропустим, начнем с единицы — это будет идентификатор тенанта vm_account_id. А идентификатор проекта у каждого тенанта использовать не будем, система везде проставит ноль (vm_project_id).
|
Номер тенанта |
Алиас тенанта |
Целевой кластер |
|
1 |
anton |
short |
|
2 |
betty |
short |
|
3 |
christopher |
long |
|
4 |
desdemona |
long |
Запросы извне
Для недоверенной среды вне кластера удобно использовать VMAuth. По сути это прокси-сервер, который в зависимости от предъявляемых к авторизации данных может перенаправить запрос. При этом пути в запросе фильтруются белым списком, а также могут быть скорректированы через добавление в начало части пути. Работа с путями обеспечивается Оператором через объекты VMUser.
Манифест для VMAuth:
apiVersion: operator.victoriametrics.com/v1beta1kind: VMAuthmetadata: name: all namespace: monitoringspec: ingress: enabled: true host: vmauth.example.com name: vmauth pathType: Prefix paths: - / tlsHosts: - vmauth.example.com tlsSecretName: vmauth.example.com replicaCount: 1 resources: {} selectAllByDefault: false userSelector: matchLabels: vmuser-enabled: 'true'
Что здесь важно:
-
.spec.ingress— запрашиваем создание ингресса; -
.spec.selectAllByDefault— не берем все имеющиеся VMUser под анализ, а будем использовать селектор; -
.spec.userSelector.matchLabels— указываем, по каким лейблам отбираем себе VMUser для конфигурации.
Все параметры спецификации VMAuth можно найти в документации API.
Запись данных
В рамках задачи у нас есть внешние агенты для каждого тенанта, которым необходимо обеспечить простой способ записи метрик. В схеме появляются объекты VMUser.

На каждом тенанте создаем столько ресурсов VMUser, сколько отдельно администрируемых агентов сбора метрик у него есть. То есть, если у тенанта есть два независимых агента, то для него можно организовать два VMUser. А если для тенанта необходим либо внешний, либо ограниченный внутренний (в рамках вашей доверенной сети) доступ, то можно организовать отдельный VMUser для чтения. При этом VMUser — это абстракция и лишь способ сконфигурировать VMAuth. Стрелки на схеме, проходящие через VMUser, условны.
Пример манифеста для записи данных:
apiVersion: operator.victoriametrics.com/v1beta1kind: VMUsermetadata: labels: vmuser-enabled: 'true' name: tenant-1-anton-writer1 namespace: monitoringspec: username: anton-writer1 passwordRef: name: vmusers-credentials key: anton-writer1 targetRefs: - crd: kind: VMCluster/vminsert name: short namespace: monitoring paths: - /api/v1/write target_path_suffix: /insert/1/prometheus
Что здесь важно:
-
.metadata.labels— по этим меткам наш VMAuth будет отбирать себе в конфигурацию объекты VMUser. Таким образом обеспечивается режим включения/отключения на лету конкретных учетных данных без их удаления; -
.spec.username— логин для базовой авторизации; -
.spec.passwordRef— указатель на секрет (.spec.passwordRef.name) и ключ (.spec.passwordRef.key), содержащий пароль для базовой авторизации; -
.spec.targetRefs[0].crd— немного магии для формирования конфига vmauth: тут мы лишь указываем, на какой компонент какого кластера нацелен наш пользователь.spec.usernameпри запросах, а адрес нужного сервиса пропишет Оператор; -
.spec.targetRefs[0].paths— те самые разрешенные пути в запросах пользователя; -
.spec.targetRefs[0].target_path_suffix— префикс в начале пути, указывающий на тенант 1.
Все параметры спецификации VMUser можно найти в документации API.
Чтение данных
Если реализовать доступ к данным тенанта из недоверенной зоны вам тоже нужно через авторизацию, то можно создать VMUser с путями для чтения.
apiVersion: operator.victoriametrics.com/v1beta1kind: VMUsermetadata: labels: vmuser-enabled: 'true' name: tenant-1-anton-reader1 namespace: monitoringspec: username: anton-reader1 passwordRef: name: vmusers-credentials key: anton-reader1 targetRefs: - crd: kind: VMCluster/vminsert name: short namespace: monitoring paths: - /api/v1/query - /api/v1/query_range - /api/v1/label/.*/values - /api/v1/labels - /api/v1/series target_path_suffix: /select/1/prometheus
Все параметры спецификации VMUser можно найти в документации API.
Совмещение чтения и записи
Вы можете в рамках одного VMUser обеспечить и чтение, и запись, указав две соответствующие позиции в списке .spec.targetRefs, но в нашем случае такой потребности нет.
Grafana
Каждому тенанту мы приготовим по датасорсу для Grafana. В URL на запросы будет указан номер тенанта.

Чисто из спортивного интереса вы можете сделать датасорс на путь, по которому получаете данные из VMSelect по всем тенантам. Тогда вы сможете увидеть те самые лейблы vm_account_id и vm_project_id. Но выводить эти данные на типовые дашборды будет неудобно — они на такое не рассчитаны.
Добавлять датасорсы в Grafana можно по-разному. Мы добавим через файловый провиженинг. Для этого при установке Grafana нам необходимо включить в чарте соответствующий сайдкар. Как это сделать — см. ниже.
Манифест конфигмапа с датасорсом:
apiVersion: v1kind: ConfigMapmetadata: name: tenant-1-anton namespace: monitoring labels: grafana_datasource: '1'data: tenant-1-datasource.yaml: | apiVersion: 1 deleteDatasources: - name: "Anton" prune: true datasources: - name: "Anton" uid: anton type: prometheus access: proxy url: http://vmselect-short.monitoring.svc:8481/select/1/prometheus isDefault: true editable: false
Что здесь важно:
-
.metadata.labels— по этим меткам сайдкар Grafana поймет, что этот конфигмап содержит датасорс. -
.data.tenant-1-datasource.yaml— под этим ключом лежит содержимое файла для провиженинга, составленного по спецификации Grafana.
Саму Grafana можно поставить из родного хелм-чарта. Нам важно добавить в ее поды сайдкары, как минимум для чтения датасорсов.
Вот часть values.yaml для этого:
sidecar: datasources: enabled: true searchNamespace: ALL label: grafana_datasource labelValue: '1' resource: both initDatasources: false
После установки Grafana мы сможем наблюдать поступающие метрики по всем четырем датасорсам.
Если потребуется сбор метрик внутри Kubernetes (внутренний агент, расширение кластера long)
Чтобы собирать метрики с Kubernetes-кластера, в котором развернута вся наша система, нам будет нужен внутренний агент. Возьмем VMAgent. Предположим также, что этот внутренний агент является источником для тенанта-5, и данные нужно хранить 30 дней, как для тенантов 3 и 4. Тогда мы просто подселим нового тенанта в кластер long, и его данные начнут равномерно распределяться по имеющимся VMStorage. Для этого нужно просто начать писать в VMInsert данные с указанием на 5-й тенант, никакой другой настройки для начала не потребуется.
Но создается дополнительная нагрузка на VMInsert и VMStorage, это надо учесть. Смотрите за метриками, есть отличные дашборды от самой VictoriaMetrics: CPU и память относительно лимитов, ошибки интерконнекта, достижение порогов/лимитов операций, Storage full ETA — прогноз времени на 100 % заполнение хранилища.
Чтобы не загромождать схему, давайте просто заменим четвертый тенант нашим Kubernetes. Тогда его агент будет не внешним, а внутренним.

Вот манифест внутреннего агента:
apiVersion: operator.victoriametrics.com/v1beta1kind: VMAgentmetadata: name: tenant-4-desdemona namespace: monitoringspec: extraArgs: promscrape.dropOriginalLabels: 'true' promscrape.streamParse: 'true' remoteWrite: - url: "http://vminsert-long.monitoring.svc:8480/insert/4/prometheus/api/v1/write" resources: {} scrapeInterval: 20s selectAllByDefault: true
Что здесь важно:
-
.spec.remoteWrite— тут нам нужно самим указать адрес сервиса, ведущего к VMInsert, Оператор тут не поможет. Важно не ошибиться в названии кластера и в номере тенанта; -
.spec.selectAllByDefault— агент всеми своими инстансами может монопольно считывать все манифесты скрейпинга VictoriaMetrics (VMNodeScrape, VMPodScrape, VMServiceScrape, VMStaticScrape). Если в Kubernetes установлены CRD от Prometheus Operator, то его PodMonitor, Probe и ServiceMonitor будут сконвертированы в манифесты скрейпинга VictoriaMetrics.
Все параметры спецификации VMAgent можно найти в документации API.
Правила алертинга и предрасчеты
Для каждого тенанта необходим VMAlert, чтобы выполнить 2 задачи:
-
подготовить алерты (alerting rules);
-
подготовить предрасчеты (recording rules).
Одним VMAlert обойтись невозможно, так как нужно явно указать VMCluster, с которым мы работаем. По одному VMAlert на кластер VMCluster обойтись тоже невозможно, поскольку у тенантов точно могут быть разные VMRule (алерты и предрасчеты). А указать в VMRule, к какому тенанту относится правило — угадаете? Правильно, тоже невозможно, так как у нас Community Edition, и такое указание будет проигнорировано.
Поэтому каждому тенанту — по VMAlert.
Все правила алертинга (alerting rules) и рекординга (recording rules) мы будем содержать в том же Kubernetes, что и кластеры VictoriaMetrics. Чтобы все VMAlert разобрались, кому какие алерты и предрасчеты нужно подготовить, в их спеках будет отключена опция отбора всего и будет включена опция отбора по лейблам. То есть каждому VMRule в лейблы нужно будет прописать применимость на том или ином тенантном VMAlert (а может, и на всех сразу).

Манифест для VMAlert на примере первого тенанта:
apiVersion: operator.victoriametrics.com/v1beta1kind: VMAlertmetadata: name: tenant-1-anton namespace: monitoringspec: datasource: url: http://vmselect-short.monitoring.svc:8481/select/1/prometheus evaluationInterval: 20s externalLabels: tenant: anton notifiers: - url: http://vmalertmanager-all-0.vmalertmanager-all.monitoring:9093 remoteRead: url: http://vmselect-short.monitoring.svc:8481/select/1/prometheus remoteWrite: url: http://vminsert-short.monitoring.svc:8480/insert/1/prometheus resources: {} ruleNamespaceSelector: {} ruleSelector: matchLabels: rules-for-tenant-1-anton: 'true' selectAllByDefault: false
Что здесь важно:
-
.spec.datasource.url— указываем на сервис, за которым стоит VMSelect кластера, читаем с него данные для вычисления выражений; -
.spec.externalLabels— обязательно указываем, какими именно лейблами мы хотим отличать в алерт-менеджере одни алерты от других. Дело в том, что агенты вне Kubernetes могут добавить, а могут и не добавить нужных вам лейблов. А этот параметр позволяет вам назначить нужные лейблы самостоятельно; -
.spec.notifiers[0].url— указываем на алерт-менеджер (его мы уже совсем скоро опишем ниже); -
.spec.remoteRead.url— указываем на сервис, за которым стоит VMSelect кластера, читаем с него стейт при перезапуске; -
.spec.remoteWrite.url— указываем на сервис, за которым стоит VMInsert кластера, пишем в него вычисления; -
.spec.ruleNamespaceSelector— выставляем значение{}, чтобы читать VMRule с любых неймспейсов, и в то же время отключить selectAllByDefault; -
.spec.ruleSelector.matchLabels— указываем, какие лейблы должны быть у VMRule, чтобы этот VMAlert их обработал.
Все параметры спецификации VMAlert можно найти в документации API.
Пример манифеста для VMRule с лейблами, который будет прочитан всеми VMAlert, кроме того, что обслуживает третий тенант:
apiVersion: operator.victoriametrics.com/v1beta1kind: VMRulemetadata: labels: rules-for-tenant-1-anton: 'true' rules-for-tenant-2-betty: 'true' rules-for-tenant-4-desdemona: 'true' name: test namespace: monitoringspec: groups: - interval: 1m name: example rules: - alert: TestAlert annotations: summary: Test alert expr: vector(1) for: 1m labels: severity: warning
Так вы сможете настроить универсальные алерты для всех тенантов, а специфичные — только для некоторых (или даже для одного). Манифесты можно разложить по неймспейсам, их найдут.
Все параметры спецификации VMRule можно найти в документации API.
Алерт-менеджер
Чтобы поделиться с миром алертами, нам потребуется алерт-менеджер. Одного хватит на все VMAlert. Опционально можно дополнить сетап датасорсом алерт-менеджера, чтобы Grafana могла реализовать отправку своих алертов через те же каналы, что и алерты из VMRule.

Манифест алерт-менеджера:
apiVersion: operator.victoriametrics.com/v1beta1kind: VMAlertmanagermetadata: name: all namespace: monitoringspec: disableNamespaceMatcher: true externalURL: '' resources: {} routePrefix: /
Все параметры спецификации VMAlertmanager можно найти в документации API.
Пример манифеста конфига алерт-менеджера с разделением алертов по ресиверам в зависимости от тенанта:
apiVersion: operator.victoriametrics.com/v1beta1kind: VMAlertmanagerConfigmetadata: name: default namespace: monitoringspec: receivers: - name: blackhole - name: anton_receiver - name: betty_receiver - name: christopher_receiver - name: desdemona_receiver route: group_by: - ... group_interval: 10m group_wait: 30s receiver: blackhole repeat_interval: 24h routes: - matchers: - tenant = "anton" receiver: anton_receiver - matchers: - tenant = "betty" receiver: betty_receiver - matchers: - tenant = "christopher" receiver: christopher_receiver - matchers: - tenant = "desdemona" receiver: desdemona_receiver - receiver: blackhole
Все параметры спецификации VMAlertmanagerConfig можно найти в документации API.
Итоги
Главный вывод: задача решаемая и легко масштабируемая. Оператор берет на себя значимую часть работы, нам остается не заблудиться в лейблах. Весь сетап хорошо раскладывается через хелм-чарт.
Выкладывать свой я постесняюсь, в нем пока не решены некоторые вопросы, за которые общественность может и спросить. Но могу точно утверждать, что реализовать логику распределения лейблов, названий и адресов сервисов нетрудно.
Спасибо, что дочитали до конца. Можете и другие мои статьи почитать:
ссылка на оригинал статьи https://habr.com/ru/articles/1044102/