Graphite based monitoring на основе clickhouse

    Есть 2 подхода для съема метрик - pull (яркий представитель - prometheus) и push (яркий представитель - graphite).  Graphite - это мониторинг, состоящий из целой пачки различных микросервисов, некоторые уже почили в бозе, некоторые еще живы. Я по приходу в компанию, где сейчас работаю, застал такую расстановку сил: brubeck (для агрегации метрик), go-carbon ( занимается тем, что принимает метрики от всех и отдаёт их carbonapi), carbonapi (демон отдаёт метрики клиентам (в частности grafana)).

Несколько слов чем оказалась привлекательна новая схема и чем не устраивала старая (на основе go-carbon):

  1.  go-carbon - много пишет на диск, на текущих нагрузках мощностей SATA-дисков не хватило => перешли на SSD, которые дороже и имеют меньшие объемы
  2.  у нас go-carbon резервирует глубину хранения метрик до 10 лет, каждый wsp-файл занимает 512Кб => вечный недостаток места на SSD (wsp файлов порядка 1млн)
  3.  использование стека, основанного  на clickhouse, позволяет использовать недорогие и объемные SATA-диски + плюс сами возможности БД (репликация и т.п.)
  4. graphite-clickhouse с новой версией carbonapi позволяет использовать tag (labels в терминологии prometheus)
  5. хотелось прикрутить систему алертинга moira (хотя по-честному, go-carbon этому не мешал, т.к. достаточно просто поместить его за carbon-c-relay)

 

За основу взяли эту статью - https://habr.com/ru/company/avito/blog/343928/ (статья от 2017 года, но мы у себя к подобной схеме перебрались только сейчас). У нас  прием метрик на порядок меньше (200тыс/мин, т.е. чуть более 3-х тыс/сек), поэтому наша схема проще и скорей всего может подойти большинству.

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

 

 1) Collectd, brubeck, скрипты пушат свои метрики в роутер метрик - carbon-c-relay (https://github.com/grobian/carbon-c-relay). Он принимает на себя весь поток метрик и далее перенаправляет на сервера бэкендов метрик.

Что дает  его использование в нашем случае - возможность фильровать метрики на самом входе, безболезненное добавление новых приемников метрик.

2) moira - сервис алертинга https://github.com/moira-alert/moira. Принимает на себя весь тот же поток метрик, что и другие бэкенды, но сразу фильтрует (т.е. принимает только те метрики, которые прописаны в триггерах).

Что она дает - можно оперативно добавлять свои алерты, используя встроенные функции графита; новый хост, который отправляет свои метрики, на которые в свою очередь "навешан" триггер в moira, сразу начинает мониториться.

3) стек приема метрик, включающий их запись и чтение из clickhouse:

  • carbon-clickhouse https://github.com/lomik/carbon-clickhouse: получает метрики, буферизует и с некоторой периодичностью отправляет их в БД
  • clickhouse database
  • graphite-clickhouse https://github.com/lomik/graphite-clickhouse: бэкенд для чтения метрик из clickhouse. В grafana для подключения нового datasource использует carbonapi, который в свою очередь обращается к graphite-clickhouse)

 

Конфиги сервисов:

1. Конфиг carbon-c-relay.conf:

cluster graphite
    fnv1a_ch replication 3
        go-carbon-server:3003
        carbon-clickhouse-server:3003
        moira-server:3003
    ;

match *
    send to graphite
    ;

listen type linemode
    2003 proto udp
    2003 proto tcp
    ;

statistics
    submit every 60 seconds
    prefix with stats.server.carbon_c_relay
    send to
        graphite
    ;

 

 

2. Конфиг carbon-clickhouse.conf:

[common]
metric-prefix = "stats.{host}.carbon_clickhouse"
metric-endpoint = "local"
max-cpu = 1
metric-interval = "1m0s"

[data]
path = "/data/carbon-clickhouse/"
compression-level = 0
chunk-interval = "30s"
chunk-auto-interval = ""
compression = "none"

[upload.graphite]
type = "points"
table = "graphite.graphite"
zero-timestamp = false
threads = 1
url = "http://user:PASSW@server:8123/?max_query_size=268435456&max_ast_elements=1000000"
timeout = "1m0s"

[upload.graphite_index]
type = "index"
table = "graphite.graphite_index"
threads = 1
url = "http://user:PASSW@server:8123/?max_query_size=268435456&max_ast_elements=1000000"
timeout = "1m0s"
cache-ttl = "12h0m0s"

[upload.graphite_tagged]
type = "tagged"
table = "graphite.graphite_tagged"
threads = 1
url = "http://user:PASSW@server:8123/?max_query_size=268435456&max_ast_elements=1000000"
timeout = "1m0s"
cache-ttl = "12h0m0s"

[udp]
listen = ":2003"
enabled = true
log-incomplete = false
drop-future = "0s"
drop-past = "0s"

[tcp]
listen = ":2003"
enabled = true
drop-future = "0s"
drop-past = "0s"

[[logging]]
logger = ""
file = "/var/log/carbon-clickhouse.log"
level = "info"
encoding = "mixed"
encoding-time = "iso8601"
encoding-duration = "seconds"

[convert_to_tagged]
enabled = false
separator = ""

 Единственно что крутили - увеличили chunk-interval с 1 до 30 сек (при 1-ой сек clickhouse  через некоторое время начал писать о том, что

DB::Exception: Too many parts (300). Merges are processing significantly slower than inserts.

),  max_query_size и max_ast_elements.

graphite.graphite, graphite.graphite_index, graphite.graphite_tagged - метрики пишутся в БД graphite и соответствующие таблицы (иначе будут попадать в дефолтную БД - default)

 

3. Конфиг graphite-clickhouse.conf:

[common]
listen = ":9090"
max-cpu = 1
max-metrics-in-find-answer = 0
[clickhouse]
url = "http://user:PASSW@clickhouse-server:8123/?max_query_size=268435456&max_ast_elements=1000000"
extra-prefix = ""
data-table = "graphite.graphite"
data-timeout = "1m0s"
index-table = "graphite.graphite_index"
index-use-daily = true
index-timeout = "1m"
tagged-table = "graphite.graphite_tagged"
date-tree-table = ""
date-tree-table-version = 0
tree-timeout = "1m0s"
[[logging]]
logger = ""
file = "/var/log/graphite-clickhouse/graphite-clickhouse.log"
level = "info"
encoding = "mixed"
encoding-time = "iso8601"
encoding-duration = "seconds"

 

4. Факультативно привожу схему таблиц clickhouse (опции были подсказаны местными программистами, активно использующими clickhouse, ничего не мешает взять схему, которая указана на гитхабе)

CREATE TABLE graphite.graphite (`Path` String CODEC(ZSTD(16)), `Value` Float64 CODEC(Gorilla, ZSTD(16)), `Time` UInt32 CODEC(DoubleDelta), `Date` Date CODEC(DoubleDelta, LZ4HC(9)), `Timestamp` UInt32 CODEC(DoubleDelta)) ENGINE = ReplicatedMergeTree('/clickhouse/tables/graphite.graphite', '{hostname}') PARTITION BY toStartOfWeek(Date) PRIMARY KEY (Path, round(Time, -5)) ORDER BY (Path, round(Time, -5), Time) SETTINGS index_granularity = 8192, enable_mixed_granularity_parts = 1 

CREATE TABLE graphite.graphite_index (`Date` Date, `Level` UInt32, `Path` String, `Version` UInt32) ENGINE = ReplicatedMergeTree('/clickhouse/tables/graphite.graphite_index', '{hostname}') PARTITION BY toYYYYMM(Date) ORDER BY (Level, Path, Date) SETTINGS index_granularity = 8192, enable_mixed_granularity_parts = 1 

CREATE TABLE graphite.graphite_tagged (`Date` Date, `Tag1` String, `Path` String, `Tags` Array(String), `Version` UInt32) ENGINE = ReplicatedMergeTree('/clickhouse/tables/graphite.graphite_tagged', '{hostname}') PARTITION BY toYYYYMM(Date) ORDER BY (Tag1, Path, Date) SETTINGS index_granularity = 8192

 В основном стремились задействовать кодек, который будет максимально сжимать столбцы + нужна была репликация

 Добавлю сюда также то, как мне популярным образом объяснили как настраивается репликация в Clickhouse:

1) создаешь в базе Х на сервере Y таблицу Z (реплицируемую) - у тебя данные пишутся только туда, никто ничего никуда не реплицирует
2) создаешь в базе AAA на сервере Y таблицу Z1 с таким же путем в zookeper - теперь на сервере Y две одинаковые таблицы с разными именами, но одинаковыми данными
3) создаешь в базе BBB на сервере C таблицу Z2 с таким же путем в zookeper - теперь все три таблицы между собой реплицируются, можно писать и читать в любую и с любой
Соответственно для репликации надо иметь 2 таблицы с одинаковой структурой (названия не важны), у которых будет 1 путь в zookeeper и он сам сразу все отреплицирует и синхронизирует

 

 

5. Конфиг carbonapi.yaml

cat /etc/carbonapi/carbonapi.yaml 
cache:
  type: 'null'
concurency: 20
cpus: 1
graphite:
  host: carbon-c-relay:2003
  interval: 60s
  prefix: stats.carbonapi-cerver.carbonapi
idleConnections: 10
listen: 0.0.0.0:7081
logger:
- encoding: console
  file: /home/carbonapi/logs/carbonapi.log
  level: info
  logger: ''
maxBatchSize: 100
pidFile: ''
sendGlobsAsIs: true
tz: ''
zipper: http://127.0.0.1:9090

 

 

Кратко о том как можно начать пользоваться тегами:

 https://graphite.readthedocs.io/en/latest/tags.html

  •  отправляем метрики с тегами:
    echo "host444.df.var.df.complex.free;disk=var;type=free `shuf -i1-10 -n1` `date +%s`" | nc -C -t  -q 0 -N SERVER 2003
    echo "host333.df.root.df.complex.free;disk=root;type=free `shuf -i1-10 -n1` `date +%s`" | nc -C -t  -q 0 -N SERVER 2003
    echo "host4.load.longterm;env=prod;location=russia `shuf -i1-10 -n1` `date +%s`" | nc -C -t  -q 0 -N SERVER 2003
    В самой базе clickhouse соответствующая таблица будет иметь примерно следующее содержимое:
    SELECT *
    FROM graphite_tagged
    
    ┌───────Date─┬─Tag1─────────────────────────────────────┬─Path────────────────────────────────────────────────┬─Tags─────────────────────────────────────────────────────────────────┬────Version─┐
    │ 2019-11-19 │ __name__=host123.df.root.df.complex.used │ host123.df.root.df.complex.used?disk=root           │ ['__name__=host123.df.root.df.complex.used','disk=root']             │ 1574141308 │
    │ 2019-11-19 │ __name__=host123.df.root.df.complex.used │ host123.df.root.df.complex.used?disk=root&type=used │ ['__name__=host123.df.root.df.complex.used','type=used','disk=root'] │ 1574140068 │
    │ 2019-11-19 │ __name__=host123.df.root.df.complex.used │ host123.df.root.df.complex.used?disk=root&type=used │ ['__name__=host123.df.root.df.complex.used','disk=root','type=used'] │ 1574140888 │
    │ 2019-11-19 │ disk=root                                │ host123.df.root.df.complex.used?disk=root           │ ['__name__=host123.df.root.df.complex.used','disk=root']             │ 1574141308 │
    │ 2019-11-19 │ disk=root                                │ host123.df.root.df.complex.used?disk=root&type=used │ ['__name__=host123.df.root.df.complex.used','type=used','disk=root'] │ 1574140068 │
    │ 2019-11-19 │ disk=root                                │ host123.df.root.df.complex.used?disk=root&type=used │ ['__name__=host123.df.root.df.complex.used','disk=root','type=used'] │ 1574140888 │
    │ 2019-11-19 │ type=used                                │ host123.df.root.df.complex.used?disk=root&type=used │ ['__name__=host123.df.root.df.complex.used','type=used','disk=root'] │ 1574140068 │
    │ 2019-11-19 │ type=used                                │ host123.df.root.df.complex.used?disk=root&type=used │ ['__name__=host123.df.root.df.complex.used','disk=root','type=used'] │ 1574140888 │
    └────────────┴──────────────────────────────────────────┴─────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────┴────────────┘
    ┌───────Date─┬─Tag1──────────────────────────────────────┬─Path─────────────────────────────────────────────────┬─Tags──────────────────────────────────────────────────────────────────┬────Version─┐
    │ 2018-04-24 │ __name__=host5555.df.root.df.complex.used │ host5555.df.root.df.complex.used?disk=root&type=used │ ['__name__=host5555.df.root.df.complex.used','disk=root','type=used'] │ 1570174122 │
    │ 2018-04-24 │ disk=root                                 │ host5555.df.root.df.complex.used?disk=root&type=used │ ['__name__=host5555.df.root.df.complex.used','disk=root','type=used'] │ 1570174122 │
    │ 2018-04-24 │ type=used                                 │ host5555.df.root.df.complex.used?disk=root&type=used │ ['__name__=host5555.df.root.df.complex.used','disk=root','type=used'] │ 1570174122 │
    └────────────┴───────────────────────────────────────────┴──────────────────────────────────────────────────────┴───────────────────────────────────────────────────────────────────────┴────────────┘
    

     

  • можно теги и их значения посмотреть в графане - https://grafana.com/docs/features/datasources/graphite/#templating
    Например, добавить переменные, где в качестве Query может быть следующее:
    Query: tags()
    Query: tag_values(disk)

     

  •  Чтобы воспользоваться тегами при создании графиков, пишем примерно так:

    в Series сразу указываем

    seriesByTag('location=russia')

     

 В чем может быть удобство использования тегов? Нет необходимости помнить/вводить длинную серию в grafana, достаточно указать tag и она сама все отобразит на графике.

 Теги конечно здесь выглядят, как по мне, по-инопланетянски. В этом плане prometheus удобней - там использование labels является базовой вещью, которую проектировали изначально.

 

Please publish modules in offcanvas position.