Зачем: рядом с кластером 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>
35 lines
823 B
Python
35 lines
823 B
Python
"""Пример независимого DAG для проверки Airflow 3."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import datetime
|
|
|
|
from airflow.sdk import dag, task
|
|
|
|
|
|
@dag(
|
|
dag_id="example_clickstream_hello",
|
|
schedule=None,
|
|
start_date=datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc),
|
|
catchup=False,
|
|
tags=["пример"],
|
|
doc_md=__doc__,
|
|
)
|
|
def example_clickstream_hello():
|
|
@task
|
|
def extract() -> dict[str, int]:
|
|
return {"clicks": 3, "views": 10}
|
|
|
|
@task
|
|
def calculate_ctr(counters: dict[str, int]) -> float:
|
|
return round(counters["clicks"] / counters["views"], 3)
|
|
|
|
@task
|
|
def show_result(ctr: float) -> None:
|
|
print(f"CTR учебного примера: {ctr}")
|
|
|
|
show_result(calculate_ctr(extract()))
|
|
|
|
|
|
example_clickstream_hello()
|