В последнее время Prometheus стал де-факто стандартом для сбора и хранения метрик. Он удобен для разработчиков ПО - экспорт метрик можно реализовать в несколько строк кода. Для DevOps/SRE, в свою очередь, есть простой язык PromQL для получения метрик из хранилища и их визуализации в той же Grafana.
Но Prometheus имеет ряд недостатков, способы устранения которых я хочу рассмотреть в этой статье. Также разберём деплой Cortex - распределённого хранилища метрик.
Но есть и минусы:
Минусы подобного решения на поверхности:
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 есть в некотором зачаточном состоянии, но в эту сторону проект, судя по всему, не развивается особо.
Как можно видеть на диаграмме выше - в нём очень много компонентов. Но бояться не стоит - большую часть из них можно не использовать, либо запускать в рамках одного процесса - single binary mode.
Так как Cortex изначально разрабатывался как SaaS решение - в нём поддерживается end-to-end multi-tenancy.
Chunks Storage, думается, со временем совсем выпилят - насколько я слышал, Grafana Labs свои метрики уже мигрировали в Blocks Storage.
Thanos, в данном, случае является внешней vendored зависимостью в коде Cortex. Есть разговоры о том, чтобы объединить два проекта в один, но когда это будет неизвестно (и будет ли вообще).
Упрощённо принцип работы следующий:
__ha_group__: group_1
__ha_replica__: replica_2
Таким же образом происходит и чтение метрик - в PromQL запросах тоже нужно тоже указывать этот заголовок.
При этом никакой реальной авторизации Cortex не проводит - он слепо доверяет этому заголовку. Auth Gateway есть в роадмапе, но когда он будет готов неизвестно. Даже просто добавить этот заголовок напрямую в Prometheus нельзя, только используя промежуточный HTTP прокси.
Для более гибкой интеграции Prometheus & Cortex я набросал простенький Remote Write прокси - cortex-tenant, который может вытаскивать ID клиента из меток Prometheus. Это позволяет использовать один инстанс (или HA-группу) Prometheus для отправки метрик нескольким разным клиентам. Мы используем этот функционал для разграничения данных разных команд, приложений и сред.
Авторизацию можно отключить, тогда Cortex не будет проверять наличие заголовка в запросах и будет подразумевать что он всегда равен fake - то есть multi-tenancy будет отключен, все метрики будут падать в один котёл.
При необходимости данные одного клиента можно полностью удалить из кластера - пока это API экспериментально, но работает.
Danger Zone! Дальше много конфигов!
Поэтому настроим его соответственно:
/etc/etcd/etcd.conf
/etc/sysconfig/memcached
По сути мы просто перечисляем хосты, которые будут использоваться для хранения данных (+ путь до директории где данные хранить - /data/minio) и указываем ключи S3. В моем случае это были ВМ с одним диском, если у вас их несколько - то формат URL несколько меняется.
По умолчанию используется странное распределение дисков под данные и под коды Рида-Соломона: половина сырого объема уходит под redundancy. Так как у нас всего 4 хоста - это не особо важно. Но на большем по размеру кластере лучше использовать Storage Classes для снижения доли Parity-дисков.
/etc/minio/minio.env
Также нужно будет создать бакет с помощью minio-client - в нашем случае пусть называется cortex
Таким образом мы имеем что-то вроде Full Mesh топологии и отказ или перезапуск любого из сервисов или хостов целиком не влияет на функциональность кластера.
На больших кластерах (сотни-тысячи хостов) такая схема может быть узким местом, но если вы работаете с такими, то и сами это знаете
/etc/haproxy/haproxy.cfg
/etc/cortex-tenant.yml
host1 /etc/prometheus/prometheus.ymlhost2 /etc/prometheus/prometheus.yml
По сути конфигурации в пределах одной HA-группы должны отличаться только лейблом реплики.
Многие модули, такие как 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
# Выбираем хранилище 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
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
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
Основная идея в том, чтобы никогда не запрашивать те блоки, которые еще не обработал 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
Если включить шардинг и репликацию, то участники кластера распределят все блоки между собой равномерно и каждый блок будет на 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:
# Временная директория для блоков.
# Должно быть достаточно много места чтобы можно было загрузить блоки,
# скомпактить их и сохранить результат.
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
# Временные файлы
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 активно развивается и на момент написания статьи часть параметров в master-ветке и документации (которая генерируется из неё) уже объявлено deprecated. Так что, вполне возможно, в новых версиях нужно будет конфиги немного исправлять.
Источник статьи: https://habr.com/ru/post/541458/
Но Prometheus имеет ряд недостатков, способы устранения которых я хочу рассмотреть в этой статье. Также разберём деплой Cortex - распределённого хранилища метрик.
Недостатки
- Отсутствие отказоустойчивости
Prometheus работает только в единственном экземпляре, никакого HA. - Отсутствие распределения нагрузки
В принципе, он хорошо скейлится вверх с ростом количества ядер. Так что это проблема только для тех у кого действительно много метрик. - Нет поддержки multi-tenancy
Все метрики летят в один большой котёл и разгребать их потом используя PromQL и метки не всегда удобно. Часто хочется разделить различные приложения и\или команды по своим песочницам чтобы они друг другу не мешали.
Плюс, многие готовые дашборды для Grafana не готовы к тому что много инстансов одного приложения хранят метрики в одном и том же месте - придётся переделывать все запросы, добавлять фильтры по меткам и так далее.
Но есть и минусы:
- После того как один хост из пары упадёт/перезагрузится/whatever - у них случится рассинхронизация. В метриках будут пропуски.
- Все метрики приложения должны умещаться на один хост
- Управлять таким зоопарком будет сложнее - какие-то из Prometheus могут быть недогружены, какие-то перегружены. В случае запуска в каком-нибудь Kubernetes это не так важно.
PromQL прокси
Например promxy, который размещается перед 2 или более инстансами Prometheus и делает fan-out входящих запросов на все из них. Затем дедуплицирует полученные метрики и, таким образом, закрывает пропуски в метриках (если, конечно, они не попали на один и тот же временной интервал).Минусы подобного решения на поверхности:
- Один запрос нагружает сразу все инстансы за прокси
- Прокси решает только проблему с пропусками в метриках.
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). Хранить блоки на локальной ФС смысла нет т.к. они должны быть доступны всему кластеру.
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 и какой у него идентификатор внутри группы.
Например так:
__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 в режиме single binary - все модули у нас будут работать в рамках одного и того же процесса.В первую очередь хотелось бы сказать, что Cortex имеет смысл для тех, у кого действительно много метрик, либо хочется хранить централизовано.
Для всех остальных гораздо проще установить несколько HA-пар Prometheus (например на каждую команду или каждый проект) и поверх них натянуть promxy
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 т.к. в репозиториях древние версии.
etcd
Как и Zookeeper, с настройками по умолчанию etcd - бомба замедленного действия. Он не удаляет ненужные снапшоты и разрастается до бесконечности. Зачем так сделано - мне не понятно.Поэтому настроим его соответственно:
/etc/etcd/etcd.conf
memcached
Тут всё просто, главное выделить нужное количество памяти. Она зависит от количества метрик, которые мы будем хранить. Чем больше уникальных комбинаций меток - тем больше нужно кеша./etc/sysconfig/memcached
Minio
Тут минимум настроек.По сути мы просто перечисляем хосты, которые будут использоваться для хранения данных (+ путь до директории где данные хранить - /data/minio) и указываем ключи S3. В моем случае это были ВМ с одним диском, если у вас их несколько - то формат URL несколько меняется.
По умолчанию используется странное распределение дисков под данные и под коды Рида-Соломона: половина сырого объема уходит под redundancy. Так как у нас всего 4 хоста - это не особо важно. Но на большем по размеру кластере лучше использовать Storage Classes для снижения доли Parity-дисков.
/etc/minio/minio.env
Также нужно будет создать бакет с помощью minio-client - в нашем случае пусть называется cortex
HAProxy
Он у нас будет служить для равномерного распределения нагрузки по кластеру и отказоустойчивости. Все сервисы обращаются к локальному HAProxy, который в свою очередь проксирует запросы куда нужно.Таким образом мы имеем что-то вроде Full Mesh топологии и отказ или перезапуск любого из сервисов или хостов целиком не влияет на функциональность кластера.
На больших кластерах (сотни-тысячи хостов) такая схема может быть узким местом, но если вы работаете с такими, то и сами это знаете
/etc/haproxy/haproxy.cfg
cortex-tenant
Это просто прокси между Prometheus и Cortex. Главное - выбрать уникальное имя метки для хранения там tenant ID. В нашем случае это ctx_tenant/etc/cortex-tenant.yml
Prometheus
В случае 4 хостов Prometheus-ы можно разбить их на две HA-пары, каждую со своим ID группы и раскидать job-ы по ним.host1 /etc/prometheus/prometheus.ymlhost2 /etc/prometheus/prometheus.yml
По сути конфигурации в пределах одной 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:
# Временные файлы
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, если находит конфигурацию в хранилище
# Временные файлы
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. Так что, вполне возможно, в новых версиях нужно будет конфиги немного исправлять.
Источник статьи: https://habr.com/ru/post/541458/