В последнее время Prometheus стал де-факто стандартом для сбора и хранения метрик. Он удобен для разработчиков ПО - экспорт метрик можно реализовать в несколько строк кода. Для DevOps/SRE, в свою очередь, есть простой язык PromQL для получения метрик из хранилища и их визуализации в той же Grafana.
Но Prometheus имеет ряд недостатков, способы устранения которых я хочу рассмотреть в этой статье. Также разберём деплой Cortex - распределённого хранилища метрик.
Недостатки
Отсутствие отказоустойчивости
Prometheus работает только в единственном экземпляре, никакого HA.
Отсутствие распределения нагрузки
В принципе, он хорошо скейлится вверх с ростом количества ядер. Так что это проблема только для тех у кого действительно много метрик.
Нет поддержки multi-tenancy
Все метрики летят в один большой котёл и разгребать их потом используя PromQL и метки не всегда удобно. Часто хочется разделить различные приложения и\или команды по своим песочницам чтобы они друг другу не мешали.
Плюс, многие готовые дашборды для Grafana не готовы к тому что много инстансов одного приложения хранят метрики в одном и том же месте - придётся переделывать все запросы, добавлять фильтры по меткам и так далее.
В принципе, все эти проблемы можно решить настроив несколько HA-пар Prometheus и распределив по ним свои приложения. Если перед каждой парой повесить прокси, то можно получить что-то вроде отказоустойчивости.
Но есть и минусы:
После того как один хост из пары упадёт/перезагрузится/whatever - у них случится рассинхронизация. В метриках будут пропуски.
Все метрики приложения должны умещаться на один хост
Управлять таким зоопарком будет сложнее - какие-то из Prometheus могут быть недогружены, какие-то перегружены. В случае запуска в каком-нибудь Kubernetes это не так важно.
Давайте рассмотрим какими ещё способами можно решить это.
PromQL прокси
Например promxy, который размещается перед 2 или более инстансами Prometheus и делает fan-out входящих запросов на все из них. Затем дедуплицирует полученные метрики и, таким образом, закрывает пропуски в метриках (если, конечно, они не попали на один и тот же временной интервал).
Минусы подобного решения на поверхности:
Один запрос нагружает сразу все инстансы за прокси
Прокси решает только проблему с пропусками в метриках.
Но для тех, у кого нагрузка укладывается в возможности одного Prometheus (либо ее можно грамотно раскидать по нескольким HA-парам) и кому не нужен multi-tenancy - это очень хороший вариант.
Thanos
Thanos - это уже более продвинутое решение.
Он устанавливает рядом с каждым инстансом Prometheus так называемый Sidecar - отдельный демон, который подглядывает за блоками данных, которые генерирует Prometheus. Как только блок закрывается - Sidecar загружает его в объектное хранилище (S3/GCS/Azure). Длина блоков в Prometheus прибита гвоздями и равна 2 часам.
Также он является прокси между GRPC Thanos StoreAPI и Prometheus для получения метрик, которые еще не были загружены в объектное хранилище.
Отдельный компонент Querier реализует PromQL: в зависимости от временного интервала запроса и настроек глубины хранения данных в Prometheus он может направить его в объектное хранилище, в Sidecar или в разбить на два подзапроса - для свежих данных запрос пойдёт через Sidecar в Prometheus, а для более старых - в объектное хранилище.
Отказоустойчивость свежих данных в Thanos реализуется примерно так же как и в promxy - делается fan-out запросов на все причастные сервера, результаты накладываются друг на друга и дедуплицируются. Задача по защите исторических данных лежит на объектном хранилище.
Multi-tenancy есть в некотором зачаточном состоянии, но в эту сторону проект, судя по всему, не развивается особо.
Cortex
Это наиболее сложный и функциональный проект. Его начали разрабатывать в Grafana Labs для своего SaaS решения по хранению метрик и несколько лет назад выложили в open source, с тех пор разработка идёт на гитхабе.
Как можно видеть на диаграмме выше - в нём очень много компонентов. Но бояться не стоит - большую часть из них можно не использовать, либо запускать в рамках одного процесса - single binary mode.
Так как Cortex изначально разрабатывался как SaaS решение - в нём поддерживается end-to-end multi-tenancy.
Хранение метрик
На данный момент в Cortex есть два движка. Оба они хранят данные в объектном хранилище, среди которых поддерживаются:
S3
GCS
Azure
OpenStack Swift (экспериментально)
Любая примонтированная ФС (например - NFS или GlusterFS). Хранить блоки на локальной ФС смысла нет т.к. они должны быть доступны всему кластеру.
Далее я буду для краткости называть объектное хранилище просто S3.
Chunks Storage
Изначальный движок в Cortex - он хранит каждую timeseries в отдельном чанке в S3 или в NoSQL (Cassandra/Amazon DynamoDB/Google BigTable), а метаданные (индексы) хранятся в NoSQL.
Chunks Storage, думается, со временем совсем выпилят - насколько я слышал, Grafana Labs свои метрики уже мигрировали в Blocks Storage.
Blocks Storage
Новый, более простой и быстрый движок, основанный на Thanos. Который, в свою очередь, использует формат блоков самого Prometheus. С ним нет нужды в NoSQL и модуле Table Manager (но нужен другой - Store Gateway).
Thanos, в данном, случае является внешней vendored зависимостью в коде Cortex. Есть разговоры о том, чтобы объединить два проекта в один, но когда это будет неизвестно (и будет ли вообще).
Архитектура
Далее я буду рассматривать работу с Blocks Storage.
Упрощённо принцип работы следующий:
Prometheus собирает метрики с endpoint-ов и периодически отправляет их в Cortex используя Remote Write протокол. По сути это HTTP POST с телом в виде сериализованных в Protocol Buffers метрик сжатый потом Snappy. В самом Prometheus, при этом, можно поставить минимальный retention period - например 1 день или меньше- читаться из него ничего не будет.
Модуль Distributor внутри Cortex принимает, валидирует, проверяет per-tenant и глобальные лимиты и опционально шардит пришедшие метрики. Далее он передает их одному или нескольким Ingester (в зависимости от того применяется ли шардинг).
Также в рамках этого модуля работает HA Tracker (о нём ниже).
Ingester ответственен за запись метрик в долговременное хранилище и выдачу их для выполнения запросов. Изначально метрики записываются в локальную ФС в виде блоков длиной 2 часа. Затем, по истечении некоторого времени, они загружаются в S3 и удаляются с локальной ФС.
Также поддерживается репликация и zone awareness для дублирования блоков по различным availability domain (стойки, ДЦ, AWS AZ и так далее)
Store-Gateway служит для отдачи блоков из S3.
Он периодически сканирует бакет, находит там новые блоки, синхронизирует их заголовки в локальную ФС (чтобы после перезапуска не скачивать опять) и индексирует.
Querier реализует PromQL.
При получении запроса анализирует его и, если необходимо, разбивает на два - одна часть пойдёт в Store Gateway (для более старых данных), а другая - в Ingester для свежих.
По факту параллельных запросов может быть больше если запрашиваемый период большой и настроено разбиение по интервалам (об этом дальше в конфиге)
Compactor периодически просыпается и сканирует объектное хранилище на предмет блоков, которые можно склеить в более крупные. Это приводит к более эффективному хранению и быстрым запросам.
Старые блоки не удаляются сразу, а маркируются и удаляются на следующих итерациях чтобы дать время Store-Gateway обнаружить новые, которые уже сжаты.
Отказоустойчивость
Помимо репликации данных между Ingester-ами нам необходимо обеспечить отказоустойчивость самих Prometheus. В Cortex это реализовано просто и элегантно:
Два (или более) Prometheus настраиваются на сбор метрик с одних и тех же endpoint-ов
В каждом из них настраиваются специальные внешние метки, которые показывают к какой HA-группе принадлежит данный Prometheus и какой у него идентификатор внутри группы.
Например так:
external_labels:
__ha_group__: group_1
__ha_replica__: replica_2
При приёме метрик Cortex из каждой группы выбирает один Prometheus и сохраняет метрики только от него
Остальным отвечает HTTP 202 Accepted и отправляет в /dev/null всё что они прислали
Если же активный инстанс перестал присылать метрики (сработал таймаут) - Cortex переключается на приём от кого-то из оставшихся в живых.
Таким образом дедупликация на этом уровне становится не нужна. Правда остаётся вопрос момента переключения - не теряется ли там что-либо (когда старый не прислал, а от нового ещё не приняли), я этот вопрос не изучал глубоко - нужно тестировать.
Авторизация
Каждый запрос на запись метрик из Prometheus должен содержать HTTP-заголовок X-Scope-OrgId
равный идентификатору клиента (далее я буду называть их просто tenant, хорошего перевода не придумал). Метрики каждого tenant-а полностью изолированны друг от друга - они хранятся в разных директориях в S3 бакете и на локальной ФС
Таким же образом происходит и чтение метрик - в PromQL запросах тоже нужно тоже указывать этот заголовок.
При этом никакой реальной авторизации Cortex не проводит - он слепо доверяет этому заголовку. Auth Gateway есть в роадмапе, но когда он будет готов неизвестно. Даже просто добавить этот заголовок напрямую в Prometheus нельзя, только используя промежуточный HTTP прокси.
Для более гибкой интеграции Prometheus & Cortex я набросал простенький Remote Write прокси - cortex-tenant, который может вытаскивать ID клиента из меток Prometheus. Это позволяет использовать один инстанс (или HA-группу) Prometheus для отправки метрик нескольким разным клиентам. Мы используем этот функционал для разграничения данных разных команд, приложений и сред.
Авторизацию можно отключить, тогда Cortex не будет проверять наличие заголовка в запросах и будет подразумевать что он всегда равен fake
- то есть multi-tenancy будет отключен, все метрики будут падать в один котёл.
При необходимости данные одного клиента можно полностью удалить из кластера - пока это API экспериментально, но работает.
Настройка Cortex
В первую очередь хотелось бы сказать, что Cortex имеет смысл для тех, у кого действительно много метрик, либо хочется хранить централизовано.
Для всех остальных гораздо проще установить несколько HA-пар Prometheus (например на каждую команду или каждый проект) и поверх них натянуть
promxy
Так как документация имеет некоторое количество белых пятен - я хочу рассмотреть настройку простого кластера Cortex в режиме single binary - все модули у нас будут работать в рамках одного и того же процесса.
Danger Zone! Дальше много конфигов!
Зависимости
Нам понадобится ряд внешних сервисов для работы.
etcd для согласования кластера и хранения Hash Ring
Cortex также поддерживает Consul и Gossip-протокол, которому не нужно внешнее KV-хранилище. Но для HA-трекера Gossip не поддерживается из-за больших задержек при сходимости. Так что будем юзать etcd
memcached для кеширования всего и вся.
Cortex поддерживает его в нескольких своих модулях. Можно поднять несколько инстансов и указать их все в Cortex - он будет шардить по всем равномерно. В принципе, он не обязателен, но крайне рекомендован с точки зрения производительности.
Также есть DNS-based discovery через SRV-записи, если не хочется указывать вручную.
minio для реализации распределённого S3 хранилища.
Несколько странный проект, который часто в процессе разработки ломает обратную совместимость и требует полного перезапуска кластера для апгрейда. Также SemVer это для слабаков, поэтому у них версионирование по датам. Его разрабатывают, в основном, индусы - возможно в этом причина...
Но других вариантов особо нет, можно поднять Ceph с S3 шлюзом, но это еще более громоздко.
minio поддерживает Erasure Coding для отказоустойчивости, что есть хорошо.
HAProxy для связывания компонентов воедино
cortex-tenant для распределения метрик по tenant-ам
Prometheus собственно для сбора метрик
Общие вводные
Кластер мы будем строить плоский из 4 хостов - все они будут идентичны, с одинаковым набором сервисов. Это хорошо для небольших инсталляций, упрощает структуру.
3 страйпа не поддерживает minio c Erasure Coding - он нарезает от 4 до 16 дисков в один EC-набор. В реальном проекте лучше использовать 5 или какое-либо большее нечетное число чтобы не было Split Brain.
Также, если у вас много хостов, то некоторые компоненты, такие как etcd, лучше вынести отдельно. etcd основан на Raft и реплицирует данные на все ноды кластера, смысла в их большом количестве нет - это только увеличит среднюю нагрузку лишними репликами.
Все данные будем хранить в
/data
Конфиги я буду приводить для одного хоста, для остальных обычно достаточно поменять адреса и\или хостнеймы
В качестве ОС используем RHEL7, но различия с другими дистрибутивами минимальны
У нас всё это дело, конечно, раскатывается через Ansible, но плейбук довольно сильно завязан на нашу инфраструктуру. Я постараюсь потом его подчистить и выложить
Некоторые RPM пакеты я собираю вручную (etcd, HAProxy и т.п.) с помощью FPM т.к. в репозиториях древние версии.
/etc/hosts
10.0.0.1 ctx1
10.0.0.2 ctx2
10.0.0.3 ctx3
10.0.0.4 ctx4
etcd
Как и Zookeeper, с настройками по умолчанию etcd - бомба замедленного действия. Он не удаляет ненужные снапшоты и разрастается до бесконечности. Зачем так сделано - мне не понятно.
Поэтому настроим его соответственно:
/etc/etcd/etcd.conf
ETCD_NAME="ctx1"
ETCD_LOGGER="zap"
ETCD_LOG_LEVEL="warn"
ETCD_DATA_DIR="/data/etcd/ctx1.etcd"
ETCD_LISTEN_CLIENT_URLS="http://10.0.0.1:2379,http://127.0.0.1:2379"
ETCD_LISTEN_PEER_URLS="http://10.0.0.1:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://10.0.0.1:2379"
ETCD_INITIAL_CLUSTER_TOKEN="cortex"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://10.0.0.1:2380"
ETCD_AUTO_COMPACTION_RETENTION="30m"
ETCD_AUTO_COMPACTION_MODE="periodic"
ETCD_SNAPSHOT_COUNT="10000"
ETCD_MAX_SNAPSHOTS="5"
ETCD_INITIAL_CLUSTER="ctx1=http://ctx1:2380,ctx2=http://ctx2:2380,ctx3=http://ctx3:2380,ctx4=http://ctx4:2380"
memcached
Тут всё просто, главное выделить нужное количество памяти. Она зависит от количества метрик, которые мы будем хранить. Чем больше уникальных комбинаций меток - тем больше нужно кеша.
/etc/sysconfig/memcached
PORT="11211"
USER="memcached"
MAXCONN="512"
CACHESIZE="2048"
OPTIONS="--lock-memory --threads=8 --max-item-size=64m"
Minio
Тут минимум настроек.
По сути мы просто перечисляем хосты, которые будут использоваться для хранения данных (+ путь до директории где данные хранить - /data/minio
) и указываем ключи S3. В моем случае это были ВМ с одним диском, если у вас их несколько - то формат URL несколько меняется.
По умолчанию используется странное распределение дисков под данные и под коды Рида-Соломона: половина сырого объема уходит под redundancy. Так как у нас всего 4 хоста - это не особо важно. Но на большем по размеру кластере лучше использовать Storage Classes для снижения доли Parity-дисков.
/etc/minio/minio.env
MINIO_ACCESS_KEY="foo"
MINIO_SECRET_KEY="bar"
MINIO_PROMETHEUS_AUTH_TYPE="public"
LISTEN="0.0.0.0:9000"
ARGS="http://ctx{1...4}/data/minio"
Также нужно будет создать бакет с помощью minio-client - в нашем случае пусть называется cortex
HAProxy
Он у нас будет служить для равномерного распределения нагрузки по кластеру и отказоустойчивости. Все сервисы обращаются к локальному HAProxy, который в свою очередь проксирует запросы куда нужно.
Таким образом мы имеем что-то вроде Full Mesh топологии и отказ или перезапуск любого из сервисов или хостов целиком не влияет на функциональность кластера.
На больших кластерах (сотни-тысячи хостов) такая схема может быть узким местом, но если вы работаете с такими, то и сами это знаете :)
/etc/haproxy/haproxy.cfg
global
daemon
maxconn 10000
log 127.0.0.1 local2
chroot /var/empty
defaults
mode http
http-reuse safe
hash-type map-based sdbm avalanche
balance roundrobin
retries 3
retry-on all-retryable-errors
timeout connect 2s
timeout client 300s
timeout server 300s
timeout http-request 300s
option splice-auto
option dontlog-normal
option dontlognull
option forwardfor
option http-ignore-probes
option http-keep-alive
option redispatch 1
option srvtcpka
option tcp-smart-accept
option tcp-smart-connect
option allbackups
listen stats
bind 0.0.0.0:6666
http-request use-service prometheus-exporter if { path /metrics }
stats enable
stats refresh 30s
stats show-node
stats uri /
frontend fe_cortex
bind 0.0.0.0:8090 tfo
default_backend be_cortex
frontend fe_cortex_tenant
bind 0.0.0.0:8009 tfo
default_backend be_cortex_tenant
frontend fe_minio
bind 0.0.0.0:9001 tfo
default_backend be_minio
backend be_cortex
option httpchk GET /ready
http-check expect rstring ^ready
server ctx1 10.0.0.1:9009 check observe layer7 inter 5s
server ctx2 10.0.0.2:9009 check observe layer7 inter 5s
server ctx3 10.0.0.3:9009 check observe layer7 inter 5s
server ctx4 10.0.0.4:9009 check observe layer7 inter 5s
backend be_cortex_tenant
option httpchk GET /alive
http-check expect status 200
server ctx1 10.0.0.1:8008 check observe layer7 inter 5s
server ctx2 10.0.0.2:8008 check observe layer7 inter 5s backup
server ctx3 10.0.0.3:8008 check observe layer7 inter 5s backup
server ctx4 10.0.0.4:8008 check observe layer7 inter 5s backup
backend be_minio
balance leastconn
option httpchk GET /minio/health/live
http-check expect status 200
server ctx1 10.0.0.1:9000 check observe layer7 inter 5s
server ctx2 10.0.0.2:9000 check observe layer7 inter 5s backup
server ctx3 10.0.0.3:9000 check observe layer7 inter 5s backup
server ctx4 10.0.0.4:9000 check observe layer7 inter 5s backup
cortex-tenant
Это просто прокси между Prometheus и Cortex. Главное - выбрать уникальное имя метки для хранения там tenant ID. В нашем случае это ctx_tenant
/etc/cortex-tenant.yml
listen: 0.0.0.0:8008
target: http://127.0.0.1:8090/api/v1/push
log_level: warn
timeout: 10s
timeout_shutdown: 10s
tenant:
label: ctx_tenant
label_remove: true
header: X-Scope-OrgID
Prometheus
В случае 4 хостов Prometheus-ы можно разбить их на две HA-пары, каждую со своим ID группы и раскидать job-ы по ним.
host1 /etc/prometheus/prometheus.yml
global:
scrape_interval: 60s
scrape_timeout: 5s
external_labels:
__ha_group__: group_1
__ha_replica__: replica_1
remote_write:
- name: cortex_tenant
url: http://127.0.0.1:8080/push
scrape_configs:
- job_name: job1
scrape_interval: 60s
static_configs:
- targets:
- ctx1:9090
labels:
ctx_tenant: foobar
- job_name: job2
scrape_interval: 60s
static_configs:
- targets:
- ctx2:9090
labels:
ctx_tenant: deadbeef
host2 /etc/prometheus/prometheus.yml
global:
scrape_interval: 60s
scrape_timeout: 5s
external_labels:
__ha_group__: group_1
__ha_replica__: replica_2
remote_write:
- name: cortex_tenant
url: http://127.0.0.1:8080/push
scrape_configs:
- job_name: job1
scrape_interval: 60s
static_configs:
- targets:
- ctx1:9090
labels:
ctx_tenant: foobar
- job_name: job2
scrape_interval: 60s
static_configs:
- targets:
- ctx2:9090
labels:
ctx_tenant: deadbeef
По сути конфигурации в пределах одной HA-группы должны отличаться только лейблом реплики.
Cortex
Ну и последнее. Так как мы будем запускать все модули вместе - конфиг получится довольно объемный. Поэтому разделим его на части, чтобы читабельнее было.
Многие модули, такие как Distributor, Ingester, Compactor, Ruler кластеризуются с помощью Hash-Ring в etcd. На весь кластер выделяется некоторое количество токенов, которые распределяются между всеми участниками кольца равномерно.
Упрощенно - при приходе, допустим, новой метрики её метки хешируются, результат делится по модулю на количество токенов и в итоге направляется на хост, который владеет данным диапазоном токенов. Если он не отвечает - то отправляют следующему по кольцу хосту.
Если хост выходит из кластера (помер, перезагружается и т.п.), то его диапазон перераспределяется по остальным.
Все настройки у нас будут лежать в /etc/cortex/cortex.yml
Также т.к. Cortex сделан для работы в контейнерах - всё можно настроить через командную строку чтобы не пропихивать конфиг в контейнер.
Глобальные настройки
# Список модулей для загрузки
target: all,compactor,ruler,alertmanager
# Требовать ли заголовок X-Scope-OrgId
auth_enabled: true
# Порты
server:
http_listen_port: 9009
grpc_listen_port: 9095
limits:
# Разрешаем HA-трекинг
accept_ha_samples: true
# Названия меток, которые мы используем в Prometheus для
# маркировки групп и реплик
ha_cluster_label: __ha_group__
ha_replica_label: __ha_replica__
# Максимальный период в прошлое на который мы можем делать
# PromQL запросы (1 год).
# Всё что больше будет обрезано до этого периода.
# Это нужно для реализации retention period.
# Для фактического удаления старых блоков нужно еще настроить lifecycle
# правило в бакете S3 на пару дней глубже
max_query_lookback: 8760h
Так как Cortex создавался для работы в распределённом режиме в облаке, то работа всех модулей в одном бинарнике считается нетипичной, но я никаких проблем не наблюдал.
Другой трудностью изначально было то, что Cortex не поддерживал гибкий список модулей, которые нужно активировать. Была возможность либо указать all
, который на самом деле ни разу не all:
# cortex -modules
alertmanager
all
compactor
configs
distributor *
flusher
ingester *
purger *
querier *
query-frontend *
query-scheduler
ruler *
store-gateway *
table-manager *
Modules marked with * are included in target All.
Либо указать строго один модуль.
Поэтому пришлось сделать пулл-реквест чтобы добавить возможность загружать список любых модулей. В данном случае мы используем all
+ compactor
, ruler
и alertmanager
Хранилище
storage:
# Выбираем хранилище Blocks Storage
engine: blocks
# Конфигурируем его
blocks_storage:
# Тип бэкенда
backend: s3
# Параметры доступа к S3
s3:
endpoint: 127.0.0.1:9001
bucket_name: cortex
access_key_id: foo
secret_access_key: bar
# TLS у нас нет
insecure: true
tsdb:
# Где хранить локальные блоки до загрузки в S3
dir: /data/cortex/tsdb
# Через какое время их удалять
retention_period: 12h
# Сжимать Write-Ahead Log
wal_compression_enabled: true
bucket_store:
# Где хранить индексы блоков, найденных в S3
# По сути это должно быть в модуле Store-Gateway,
# но по какой-то причине тут
sync_dir: /data/cortex/tsdb-sync
# Как часто сканировать S3 в поиске новых блоков
sync_interval: 1m
# Настраиваем различные кеши на наши memcached
# Каждый кеш имеет свой префикс ключей, так что пересекаться они не будут
index_cache:
backend: memcached
memcached:
addresses: ctx1:11211,ctx2:11211,ctx3:11211,ctx4:11211
chunks_cache:
backend: memcached
memcached:
addresses: ctx1:11211,ctx2:11211,ctx3:11211,ctx4:11211
metadata_cache:
backend: memcached
memcached:
addresses: ctx1:11211,ctx2:11211,ctx3:11211,ctx4:11211
Distributor
distributor:
ha_tracker:
# Включить HA-трекер для Prometheus
enable_ha_tracker: true
# Таймаут после которого срабатывает failover на другую реплику Prometheus.
# Нужно настроить так чтобы метрики приходили не реже этого интервала,
# иначе будут ложные срабатывания.
ha_tracker_failover_timeout: 30s
# Настраиваем etcd для HA-трекера
kvstore:
store: etcd
etcd:
endpoints:
- http://ctx1:2379
- http://ctx2:2379
- http://ctx3:2379
- http://ctx4:2379
# Настраиваем etcd для Hash-Ring дистрибьютеров
ring:
kvstore:
store: etcd
etcd:
endpoints:
- http://ctx1:2379
- http://ctx2:2379
- http://ctx3:2379
- http://ctx4:2379
Ingester
ingester:
lifecycler:
address: 10.0.0.1
# Название зоны доступности
availability_zone: dc1
# Немного ждём чтобы всё устаканилось перед перераспределением
# токенов на себя
join_after: 10s
# Храним токены чтобы не генерировать их каждый раз при запуске
tokens_file_path: /data/cortex/ingester_tokens
ring:
# На сколько Ingester-ов реплицировать метрики.
# Если указана зона доступности, то реплики будут выбираться из разных зон
replication_factor: 2
# etcd для Hash-Ring Ingester-ов
kvstore:
store: etcd
etcd:
endpoints:
- http://ctx1:2379
- http://ctx2:2379
- http://ctx3:2379
- http://ctx4:2379
Querier
По поводу подбора правильных величин лучше почитать документацию.
Основная идея в том, чтобы никогда не запрашивать те блоки, которые еще не обработал Compactor:
querier:
# Временные файлы
active_query_tracker_dir: /data/cortex/query-tracker
# Запросы с глубиной больше этой будут направляться в S3
query_store_after: 6h
# Запросы с глубиной меньше этой отправляются в Ingester-ы
query_ingesters_within: 6h5m
frontend_worker:
frontend_address: 127.0.0.1:9095
query_range:
# Запросы будут разбиваться на куски такой длины и выполняться параллельно
split_queries_by_interval: 24h
# Выравнивать интервал запроса по его шагу
align_queries_with_step: true
# Включить кеширование результатов
cache_results: true
# Кешируем в memcached
results_cache:
# Сжимаем
compression: snappy
cache:
# TTL кеша
default_validity: 60s
memcached:
expiration: 60s
memcached_client:
addresses: ctx1:11211,ctx2:11211,ctx3:11211,ctx4:11211
Store-Gateway
Этот модуль подгружает из S3 бакета заголовки блоков (комбинации меток, временные интервалы и т.п.).
Если включить шардинг и репликацию, то участники кластера распределят все блоки между собой равномерно и каждый блок будет на 2 или более хостах.
store_gateway:
# Включаем шардинг
sharding_enabled: true
sharding_ring:
# Включаем zone awareness
zone_awareness_enabled: true
# Идентификатор зоны
instance_availability_zone: dc1
# Сколько реплик держать
replication_factor: 2
# Hash-ring для Store-Gateway
kvstore:
store: etcd
etcd:
endpoints:
- http://ctx1:2379
- http://ctx2:2379
- http://ctx3:2379
- http://ctx4:2379
Compactor
Этот модуль работает сам по себе и с остальными никак не взаимодействует. Можно активировать шардинг, тогда все компакторы в кластере распределят между собой tenant-ов и будут обрабатывать их параллельно.
compactor:
# Временная директория для блоков.
# Должно быть достаточно много места чтобы можно было загрузить блоки,
# скомпактить их и сохранить результат.
data_dir: /data/cortex/compactor
# Как часто запускать компакцию
compaction_interval: 30m
# Hash-Ring для компакторов
sharding_enabled: true
sharding_ring:
kvstore:
store: etcd
etcd:
endpoints:
- http://ctx1:2379
- http://ctx2:2379
- http://ctx3:2379
- http://ctx4:2379
Ruler + AlertManager
Эти модули опциональны и нужны только если хочется генерировать алерты на основе правил.
Правила в стандартном Prometheus формате мы будем складывать в
/data/cortex/rules/<tenant>/rulesN.yml
на каждом хосте. Можно использовать для этого S3 или другие хранилища - см. документациюCortex периодически сканирует хранилище и перезагружает правила
Конфиги AlertManager в стандартном формате складываем в
/data/cortex/alert-rules/<tenant>.yml
Аналогично можно складывать в S3 и т.п.
Cortex запускает инстанс AlertManager (внутри своего процесса) отдельно для каждого tenant, если находит конфигурацию в хранилище
ruler:
# Временные файлы
rule_path: /data/cortex/rules-tmp
# Включаем шардинг
enable_sharding: true
# Какому AlertManager-у сообщать об алертах
alertmanager_url: http://ctx1:9009/alertmanager
# Откуда загружать правила
storage:
type: local
local:
directory: /data/cortex/rules
# Hash-ring для Ruler-ов
ring:
kvstore:
store: etcd
etcd:
endpoints:
- http://ctx1:2379
- http://ctx2:2379
- http://ctx3:2379
- http://ctx4:2379
alertmanager:
# Где хранить состояние алертов
data_dir: /data/cortex/alert-data
# Внешний URL нашего инстанса (нужен для генерации ссылок и т.п.)
external_url: http://ctx1:9009/alertmanager
# Кластеринг - какой адрес слушать и какой анонсировать пирам
cluster_bind_address: 0.0.0.0:9094
cluster_advertise_address: 10.0.0.1:9094
# Список пиров
peers:
- ctx2:9094
- ctx3:9094
- ctx4:9094
# Откуда загружать настройки
storage:
type: local
local:
path: /data/cortex/alert-rules
Заключение
Вот и всё, можно запускать все сервисы - сначала зависимости, потом Cortex, затем - Prometheus.
Я не претендую на полноту, но этого должно быть достаточно чтобы начать работать.
Нужно учитывать, что Cortex активно развивается и на момент написания статьи часть параметров в master-ветке и документации (которая генерируется из неё) уже объявлено deprecated. Так что, вполне возможно, в новых версиях нужно будет конфиги немного исправлять.
Если есть вопросы и\или замечания - пишите, постараюсь добавить в статью.
chemtech
Спасибо за пост. Смотрели ли вы Victoriametrics?
blind_oracle Автор
Да, глядел когда рассматривал варианты, но архитектура мне почему-то на тот момент не очень понравилась. Вроде идея хорошая, автор взял Кликхаус MergeTree за основу, но выглядит как изобретение велосипеда немного.
Плюс, он пытается уместить в одном продукте Graphite/Prometheus/InfluxDB/OpenTSDB и еще кучу всего. Учитывая что модель данных у тех же Graphite и Prometheus несколько разная — мне кажется что это не очень хорошая идея.
valyala
Спасибо за интересную статью! Видно, что Cortex не очень прост в установке, раз на это выделена половина статьи :)
Можете более развернуто рассказать, почему вы считаете, что VictoriaMetrics выглядит как изобретение велосипеда?
Если VictoriaMetrics поддерживает загрузку данных по разным протоколам, то это не значит, что нужно использовать одновременно все эти протоколы. Если вы собираете метрики только с помощью Prometheus, то просто записывайте их в VictoriaMetrics по remote_write протоколу таким же образом, как делаете это при записи метрик в Cortex. Кстати, Prometheus позволяет записывать метрики одновременно в несколько удаленных хранилищ. Поэтому можете попробовать VictoriaMetrics параллельно с Cortex на одних и тех же данных, чтобы затем можно было сравнить их по таким параметрам, как:
Еще можете почитать https://victoriametrics.github.io/FAQ.html#what-is-the-difference-between-victoriametrics-and-cortex
blind_oracle Автор
Привет.
Я, если честно, плотно ваш продукт не изучал, каюсь — пробежался поверхностно с год назад, когда выбирал решение, по гитхабу. Просто создалось такое ощущение в процессе изучения. Ну и умозрительно поддержка нескольких разных форматов, которые местами сильно разные, показалась переусложнением.
Кстати, я сейчас храню относительно много (~2млн в минуту, всего в истории триллион или около того) Graphite метрик напрямую в Кликхаусе. Будут ли какие-либо преимущества от перехода на VM в этом случае?
Попробую развернуть при случае — возможно в новых проектах пригодится.
Ну, как видно система довольно фичастая и скейлится почти бесконечно, так что, как мне кажется, это оправданно. Но, согласен, конфиг у него огромен.
...
Строго говоря никто не мешает запустить один инстанс Cortex и всё будет работать безо всяких внешних зависимостей.
Тоже немного странно, с репликацией Ingester ничего не потеряется даже если один из них сдохнет полностью невосстановимо. При просто перезагрузке он накатит WAL и поедет дальше.
Так что может пора поправить FAQ ;)
ЗЫ:
Спасибо за fasthttp — вот его я юзаю много и плотно :)
valyala
2 миллиона метрик в минуту — это 33 тысячи метрик в секунду. Single-node VictoriaMetrics должна переварить такую нагрузку на самом слабом компе с одним ядром CPU. Single-node VictoriaMetrics успешно используют для production нагрузок по записи в более миллион метрик в секунду — см. https://victoriametrics.github.io/CaseStudies.html, в базе при этом хранится более 10 триллионов точек.
Скорее всего, будут следующие преимущества:
Количество фич и масштабирование не должно усложнять настройку и сопровождение сервиса. Например, у VictoriaMetrics тоже много разных фич, и она отлично масштабируется как вертикально (при переходе на более мощное железо), так и горизонтально — есть production инсталляции кластерной версии VictoriaMetrics с сотней vmstorage инстансов. При этом VM намного проще в настройке и сопровождении по сравнению с Cortex.
Да, только, насколько я помню, single-node Cortex не предназначен для использования в production, в отличие от VictoriaMetrics.
Если Cortex использует реализацию WAL из Prometheus, то это может приводить к различным проблемам вроде OOM crash loop, data loss и too long data recovery. См. https://valyala.medium.com/wal-usage-looks-broken-in-modern-time-series-databases-b62a627ab704
Согласен — Cortex не стоит на месте и постоянно развивается )