Merge pull request 'Пол мониторинга: источник ClickHouse в Grafana и экспортёр Kafka' (#118) from feat/114-monitoring-floor into main
Reviewed-on: #118
This commit was merged in pull request #118.
This commit is contained in:
commit
2ccb8ff72c
14 files changed
+326
-47
No files matched your search
@@ -9,6 +9,7 @@ CLICKHOUSE_DEFAULT_PASSWORD=default-26-local
|
||||
CLICKHOUSE_ETL_PASSWORD=etl-26-writer
|
||||
CLICKHOUSE_BI_PASSWORD=bi-26-reader
|
||||
CLICKHOUSE_ANALYST_PASSWORD=analyst-26-reader
|
||||
CLICKHOUSE_GRAFANA_PASSWORD=grafana-26-reader
|
||||
KAFKA_EXTERNAL_PORT=29092
|
||||
PROMETHEUS_PORT=29090
|
||||
GRAFANA_PORT=23000
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
[«Боевой реализм стенда (v2)»](docs/specs/2026-07-30-stand-v2-realism.md).
|
||||
Сейчас работают кластер ClickHouse из двух шардов, отдельный
|
||||
clickhouse-keeper, односерверная Kafka в режиме KRaft, Airflow 3.3, Superset,
|
||||
Prometheus, Grafana и общая база Postgres для метаданных. Генератор
|
||||
кликстрима в [`generator/`](generator/) собран целиком со стороны клиента: у
|
||||
него есть контракт схемы события, из которого собрано
|
||||
[описание выгрузки](docs/formats/clickstream-event.md), модельный мир и
|
||||
Prometheus, Grafana, экспортёр метрик Kafka и общая база Postgres для
|
||||
метаданных. Генератор кликстрима в [`generator/`](generator/) собран
|
||||
целиком со стороны клиента: у него есть контракт схемы события, из которого
|
||||
собрано [описание выгрузки](docs/formats/clickstream-event.md), модельный мир и
|
||||
проигрыватель, отправляющий дни в Kafka или в файл. События доезжают до
|
||||
типизированного `ods.event`, а `make up` наполняет стенд стартовым миром —
|
||||
первой неделей модельного времени.
|
||||
@@ -63,9 +63,9 @@ make check-services
|
||||
все локальные данные стенда, а следующий `make up` создаст их с новыми
|
||||
значениями.
|
||||
|
||||
`make up` собирает локальные образы Airflow и Superset, поднимает весь стенд и
|
||||
ждёт здорового состояния долгоживущих контейнеров. В образ Airflow добавлены
|
||||
закреплённые клиенты ClickHouse и Kafka. Одноразовые `airflow-init` и
|
||||
`make up` собирает локальные образы Airflow, Superset и Grafana, поднимает весь
|
||||
стенд и ждёт здорового состояния долгоживущих контейнеров. В образ Airflow
|
||||
добавлены закреплённые клиенты ClickHouse и Kafka. Одноразовые `airflow-init` и
|
||||
`superset-init` завершаются с кодом 0. Первый обновляет схему Airflow,
|
||||
подготавливает администратора и подключение к `clickhouse-01`. Второй обновляет
|
||||
Superset, создаёт администратора и импортирует подключение к `clickhouse-02`.
|
||||
@@ -73,13 +73,13 @@ Superset, создаёт администратора и импортирует
|
||||
|
||||
`make smoke` за секунды спрашивает, собран ли стенд: зависимости машины,
|
||||
здоровье контейнеров, устройство keeper, ответ Kafka с машины через отображённый
|
||||
порт, три цели Prometheus, источник Grafana, компоненты Airflow и подготовленное
|
||||
подключение к `clickhouse-01`. В конце проверка спрашивает у Docker, не убивало
|
||||
ли ядро что-нибудь в долгоживущих контейнерах за нехватку памяти и не включалась
|
||||
ли политика перезапуска: убитый контейнер Docker поднимает сам, и проверка
|
||||
состояния об этом промолчит.
|
||||
порт, четыре цели Prometheus, два источника Grafana, компоненты Airflow и
|
||||
подготовленное подключение к `clickhouse-01`. В конце проверка спрашивает у
|
||||
Docker, не убивало ли ядро что-нибудь в долгоживущих контейнерах за нехватку
|
||||
памяти и не включалась ли политика перезапуска: убитый контейнер Docker
|
||||
поднимает сам, и проверка состояния об этом промолчит.
|
||||
|
||||
Одиннадцать проверок здоровья сразу после `make up --wait` повторяют то, чего
|
||||
Двенадцать проверок здоровья сразу после `make up --wait` повторяют то, чего
|
||||
Compose уже дождался: у каждой долгоживущей службы есть своя `healthcheck`.
|
||||
Оставлены они потому, что первый вопрос к стенду всё равно «всё ли живо», а
|
||||
ответ на него стоит меньше секунды. Устройство keeper — другое дело: он должен
|
||||
@@ -268,12 +268,13 @@ uv run --project generator python -m clickstream_generator batch \
|
||||
- `clickhouse-02` — точка подключения Superset;
|
||||
- `clickhouse-keeper` — координатор кластера;
|
||||
- `kafka` — один брокер KRaft;
|
||||
- `kafka-exporter` — экспортёр метрик Kafka для Prometheus;
|
||||
- `postgres-metadata` — один Postgres с отдельными базами и пользователями
|
||||
`airflow` и `superset`;
|
||||
- `airflow-apiserver`, `airflow-scheduler` и `airflow-dag-processor` —
|
||||
Airflow 3.3 с LocalExecutor, без triggerer;
|
||||
- `superset` — интерфейс и подготовленное подключение ClickHouse;
|
||||
- `prometheus` и `grafana` — сбор и просмотр встроенных метрик ClickHouse.
|
||||
- `prometheus` и `grafana` — сбор и просмотр метрик ClickHouse и Kafka.
|
||||
|
||||
С настройками из `.env.example` порты доступны только с локальной машины:
|
||||
|
||||
@@ -285,7 +286,7 @@ uv run --project generator python -m clickstream_generator batch \
|
||||
- Prometheus — `http://127.0.0.1:29090`;
|
||||
- Grafana — `http://127.0.0.1:23000`, пользователь `admin`, пароль `admin`.
|
||||
|
||||
В ClickHouse пять пользователей:
|
||||
В ClickHouse шесть пользователей и пять ролей:
|
||||
|
||||
- `etl` применяет DDL и подключает Airflow; роль `etl_writer` читает и пишет
|
||||
слои хранилища;
|
||||
@@ -293,6 +294,8 @@ uv run --project generator python -m clickstream_generator batch \
|
||||
слои DDS и DM;
|
||||
- `analyst` предназначен для подключения человека и читает все слои и
|
||||
справочники;
|
||||
- `grafana` с ролью `monitoring_reader` подключает Grafana и читает все слои,
|
||||
системные таблицы и данные со всех реплик кластера;
|
||||
- `dict` читает только базу `dic` и пароля не имеет: им словарь товаров ходит
|
||||
за своей подложкой;
|
||||
- `default` остаётся служебным: им ходят проверки здоровья и скрипты внутри
|
||||
@@ -302,7 +305,7 @@ uv run --project generator python -m clickstream_generator batch \
|
||||
[ADR 0007](docs/adr/0007-clickhouse-access.md).
|
||||
Пользователи и роли объявлены в `infra/clickhouse/users.d/access.xml`. Пароли и
|
||||
общий секрет нод живут в `.env` и передаются в конфигурацию через окружение;
|
||||
Superset получает пароль `bi` тем же путём через штатную функцию настройки.
|
||||
Superset получает пароль `bi`, а Grafana — пароль `grafana` тем же путём.
|
||||
Значения для локального стенда есть в `.env.example`.
|
||||
|
||||
Изменения значений ClickHouse в `.env` и файлов настройки серверов в
|
||||
@@ -344,10 +347,13 @@ docker compose up --force-recreate --wait clickhouse-01 clickhouse-02
|
||||
серверы и keeper используют один образ. Настройки Kafka 4.3.1 сверены с
|
||||
[примером односерверного KRaft](https://github.com/apache/kafka/blob/4.3.1/docker/examples/docker-compose-files/single-node/plaintext/docker-compose.yml).
|
||||
Секция метрик взята из конфигурации закреплённого образа ClickHouse и проверена
|
||||
на серверах и keeper. Подготовка источника Grafana сверена с
|
||||
[официальным описанием автоматической настройки](https://grafana.com/docs/grafana/latest/administration/provisioning/).
|
||||
Prometheus собирает только встроенные метрики двух серверов и keeper; внешних
|
||||
сборщиков, панелей и правил оповещения пока нет.
|
||||
на серверах и keeper. Подготовка источников Grafana сверена с
|
||||
[официальным описанием автоматической настройки](https://grafana.com/docs/grafana/latest/administration/provisioning/)
|
||||
и [документацией плагина ClickHouse](https://grafana.com/docs/plugins/grafana-clickhouse-datasource/latest/configure/).
|
||||
Prometheus собирает встроенные метрики двух серверов и keeper, а через
|
||||
`kafka-exporter` — состояние Kafka. Устройство зоны описано в
|
||||
[справочнике мониторинга](docs/architecture/monitoring.md); панелей и правил
|
||||
оповещения пока нет.
|
||||
|
||||
Airflow закреплён на 3.3.0. Состав обязательных процессов, LocalExecutor,
|
||||
публичный `airflow.sdk`, API здоровья и SimpleAuthManager сверены с
|
||||
|
||||
+20
-1
@@ -19,6 +19,7 @@ x-clickhouse-common: &clickhouse-common
|
||||
CLICKHOUSE_ETL_PASSWORD: ${CLICKHOUSE_ETL_PASSWORD:?Скопируйте .env.example в .env}
|
||||
CLICKHOUSE_BI_PASSWORD: ${CLICKHOUSE_BI_PASSWORD:?Скопируйте .env.example в .env}
|
||||
CLICKHOUSE_ANALYST_PASSWORD: ${CLICKHOUSE_ANALYST_PASSWORD:?Скопируйте .env.example в .env}
|
||||
CLICKHOUSE_GRAFANA_PASSWORD: ${CLICKHOUSE_GRAFANA_PASSWORD:?Скопируйте .env.example в .env}
|
||||
depends_on:
|
||||
clickhouse-keeper:
|
||||
condition: service_healthy
|
||||
@@ -213,6 +214,20 @@ services:
|
||||
retries: 20
|
||||
start_period: 30s
|
||||
|
||||
kafka-exporter:
|
||||
image: danielqsj/kafka-exporter:v1.9.0
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
kafka:
|
||||
condition: service_healthy
|
||||
command: ["--kafka.server=kafka:9092"]
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:9308/metrics >/dev/null 2>&1"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 15
|
||||
start_period: 10s
|
||||
|
||||
kafka-init:
|
||||
image: apache/kafka:4.3.1
|
||||
restart: "no"
|
||||
@@ -504,7 +519,9 @@ services:
|
||||
start_period: 10s
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:13.1.1
|
||||
image: clickstream-grafana:local
|
||||
build:
|
||||
context: ./infra/grafana
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
prometheus:
|
||||
@@ -516,8 +533,10 @@ services:
|
||||
GF_NEWS_NEWS_FEED_ENABLED: "false"
|
||||
GF_ANALYTICS_REPORTING_ENABLED: "false"
|
||||
GF_ANALYTICS_CHECK_FOR_UPDATES: "false"
|
||||
CLICKHOUSE_GRAFANA_PASSWORD: ${CLICKHOUSE_GRAFANA_PASSWORD:?Скопируйте .env.example в .env}
|
||||
volumes:
|
||||
- ./infra/grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro
|
||||
- ./infra/grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards:ro
|
||||
- grafana_data:/var/lib/grafana
|
||||
ports:
|
||||
- "127.0.0.1:${GRAFANA_PORT:?Скопируйте .env.example в .env}:3000"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# ADR 0007. Доступ к ClickHouse: пользователи файлом, доверие нод секретом
|
||||
|
||||
Дата: 9 августа 2026 года. Статус: принято.
|
||||
Дата: 9 августа 2026 года. Статус: принято; роль `monitoring_reader`
|
||||
и пользователь `grafana` добавлены 21 августа 2026 года (#114).
|
||||
|
||||
## Решение
|
||||
|
||||
У кластера пять пользователей и четыре роли.
|
||||
У кластера шесть пользователей и пять ролей.
|
||||
|
||||
- `default` — с паролем, только служебный: проверки здоровья нод и работа
|
||||
изнутри контейнеров. Приложения им не ходят.
|
||||
@@ -14,6 +15,8 @@
|
||||
Им ходит Superset.
|
||||
- `analyst` с ролью `analyst_reader` — чтение всех слоёв и справочников. Им
|
||||
человек подключается снаружи, из своего клиента.
|
||||
- `grafana` с ролью `monitoring_reader` — чтение слоёв, таблиц базы `dic` и
|
||||
системных таблиц, `CLUSTER` и `REMOTE`. Им ходит Grafana, и больше никто.
|
||||
- `dict` с ролью `dict_reader` — чтение одной базы `dic`, пароля нет. Им
|
||||
словарь читает свою подложку, и больше он никем не используется
|
||||
([ADR 0012](0012-dictionary-home.md)).
|
||||
@@ -58,6 +61,20 @@ DDS под ними, потому что так чаще всего и быва
|
||||
есть положить секрет в git. Ходить этой учёткой некуда: право у неё одно —
|
||||
`SELECT` на `dic`, и хост источника локальный.
|
||||
|
||||
**Своя роль дашбордам.** Панели мониторинга живут не на витринах, а на
|
||||
`system.query_log`, частях MergeTree и очереди распределённых DDL — и читают
|
||||
их через `clusterAllReplicas`, потому что системные таблицы у каждой ноды свои
|
||||
([ADR 0002](0002-monitoring-scope.md)). Для этого нужны `SELECT` на `system.*`
|
||||
и `REMOTE`; контракт роли также включает `CLUSTER` для запросов `ON CLUSTER`.
|
||||
Такого набора не было ни у одного читателя: у обеих читающих ролей из
|
||||
системных таблиц только `system.settings`. Догрузить
|
||||
недостающее в `analyst_reader` отвергнуто: аналитик получил бы системные
|
||||
таблицы и `REMOTE` заодно, а в `system.query_log` запросы Grafana стали бы
|
||||
неотличимы от человеческих — ровно там, где дашборд «Запросы» и должен их
|
||||
различать. Пускать Grafana под `etl` отвергнуто сразу: читатель не получает
|
||||
права писать. Осталась своя роль по здешней общей схеме — у каждого
|
||||
потребителя она своя.
|
||||
|
||||
**Секрет, а не учётные данные по репликам.** У каждой реплики в описании
|
||||
кластера своя учётка — вписанная явно или подразумеваемая, и тогда это
|
||||
беспарольный `default`. Подключение к соседней ноде идёт от неё, а не от
|
||||
@@ -141,6 +158,25 @@ DDS под ними, потому что так чаще всего и быва
|
||||
настройки ClickHouse.
|
||||
- Весь DDL репозитория идёт через `IF NOT EXISTS` (файлы `sql/ddl/`).
|
||||
|
||||
Права роли `monitoring_reader` сверены 21 августа 2026 года по документации
|
||||
ClickHouse через MCP Context7 (#114).
|
||||
|
||||
- Системные таблицы выдаются обычным `SELECT`: в документации это
|
||||
`GRANT SELECT ON system.query_log`, значит `SELECT ON system.*` и есть «все
|
||||
системные таблицы». Отдельного права на них нет.
|
||||
- `GRANT CLUSTER ON *.*` — документированная форма права на запросы
|
||||
`ON CLUSTER`; текущий запрос `clusterAllReplicas` его не использует.
|
||||
- `clusterAllReplicas` документация держит в одном ряду с `remote`,
|
||||
`remoteSecure` и `cluster`: это табличная функция, которая берёт имя
|
||||
кластера из `remote_servers` и идёт на каждую его реплику. Отсюда `REMOTE` —
|
||||
без права на удалённое обращение запрос дальше своей ноды не пойдёт, а
|
||||
панели ADR 0002 читают `system.*` именно так.
|
||||
|
||||
Набор проверен на живом стенде 21 августа 2026 года (#114): пользователь
|
||||
`grafana` из Grafana выполнил
|
||||
`SELECT count() AS replicas FROM clusterAllReplicas('clickstream_cluster', system.one)`
|
||||
и получил 2 — прав хватает, чтобы прочесть системную таблицу с обеих нод.
|
||||
|
||||
Проверено на живом стенде 13 августа 2026 года, ClickHouse 26.3.17.56.
|
||||
|
||||
- Секрет кластера принимается внутри `remote_servers` в виде
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# Мониторинг: из чего собран и что скребёт
|
||||
|
||||
Документ описывает зону мониторинга: какие сервисы в неё входят, что собирает
|
||||
Prometheus, какие источники данных подготовлены в Grafana и каким
|
||||
пользователем она ходит в ClickHouse. Зачем зоне именно такой состав и какие
|
||||
дашборды на нём задуманы — [ADR 0002](../adr/0002-monitoring-scope.md);
|
||||
устройство доступа — [ADR 0007](../adr/0007-clickhouse-access.md).
|
||||
|
||||
**Что здесь есть и чего ещё нет.** Собран пол: свой образ Grafana с плагином
|
||||
ClickHouse, два подготовленных источника данных, экспортёр метрик Kafka и цель
|
||||
для него у Prometheus. Дашбордов пока ни одного — каталог под них заведён
|
||||
пустым, панели приносят отдельные тикеты. Правил оповещения и серверного
|
||||
журнала `system.text_log` тоже пока нет.
|
||||
|
||||
## Состав
|
||||
|
||||
| Сервис | Образ | Что делает |
|
||||
|---|---|---|
|
||||
| `prometheus` | `prom/prometheus:v3.13.2` | опрашивает цели раз в 15 секунд, порт стенда `29090` |
|
||||
| `grafana` | `clickstream-grafana:local` | интерфейс мониторинга; собирается из `infra/grafana/`, порт стенда `23000` |
|
||||
| `kafka-exporter` | `danielqsj/kafka-exporter:v1.9.0` | переводит состояние брокера в метрики Prometheus, порт `9308` |
|
||||
|
||||
Плагин ClickHouse 4.20.0 входит в образ Grafana. Фоновая установка плагинов
|
||||
при запуске отключена.
|
||||
|
||||
## Что скребёт Prometheus
|
||||
|
||||
| Цель | Задание | Метка `role` | Откуда метрики |
|
||||
|---|---|---|---|
|
||||
| `clickhouse-01:9363` | `clickhouse` | `server` | встроенный экспортёр ноды |
|
||||
| `clickhouse-02:9363` | `clickhouse` | `server` | встроенный экспортёр ноды |
|
||||
| `clickhouse-keeper:9363` | `clickhouse-keeper` | `keeper` | встроенный экспортёр keeper |
|
||||
| `kafka-exporter:9308` | `kafka` | `exporter` | внешний экспортёр |
|
||||
|
||||
Ровно эти четыре цели пересчитывает `check_prometheus_targets` в
|
||||
`scripts/stand-smoke.sh` — и по значению `up`, и по списку адресов. Пятая цель
|
||||
красит смоук; это и есть способ заметить, что состав пола поменяли молча.
|
||||
|
||||
## Источники данных Grafana
|
||||
|
||||
Оба источника подготовлены файлами в `infra/grafana/provisioning/datasources/`
|
||||
и объявлены `editable: false`: править их через интерфейс нельзя, источник
|
||||
истины — репозиторий.
|
||||
|
||||
- `prometheus` — основной по умолчанию, ходит на `http://prometheus:9090`.
|
||||
- `clickhouse` — плагин `grafana-clickhouse-datasource`, нативный протокол,
|
||||
`clickhouse-01:9000`, пользователь `grafana`, пароль из `.env`.
|
||||
|
||||
Источник ClickHouse смотрит на `clickhouse-01`. Системные таблицы обеих нод
|
||||
панели читают через `clusterAllReplicas`, как принято в ADR 0002.
|
||||
|
||||
## Доступ к ClickHouse
|
||||
|
||||
Grafana ходит пользователем `grafana` с ролью `monitoring_reader`: читает слои,
|
||||
таблицы базы `dic` и системные таблицы. `REMOTE` разрешает
|
||||
`clusterAllReplicas`, `CLUSTER` — запросы `ON CLUSTER`. Решение о своей роли
|
||||
записано в [ADR 0007](../adr/0007-clickhouse-access.md). Пароль задаётся
|
||||
переменной `CLICKHOUSE_GRAFANA_PASSWORD`.
|
||||
|
||||
## Куда кладутся дашборды
|
||||
|
||||
Каталог `infra/grafana/provisioning/dashboards/` смонтирован в контейнер и
|
||||
объявлен провайдером в `provider.yml`: Grafana подхватывает оттуда файлы
|
||||
дашбордов, удалять и править их через интерфейс запрещено. Сейчас в каталоге
|
||||
только сам файл провайдера. Тикет с панелями кладёт туда JSON и в
|
||||
`compose.yaml` уже не заходит — ради этого каталог и заведён заранее.
|
||||
|
||||
## Что проверено
|
||||
|
||||
Права роли `monitoring_reader` сверены 21 августа 2026 года по документации
|
||||
ClickHouse через MCP Context7; разбор — в
|
||||
[ADR 0007](../adr/0007-clickhouse-access.md), раздел «Что проверено».
|
||||
|
||||
Сам состав проверен на живом стенде 21 августа 2026 года, собранном заново
|
||||
после `make clean`.
|
||||
|
||||
- Grafana видит плагин `grafana-clickhouse-datasource` 4.20.0
|
||||
(`/api/plugins?type=datasource`), оба подготовленных источника здоровы.
|
||||
- Запрос источника ClickHouse к `system.one` через `clusterAllReplicas` вернул
|
||||
2 — с одной ноды видны обе.
|
||||
- Prometheus видит ровно четыре цели, все со значением `up = 1`; запрос
|
||||
`kafka_consumergroup_lag` вернул ряды по группам `clickstream_*`.
|
||||
- `make smoke` 21/21, `make check-clickhouse` 10/10, `make check-services` 7/7.
|
||||
@@ -33,18 +33,18 @@
|
||||
генератора: он отдельный пакет со своим `pyproject.toml`, локом и образом, и
|
||||
спрашивают его отдельно.
|
||||
|
||||
Стенд нужен трём целям из восьми. Цена — замер, см. «Что проверено»:
|
||||
`make test`, `make smoke`, `make check-clickhouse` и `make check-services`
|
||||
перемерены 7 августа 2026 года на приёмке этапа 2, корневой `make lint` —
|
||||
9 августа при его появлении; остальные стоят с замера 6 августа.
|
||||
Стенд нужен трём целям из восьми. Цена — замер, см. «Что проверено».
|
||||
`make smoke` и `make check-services` перемерены 21 августа 2026 года,
|
||||
`make check-clickhouse` — 19 августа, корневой `make lint` — 9 августа,
|
||||
`make test` — 7 августа; остальные стоят с замера 6 августа.
|
||||
|
||||
| Цель | Откуда | Что утверждает | Стенд | Цена |
|
||||
|---|---|---|---|---|
|
||||
| `make config-test` | корень | Compose разбирается, файлы DAG синтаксически целы, в diff нет пробельных ошибок. О работоспособности не говорит ничего | не нужен | 1 с |
|
||||
| `make lint` | корень | Код стенда — `dags/` и `infra/superset/` — отформатирован и проходит ruff | не нужен | 0,4 с |
|
||||
| `make smoke` | корень | Стенд **собран**: службы живы, порты отвечают, подключения настроены друг на друга. Вширь и по касательной к каждой службе. Единственная цель, которая здесь правда смоук | нужен | 9 с |
|
||||
| `make smoke` | корень | Стенд **собран**: службы живы, порты отвечают, подключения настроены друг на друга. Вширь и по касательной к каждой службе. Единственная цель, которая здесь правда смоук | нужен | 17 с |
|
||||
| `make check-clickhouse` | корень | Всё, что спрашивают **у ClickHouse** и он отвечает сам: макросы, шарды, реплики, путь в keeper, ключ шардирования, очередь распределённых DDL, ответ словаря товаров, счёт событий стартового мира против описи | нужен | 24 с |
|
||||
| `make check-services` | корень | **Службы работают**: DAG запускается и доходит, топик создаётся и удаляется, Superset логинится и ходит в базу | нужен | 59 с |
|
||||
| `make check-services` | корень | **Службы работают**: DAG запускается и доходит, топик создаётся и удаляется, Superset логинится и ходит в базу | нужен | 93 с |
|
||||
| `make lint` | `generator/` | Код генератора отформатирован и проходит ruff | не нужен | 0,4 с |
|
||||
| `make typecheck` | `generator/` | Типы генератора сходятся (ty) | не нужен | 0,5 с |
|
||||
| `make test` | `generator/` | Генератор делает то, что обещает; схема события остаётся объявленным контрактом, а собранные из кода [описание выгрузки](../formats/clickstream-event.md) и [опись мира](../../data/world-inventory.json) — свежими | не нужен | 71 с |
|
||||
@@ -209,7 +209,7 @@ Kafka → STG → ODS, и живёт он в `make check-clickhouse`.
|
||||
контейнер Docker поднимает сам, и проверка здоровья об этом промолчит
|
||||
(ADR 0004). Стенд нагружает `check-services`, а увидеть последствия нужно и
|
||||
тому, кто гонял один смоук. Это единственная проверка, которая считается
|
||||
дважды: 20 у смоука плюс 7 у `check-services` — это 26 разных проверок.
|
||||
дважды: 21 у смоука плюс 7 у `check-services` — это 27 разных проверок.
|
||||
- **Зависимости машины считает только смоук.** «На машине есть Docker, curl и
|
||||
jq» — вопрос к машине, а не к службам, и на оси он стоит рядом с «стенд
|
||||
собран». Для `check-services` это условие запуска: без них он не начнёт
|
||||
@@ -221,6 +221,12 @@ Kafka → STG → ODS, и живёт он в `make check-clickhouse`.
|
||||
|
||||
## Что проверено
|
||||
|
||||
**Перезамер 21 августа 2026 года при исполнении #114.** После добавления
|
||||
`kafka-exporter` и источника ClickHouse смоук прошёл 21/21 за 16–17 секунд,
|
||||
`check-services` — 7/7 за 93 секунды. При остановленном экспортёре смоук дал
|
||||
19/2: назвал нездоровый сервис и отсутствие одной из четырёх целей. После
|
||||
возврата сервиса прогон снова дал 21/21.
|
||||
|
||||
**Перезамер 20 августа 2026 года при исполнении #96.** `make up` после
|
||||
`make clean` прошёл за 8 м 18 с, повтор на живом стенде — за 3 м 35 с. Служба
|
||||
семи слепков заняла около 28 секунд между концом `world-init` и своим концом.
|
||||
|
||||
@@ -44,6 +44,18 @@
|
||||
<query>GRANT SELECT ON system.settings</query>
|
||||
</grants>
|
||||
</analyst_reader>
|
||||
<monitoring_reader>
|
||||
<grants>
|
||||
<query>GRANT SELECT ON stg.*</query>
|
||||
<query>GRANT SELECT ON ods.*</query>
|
||||
<query>GRANT SELECT ON dds.*</query>
|
||||
<query>GRANT SELECT ON dm.*</query>
|
||||
<query>GRANT SELECT ON dic.*</query>
|
||||
<query>GRANT SELECT ON system.*</query>
|
||||
<query>GRANT CLUSTER ON *.*</query>
|
||||
<query>GRANT REMOTE ON *.*</query>
|
||||
</grants>
|
||||
</monitoring_reader>
|
||||
<!-- Учётка словаря: пароля нет намеренно, см. ADR 0007. -->
|
||||
<dict_reader>
|
||||
<grants>
|
||||
@@ -77,6 +89,13 @@
|
||||
<query>GRANT analyst_reader</query>
|
||||
</grants>
|
||||
</analyst>
|
||||
<grafana>
|
||||
<password from_env="CLICKHOUSE_GRAFANA_PASSWORD"/>
|
||||
<quota>default</quota>
|
||||
<grants>
|
||||
<query>GRANT monitoring_reader</query>
|
||||
</grants>
|
||||
</grafana>
|
||||
<dict>
|
||||
<no_password/>
|
||||
<quota>default</quota>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
FROM grafana/grafana:13.1.1
|
||||
|
||||
# Именованный том закрывает /var/lib/grafana, поэтому плагин остаётся в образе.
|
||||
# Фоновая установка выключена: нужная версия уже закреплена, а каталог образа
|
||||
# доступен процессу Grafana только для чтения.
|
||||
USER root
|
||||
ENV GF_PATHS_PLUGINS=/usr/share/grafana/plugins \
|
||||
GF_PLUGINS_PREINSTALL_DISABLED=true
|
||||
RUN grafana cli --pluginsDir "$GF_PATHS_PLUGINS" \
|
||||
plugins install grafana-clickhouse-datasource 4.20.0
|
||||
USER grafana
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: clickstream
|
||||
orgId: 1
|
||||
folder: ""
|
||||
type: file
|
||||
disableDeletion: true
|
||||
editable: false
|
||||
options:
|
||||
path: /etc/grafana/provisioning/dashboards
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: ClickHouse
|
||||
uid: clickhouse
|
||||
type: grafana-clickhouse-datasource
|
||||
access: proxy
|
||||
isDefault: false
|
||||
editable: false
|
||||
jsonData:
|
||||
host: clickhouse-01
|
||||
port: 9000
|
||||
protocol: native
|
||||
username: grafana
|
||||
defaultDatabase: system
|
||||
secureJsonData:
|
||||
password: ${CLICKHOUSE_GRAFANA_PASSWORD}
|
||||
@@ -16,3 +16,9 @@ scrape_configs:
|
||||
- clickhouse-keeper:9363
|
||||
labels:
|
||||
role: keeper
|
||||
- job_name: kafka
|
||||
static_configs:
|
||||
- targets:
|
||||
- kafka-exporter:9308
|
||||
labels:
|
||||
role: exporter
|
||||
@@ -30,6 +30,26 @@ done
|
||||
jq -e '
|
||||
.services.superset.image == "clickstream-superset:local"
|
||||
' >/dev/null <<<"$config_json"
|
||||
jq -e '
|
||||
.services.grafana.image == "clickstream-grafana:local" and
|
||||
any(
|
||||
.services.grafana.volumes[];
|
||||
(.source | endswith("/infra/grafana/provisioning/dashboards")) and
|
||||
.target == "/etc/grafana/provisioning/dashboards" and
|
||||
.read_only == true
|
||||
)
|
||||
' >/dev/null <<<"$config_json"
|
||||
jq -e '
|
||||
.services.grafana.environment.CLICKHOUSE_GRAFANA_PASSWORD as $password |
|
||||
($password | length > 0) and
|
||||
.services["clickhouse-01"].environment.CLICKHOUSE_GRAFANA_PASSWORD == $password and
|
||||
.services["clickhouse-02"].environment.CLICKHOUSE_GRAFANA_PASSWORD == $password
|
||||
' >/dev/null <<<"$config_json"
|
||||
jq -e '
|
||||
.services["kafka-exporter"].image == "danielqsj/kafka-exporter:v1.9.0" and
|
||||
.services["kafka-exporter"].command == ["--kafka.server=kafka:9092"] and
|
||||
.services["kafka-exporter"].depends_on.kafka.condition == "service_healthy"
|
||||
' >/dev/null <<<"$config_json"
|
||||
jq -e '
|
||||
.services["airflow-init"].image == "clickstream-airflow:local" and
|
||||
all(
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
readonly ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
read -r -a COMPOSE_CMD <<<"${COMPOSE_BIN:-docker compose}"
|
||||
readonly -a LONG_LIVED_SERVICES=(
|
||||
clickhouse-keeper clickhouse-01 clickhouse-02 kafka postgres-metadata
|
||||
clickhouse-keeper clickhouse-01 clickhouse-02 kafka kafka-exporter postgres-metadata
|
||||
airflow-apiserver airflow-scheduler airflow-dag-processor
|
||||
superset prometheus grafana
|
||||
)
|
||||
|
||||
+60
-16
@@ -87,33 +87,37 @@ check_prometheus_targets() {
|
||||
targets_response="$(curl -sf "http://127.0.0.1:${port}/api/v1/targets?state=active" 2>/dev/null || true)"
|
||||
if jq -e '
|
||||
.status == "success" and
|
||||
(.data.result | length == 3) and
|
||||
(.data.result | length == 4) and
|
||||
([.data.result[].metric.instance] | sort ==
|
||||
["clickhouse-01:9363", "clickhouse-02:9363", "clickhouse-keeper:9363"]) and
|
||||
["clickhouse-01:9363", "clickhouse-02:9363", "clickhouse-keeper:9363", "kafka-exporter:9308"]) and
|
||||
all(.data.result[]; .value[1] == "1")
|
||||
' >/dev/null 2>&1 <<<"$response" && jq -e '
|
||||
.status == "success" and
|
||||
(.data.activeTargets | length == 3) and
|
||||
(.data.activeTargets | length == 4) and
|
||||
([.data.activeTargets[].scrapeUrl] | sort == [
|
||||
"http://clickhouse-01:9363/metrics",
|
||||
"http://clickhouse-02:9363/metrics",
|
||||
"http://clickhouse-keeper:9363/metrics"
|
||||
"http://clickhouse-keeper:9363/metrics",
|
||||
"http://kafka-exporter:9308/metrics"
|
||||
]) and
|
||||
all(.data.activeTargets[]; .health == "up" and .lastError == "")
|
||||
' >/dev/null 2>&1 <<<"$targets_response"; then
|
||||
pass 'Prometheus видит ровно три цели ClickHouse, все со значением up=1'
|
||||
pass 'Prometheus видит ровно четыре здоровые цели ClickHouse и Kafka'
|
||||
else
|
||||
fail 'Prometheus не видит ровно три здоровые цели: clickhouse-01, clickhouse-02 и clickhouse-keeper'
|
||||
fail 'Prometheus не видит ровно четыре здоровые цели: clickhouse-01, clickhouse-02, clickhouse-keeper и kafka-exporter'
|
||||
fi
|
||||
}
|
||||
|
||||
check_grafana_datasource() {
|
||||
local clickhouse_datasource
|
||||
local clickhouse_response
|
||||
local config
|
||||
local datasources
|
||||
local password
|
||||
local port
|
||||
local datasource
|
||||
local provisioning_file_ok=0
|
||||
local response
|
||||
local prometheus_datasource
|
||||
local prometheus_response
|
||||
local user
|
||||
|
||||
config="$(compose config --format json 2>/dev/null || true)"
|
||||
@@ -129,24 +133,64 @@ check_grafana_datasource() {
|
||||
grep -Eq "^[[:space:]]+url: http://prometheus:9090$" "$file"
|
||||
grep -Eq "^[[:space:]]+isDefault: true$" "$file"
|
||||
grep -Eq "^[[:space:]]+editable: false$" "$file"
|
||||
file=/etc/grafana/provisioning/datasources/clickhouse.yml
|
||||
test -r "$file"
|
||||
grep -Eq "^[[:space:]]+uid: clickhouse$" "$file"
|
||||
grep -Eq "^[[:space:]]+type: grafana-clickhouse-datasource$" "$file"
|
||||
grep -Eq "^[[:space:]]+host: clickhouse-01$" "$file"
|
||||
grep -Eq "^[[:space:]]+port: 9000$" "$file"
|
||||
grep -Eq "^[[:space:]]+username: grafana$" "$file"
|
||||
grep -Eq "^[[:space:]]+editable: false$" "$file"
|
||||
' >/dev/null 2>&1 && provisioning_file_ok=1
|
||||
datasource="$(curl -sf -u "${user}:${password}" \
|
||||
datasources="$(curl -sf -u "${user}:${password}" \
|
||||
"http://127.0.0.1:${port}/api/plugins?type=datasource" 2>/dev/null || true)"
|
||||
prometheus_datasource="$(curl -sf -u "${user}:${password}" \
|
||||
"http://127.0.0.1:${port}/api/datasources/uid/prometheus" 2>/dev/null || true)"
|
||||
response="$(curl -sf -u "${user}:${password}" \
|
||||
prometheus_response="$(curl -sf -u "${user}:${password}" \
|
||||
"http://127.0.0.1:${port}/api/datasources/uid/prometheus/health" 2>/dev/null || true)"
|
||||
if [[ "$provisioning_file_ok" -eq 1 ]] && jq -e '
|
||||
clickhouse_datasource="$(curl -sf -u "${user}:${password}" \
|
||||
"http://127.0.0.1:${port}/api/datasources/uid/clickhouse" 2>/dev/null || true)"
|
||||
clickhouse_response="$(curl -sf -u "${user}:${password}" \
|
||||
"http://127.0.0.1:${port}/api/datasources/uid/clickhouse/health" 2>/dev/null || true)"
|
||||
if [[ "$provisioning_file_ok" -ne 1 ]]; then
|
||||
fail 'Grafana не получила файлы настройки источников'
|
||||
return
|
||||
fi
|
||||
if ! jq -e '
|
||||
any(.[]; .id == "grafana-clickhouse-datasource")
|
||||
' >/dev/null 2>&1 <<<"$datasources"; then
|
||||
fail 'Grafana не видит плагин ClickHouse'
|
||||
return
|
||||
fi
|
||||
if ! jq -e '
|
||||
.uid == "prometheus" and
|
||||
.type == "prometheus" and
|
||||
.url == "http://prometheus:9090" and
|
||||
.access == "proxy" and
|
||||
.isDefault == true and
|
||||
.readOnly == true
|
||||
' >/dev/null 2>&1 <<<"$datasource" && \
|
||||
jq -e '.status == "OK"' >/dev/null 2>&1 <<<"$response"; then
|
||||
pass 'Grafana проверила файл настройки и подготовленный источник Prometheus с uid=prometheus'
|
||||
else
|
||||
fail 'Grafana не смогла проверить файл настройки и подготовленный источник Prometheus с uid=prometheus'
|
||||
' >/dev/null 2>&1 <<<"$prometheus_datasource" || \
|
||||
! jq -e '.status == "OK"' >/dev/null 2>&1 <<<"$prometheus_response"; then
|
||||
fail 'Grafana не видит здоровый подготовленный источник Prometheus'
|
||||
return
|
||||
fi
|
||||
if ! jq -e '
|
||||
.uid == "clickhouse" and
|
||||
.type == "grafana-clickhouse-datasource" and
|
||||
.access == "proxy" and
|
||||
.isDefault == false and
|
||||
.readOnly == true and
|
||||
.jsonData.host == "clickhouse-01" and
|
||||
.jsonData.port == 9000 and
|
||||
.jsonData.protocol == "native" and
|
||||
.jsonData.username == "grafana" and
|
||||
.jsonData.defaultDatabase == "system"
|
||||
' >/dev/null 2>&1 <<<"$clickhouse_datasource" || \
|
||||
! jq -e '.status == "OK"' >/dev/null 2>&1 <<<"$clickhouse_response"; then
|
||||
fail 'Grafana не видит здоровый подготовленный источник ClickHouse'
|
||||
return
|
||||
fi
|
||||
pass 'Grafana видит плагин и два здоровых подготовленных источника'
|
||||
}
|
||||
|
||||
# Три вопроса о связности, и все три Airflow отвечает сразу. Запуск пробников —
|
||||
|
||||
Reference in new issue
Block a user