feat(infra): стенд целиком — Kafka, Airflow 3, Superset и мониторинг одной командой

Зачем: рядом с кластером ClickHouse не хватало остальной платформы, а
поднимать её по кускам — значит каждый раз вспоминать порядок. Теперь
`make up` даёт стенд целиком, а `make smoke` честно отвечает, работает он
или нет.

Что:
- Kafka в режиме KRaft (без ZooKeeper), Postgres под метаданные, Airflow
  3.3 четырьмя сервисами и Superset 6.1 с драйверами ClickHouse;
- мониторинг: Prometheus снимает метрики с обеих нод ClickHouse и keeper,
  Grafana получает подготовленный источник данных;
- подключение Airflow ведёт на ноду 1, подключение Superset — на ноду 2:
  ловушка правильных ошибок, забытый `ON CLUSTER` виден в дашборде сам;
- `scripts/stand-smoke.sh` — сквозная проверка из 24 пунктов: топик в
  Kafka, цели Prometheus, запуск примера DAG через API Airflow, проверка
  подключения Superset и расход памяти против порога 3,4 ГБ;
- `make config-test` — статические ворота: Compose, синтаксис Bash и
  Python, стражи README; стражи smoke проверяют, что отчёт краснеет на
  сломанном стенде и зеленеет после восстановления;
- решения записаны в `docs/adr/0001-stand-services.md`, состав стенда и
  порядок работы — в README.

Проверка: на чистых томах `make clean` → `cp .env.example .env` →
`make up` (1 мин 51 с) → `make smoke` — 24 пройдено, 0 ошибок, 2171 MiB.
Перезапуск `make down` → `make up` → `make smoke` — 24/0. Также зелены
`make config-test` (3/0 и 5/0), `make smoke-cluster` (8/8) и
`make smoke-guards` (3/0 и 3/0).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 08:46:04 +03:00
co-authored by Claude Opus 5
parent dca0142fd1
commit 63c42bc068
24 changed files with 1493 additions and 31 deletions
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
readonly ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
readonly README="$ROOT_DIR/README.md"
grep -Eq 'нода 1.*28123.*29000' "$README"
grep -Eq 'нода 2.*28124.*29001' "$README"
printf 'ЗЕЛЁНО: README перечисляет HTTP- и нативные порты обеих нод.\n'
grep -Eq 'После первого запуска.*`make clean`' "$README"
printf 'ЗЕЛЁНО: README объясняет сброс томов после смены исходных учётных данных.\n'
grep -Fxq -- '- нода 2 — `http://127.0.0.1:28124`, нативный порт `29001`;' "$README"
printf 'ЗЕЛЁНО: список портов остаётся единым списком.\n'
awk '
previous == "После изменения `infra/clickhouse/config.d/prometheus.xml` выполните" &&
$0 == "`docker compose restart clickhouse-01 clickhouse-02`: обычный `make up` не" {
found = 1
}
{previous = $0}
END {exit !found}
' "$README"
printf 'ЗЕЛЁНО: README требует перезапуск ClickHouse после изменения настройки метрик.\n'
grep_status=0
grep -q '3,4 GB' "$ROOT_DIR/scripts/stand-smoke.sh" || grep_status=$?
if [[ "$grep_status" -eq 0 ]]; then
printf 'ОШИБКА: отчёт проверки использует латинское обозначение GB.\n' >&2
exit 1
elif [[ "$grep_status" -ne 1 ]]; then
printf 'ОШИБКА: не удалось проверить обозначение единицы памяти.\n' >&2
exit 1
fi
printf 'ЗЕЛЁНО: отчёт проверки использует русское обозначение ГБ.\n'
printf 'ИТОГ: пройдено 5, ошибок 0\n'
+1 -1
View File
@@ -61,7 +61,7 @@ check_failure_guards() {
failure_log="$(mktemp)"
started_at="$(date +%s)"
setsid make --no-print-directory -C "$ROOT_DIR" smoke >"$failure_log" 2>&1 &
setsid make --no-print-directory -C "$ROOT_DIR" smoke-cluster >"$failure_log" 2>&1 &
failure_pid=$!
if wait_for_local_table; then
compose kill clickhouse-02 >/dev/null
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
set -uo pipefail
readonly ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
read -r -a COMPOSE_CMD <<<"${COMPOSE_BIN:-docker compose}"
readonly LOG_FILE="$(mktemp)"
restored=0
passed=0
compose() {
"${COMPOSE_CMD[@]}" --project-directory "$ROOT_DIR" "$@"
}
restore_stand() {
make --no-print-directory -C "$ROOT_DIR" COMPOSE="${COMPOSE_BIN:-docker compose}" up >/dev/null
}
on_exit() {
local status=$?
trap - EXIT INT TERM
rm -f "$LOG_FILE"
if [[ "$restored" -eq 0 ]] && ! restore_stand; then
printf 'ОШИБКА: не удалось восстановить стенд после проверки.\n' >&2
status=1
fi
exit "$status"
}
trap on_exit EXIT
trap 'exit 130' INT TERM
if make --no-print-directory -C "$ROOT_DIR" COMPOSE="${COMPOSE_BIN:-docker compose}" smoke >"$LOG_FILE" 2>&1; then
passed=$((passed + 1))
printf 'ЗЕЛЁНО: перед проверкой отказа make smoke проходит полностью.\n'
else
printf 'ОШИБКА: исходный стенд не проходит make smoke; проверка отказа недостоверна.\n' >&2
exit 1
fi
compose stop prometheus >/dev/null
make --no-print-directory -C "$ROOT_DIR" COMPOSE="${COMPOSE_BIN:-docker compose}" smoke >"$LOG_FILE" 2>&1
status=$?
if [[ "$status" -ne 0 ]] && grep -q 'ОШИБКА: сервис prometheus' "$LOG_FILE"; then
passed=$((passed + 1))
printf 'ЗЕЛЁНО: остановленный prometheus делает make smoke красным и назван в отчёте.\n'
else
printf 'ОШИБКА: make smoke не обнаружил остановленный prometheus; код=%s.\n' "$status" >&2
exit 1
fi
if ! restore_stand; then
printf 'ОШИБКА: не удалось восстановить стенд после проверки.\n' >&2
exit 1
fi
restored=1
if make --no-print-directory -C "$ROOT_DIR" COMPOSE="${COMPOSE_BIN:-docker compose}" smoke >"$LOG_FILE" 2>&1; then
passed=$((passed + 1))
printf 'ЗЕЛЁНО: после восстановления make smoke снова проходит полностью.\n'
else
printf 'ОШИБКА: после восстановления стенд не проходит make smoke.\n' >&2
exit 1
fi
rm -f "$LOG_FILE"
trap - EXIT INT TERM
printf 'ИТОГ: пройдено %d, ошибок 0\n' "$passed"
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
set -euo pipefail
readonly ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
readonly FIXTURE_DIR="$(mktemp -d)"
cleanup() {
rm -rf "$FIXTURE_DIR"
}
trap cleanup EXIT
mkdir -p "$FIXTURE_DIR/bin" "$FIXTURE_DIR/scripts"
cp "$ROOT_DIR/scripts/stand-smoke.sh" "$FIXTURE_DIR/scripts/stand-smoke.sh"
cp "$(type -P false)" "$FIXTURE_DIR/bin/docker"
set +e
output="$(PATH="$FIXTURE_DIR/bin:$PATH" COMPOSE_BIN=false \
"$FIXTURE_DIR/scripts/stand-smoke.sh" 2>&1)"
status=$?
set -e
error_message_status=0
grep -q 'ОШИБКА: compose.yaml или .env.example недоступны для чтения' \
<<<"$output" || error_message_status=$?
green_message_status=0
grep -q 'ЗЕЛЁНО: .env.example совпадает' <<<"$output" || green_message_status=$?
if [[ "$status" -ne 0 ]] && \
[[ "$error_message_status" -eq 0 ]] && \
[[ "$green_message_status" -eq 1 ]]; then
printf 'ЗЕЛЁНО: недоступные compose.yaml и .env.example не проходят статическую проверку.\n'
else
printf 'ОШИБКА: статическая проверка приняла недоступные файлы.\n' >&2
exit 1
fi
cp "$ROOT_DIR/compose.yaml" "$ROOT_DIR/.env.example" "$FIXTURE_DIR/"
set +e
timeout 3s env LC_ALL=ru_RU.UTF-8 PATH="$FIXTURE_DIR/bin:$PATH" COMPOSE_BIN=false \
"$FIXTURE_DIR/scripts/stand-smoke.sh" >/dev/null 2>&1
status=$?
set -e
if [[ "$status" -ne 124 ]]; then
printf 'ЗЕЛЁНО: статическая проверка укладывается в три секунды при русской локали.\n'
else
printf 'ОШИБКА: статическая проверка превысила три секунды при русской локали.\n' >&2
exit 1
fi
grep -Eq '^ # .*подстановки Compose.*\$\$' "$ROOT_DIR/scripts/stand-smoke.sh"
printf 'ЗЕЛЁНО: сложный разбор подстановок Compose пояснён рядом с кодом.\n'
printf 'ИТОГ: пройдено 3, ошибок 0\n'