Files
clickstream-data-platform/compose.yaml
T
ddadminandClaude Opus 5 292302e161 fix(airflow): правки пульта мира по двум линиям ревью
Зачем: линия дефектов нашла опору на дефект провайдера, линия постановки —
незаписанный ответ на вопрос ADR 0009 и переменную образца, чьё значение на
чужой машине неверно, а узнаёт об этом читатель через двести строк.

Что: `auto_remove` у прогона генератора переведён с `success` на `force`.
Значение `success` тоже убирало контейнер в любом исходе, но случайно —
удаление у провайдера 4.5.7 стоит в `finally` вопреки собственной
документации; `force` то же поведение называет прямо и переживёт починку.
У выключателя назван `catchup=False`: умолчание Airflow 3 то же самое, но
у дага с тиком в 25 минут и `start_date` в январе это первый вопрос
читателя. «Быстрый старт» предупреждает про `DOCKER_GID` — единственное
значение образца, неверное вне этой машины. В образе Airflow записано, что
провайдер docker приходит с базой (4.5.7 к 3.3.0), — это ответ на вопрос,
который ADR 0009 оставил тикету. Список томов планировщика получил ту же
пометку «правя одно, правьте второе», что стоит у числа дней. Проход на
вычитание срезал три комментария, пересказывавших ADR.

Проверка: `make lint`, `make config-test`, `make smoke` (20/0),
`make check-clickhouse` (9/9) — зелёные. Живой пульт на чистом стенде:
снятый с паузы `world_live` сыграл два дня подряд без нажатия (24 м 07 с и
24 м 06 с, пауза между ними 55 секунд), дочерний прогон виден ссылкой из
задачи выключателя; пауза остановила мир на границе суток — тик прошёл,
третьего прогона нет. Контейнер после прогона с `force` не остался.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 13:57:59 +03:00

505 lines
22 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: ${COMPOSE_PROJECT_NAME:?Скопируйте .env.example в .env}
x-clickhouse-nofile: &clickhouse-nofile
nofile:
soft: 262144
hard: 262144
x-clickhouse-common: &clickhouse-common
image: clickhouse/clickhouse-server:26.3.17.56
restart: unless-stopped
environment:
# Пользователей объявляет access.xml; настройка учётки образом не нужна.
CLICKHOUSE_SKIP_USER_SETUP: "1"
CLICKHOUSE_CLUSTER_SECRET: ${CLICKHOUSE_CLUSTER_SECRET:?Скопируйте .env.example в .env}
CLICKHOUSE_DEFAULT_PASSWORD: ${CLICKHOUSE_DEFAULT_PASSWORD:?Скопируйте .env.example в .env}
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}
depends_on:
clickhouse-keeper:
condition: service_healthy
ulimits: *clickhouse-nofile
# Гигабайта не хватало: половину съедали страницы самого бинарника (ADR 0004).
mem_limit: 4g
healthcheck:
test: ["CMD-SHELL", "clickhouse-client --host 127.0.0.1 --user default --password \"$$CLICKHOUSE_DEFAULT_PASSWORD\" --query 'SELECT 1' >/dev/null 2>&1"]
interval: 5s
timeout: 3s
retries: 30
start_period: 10s
# Факты стенда, у которых стало по два потребителя: разовая служба генератора и
# пульт мира. Названы по одному разу — иначе однажды разойдутся, и заметит это
# не проверка, а менти с пустым топиком.
x-generator-image: &generator-image clickstream-generator:local
x-kafka-target: &kafka-target
KAFKA_BOOTSTRAP_SERVERS: kafka:9092
KAFKA_TOPIC: hits
x-world-starting-days: &world-starting-days "8"
x-airflow-common: &airflow-common
image: clickstream-airflow:local
build:
context: ./infra/airflow
environment:
AIRFLOW__CORE__EXECUTOR: LocalExecutor
AIRFLOW__CORE__PARALLELISM: "4"
AIRFLOW__CORE__LOAD_EXAMPLES: "false"
AIRFLOW__CORE__AUTH_MANAGER: airflow.api_fastapi.auth.managers.simple.simple_auth_manager.SimpleAuthManager
AIRFLOW__CORE__SIMPLE_AUTH_MANAGER_USERS: "${AIRFLOW_ADMIN_USER:?Скопируйте .env.example в .env}:admin"
AIRFLOW__CORE__SIMPLE_AUTH_MANAGER_PASSWORDS_FILE: /opt/airflow/auth/simple_auth_manager_passwords.json
AIRFLOW__CORE__EXECUTION_API_SERVER_URL: http://airflow-apiserver:8080/execution/
AIRFLOW__CORE__FERNET_KEY: ${AIRFLOW_FERNET_KEY:?Скопируйте .env.example в .env}
AIRFLOW__API_AUTH__JWT_SECRET: ${AIRFLOW_JWT_SECRET:?Скопируйте .env.example в .env}
AIRFLOW__API_AUTH__JWT_ISSUER: airflow
AIRFLOW__API__SECRET_KEY: ${AIRFLOW_API_SECRET_KEY:?Скопируйте .env.example в .env}
AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: "postgresql+psycopg2://${AIRFLOW_METADATA_USER:?Скопируйте .env.example в .env}:${AIRFLOW_METADATA_PASSWORD:?Скопируйте .env.example в .env}@postgres-metadata:5432/airflow"
AIRFLOW__SCHEDULER__ENABLE_HEALTH_CHECK: "true"
AIRFLOW_ADMIN_USER: ${AIRFLOW_ADMIN_USER:?Скопируйте .env.example в .env}
AIRFLOW_ADMIN_PASSWORD: ${AIRFLOW_ADMIN_PASSWORD:?Скопируйте .env.example в .env}
CLICKHOUSE_ETL_PASSWORD: ${CLICKHOUSE_ETL_PASSWORD:?Скопируйте .env.example в .env}
# Пульт мира поднимает генератор сам, отдельным контейнером, — значит те же
# факты стенда, что compose даёт разовой службе генератора, нужны и дагам.
# Имя сети собирается из имени проекта: у второй копии стенда оно другое.
<<: *kafka-target
GENERATOR_IMAGE: *generator-image
STAND_NETWORK: ${COMPOSE_PROJECT_NAME}_default
WORLD_STARTING_DAYS: *world-starting-days
volumes:
- ./dags:/opt/airflow/dags:ro
- ./infra/airflow/init.sh:/opt/airflow/init.sh:ro
- airflow_logs:/opt/airflow/logs
- airflow_auth:/opt/airflow/auth
x-generator-common: &generator-common
image: *generator-image
build:
context: .
dockerfile: generator/Dockerfile
restart: "no"
depends_on:
kafka-init:
condition: service_completed_successfully
# Читать топик движок Kafka начинает не при своём создании, а когда над ним
# появляется матвью приёма, и она создаётся последней по номеру файла
# (sql/ddl/40-stg-views.sql). Отправь генератор события раньше — они лягут в
# топик и молча минуют хранилище.
clickhouse-init:
condition: service_completed_successfully
environment:
# Адрес брокера и имя топика — факты стенда, и называет их стенд.
# Остальное (день, зерно, число дней, предел пачки) приходит аргументами
# от того, кто запускает: у службы нет позиции на оси мира.
<<: *kafka-target
x-superset-common: &superset-common
image: clickstream-superset:local
build:
context: ./infra/superset
environment:
SUPERSET_CONFIG_PATH: /app/pythonpath/superset_config.py
SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY:?Скопируйте .env.example в .env}
SUPERSET__SQLALCHEMY_DATABASE_URI: "postgresql+psycopg2://${SUPERSET_METADATA_USER:?Скопируйте .env.example в .env}:${SUPERSET_METADATA_PASSWORD:?Скопируйте .env.example в .env}@postgres-metadata:5432/superset"
SUPERSET_ADMIN_USER: ${SUPERSET_ADMIN_USER:?Скопируйте .env.example в .env}
SUPERSET_ADMIN_PASSWORD: ${SUPERSET_ADMIN_PASSWORD:?Скопируйте .env.example в .env}
CLICKHOUSE_BI_PASSWORD: ${CLICKHOUSE_BI_PASSWORD:?Скопируйте .env.example в .env}
volumes:
- ./infra/superset/superset_config.py:/app/pythonpath/superset_config.py:ro
- ./infra/superset/init.sh:/app/infra/init.sh:ro
- ./infra/superset/import:/app/import:ro
- superset_home:/app/superset_home
services:
clickhouse-keeper:
image: clickhouse/clickhouse-server:26.3.17.56
restart: unless-stopped
user: clickhouse
ulimits: *clickhouse-nofile
entrypoint:
- clickhouse
- keeper
- --config-file=/etc/clickhouse-keeper/keeper_config.xml
volumes:
- ./infra/clickhouse/keeper/keeper_config.xml:/etc/clickhouse-keeper/keeper_config.xml:ro
- clickhouse_keeper_data:/var/lib/clickhouse
mem_limit: 512m
healthcheck:
test: ["CMD-SHELL", "clickhouse keeper-client --host 127.0.0.1 --port 9181 --query ruok | grep -q imok"]
interval: 5s
timeout: 3s
retries: 30
start_period: 10s
# Инициатор DDL и точка подключения Airflow.
clickhouse-01:
<<: *clickhouse-common
hostname: clickhouse-01
ports:
- "127.0.0.1:${CLICKHOUSE_01_HTTP_PORT:?Скопируйте .env.example в .env}:8123"
- "127.0.0.1:${CLICKHOUSE_01_TCP_PORT:?Скопируйте .env.example в .env}:9000"
volumes:
- ./infra/clickhouse/config.d/cluster.xml:/etc/clickhouse-server/config.d/cluster.xml:ro
- ./infra/clickhouse/config.d/prometheus.xml:/etc/clickhouse-server/config.d/prometheus.xml:ro
- ./infra/clickhouse/config.d/macros-01.xml:/etc/clickhouse-server/config.d/macros.xml:ro
- ./infra/clickhouse/users.d/access.xml:/etc/clickhouse-server/users.d/access.xml:ro
- clickhouse_01_data:/var/lib/clickhouse
# Точка подключения Superset.
clickhouse-02:
<<: *clickhouse-common
hostname: clickhouse-02
ports:
- "127.0.0.1:${CLICKHOUSE_02_HTTP_PORT:?Скопируйте .env.example в .env}:8123"
- "127.0.0.1:${CLICKHOUSE_02_TCP_PORT:?Скопируйте .env.example в .env}:9000"
volumes:
- ./infra/clickhouse/config.d/cluster.xml:/etc/clickhouse-server/config.d/cluster.xml:ro
- ./infra/clickhouse/config.d/prometheus.xml:/etc/clickhouse-server/config.d/prometheus.xml:ro
- ./infra/clickhouse/config.d/macros-02.xml:/etc/clickhouse-server/config.d/macros.xml:ro
- ./infra/clickhouse/users.d/access.xml:/etc/clickhouse-server/users.d/access.xml:ro
- clickhouse_02_data:/var/lib/clickhouse
kafka:
image: apache/kafka:4.3.1
hostname: kafka
restart: unless-stopped
ports:
- "127.0.0.1:${KAFKA_EXTERNAL_PORT:?Скопируйте .env.example в .env}:29092"
environment:
KAFKA_NODE_ID: "1"
KAFKA_PROCESS_ROLES: "broker,controller"
KAFKA_CONTROLLER_QUORUM_VOTERS: "1@kafka:9093"
KAFKA_LISTENERS: "INTERNAL://:9092,CONTROLLER://:9093,EXTERNAL://:29092"
KAFKA_ADVERTISED_LISTENERS: "INTERNAL://kafka:9092,EXTERNAL://localhost:${KAFKA_EXTERNAL_PORT:?Скопируйте .env.example в .env}"
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: "INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT"
KAFKA_INTER_BROKER_LISTENER_NAME: "INTERNAL"
KAFKA_CONTROLLER_LISTENER_NAMES: "CONTROLLER"
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: "1"
KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: "1"
KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: "1"
KAFKA_SHARE_COORDINATOR_STATE_TOPIC_REPLICATION_FACTOR: "1"
KAFKA_SHARE_COORDINATOR_STATE_TOPIC_MIN_ISR: "1"
KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: "0"
KAFKA_NUM_PARTITIONS: "1"
KAFKA_LOG_DIRS: "/var/lib/kafka/data"
KAFKA_HEAP_OPTS: "-Xmx512m -Xms256m"
CLUSTER_ID: "4L6g3nShT-eMCtK--X86sw"
volumes:
- kafka_data:/var/lib/kafka/data
mem_limit: 1g
healthcheck:
test: ["CMD-SHELL", "/opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --list >/dev/null 2>&1"]
interval: 15s
timeout: 10s
retries: 20
start_period: 30s
kafka-init:
image: apache/kafka:4.3.1
restart: "no"
depends_on:
kafka:
condition: service_healthy
entrypoint: ["/bin/bash", "-ec"]
command:
- |
topic_partition_count() {
/opt/kafka/bin/kafka-topics.sh \
--bootstrap-server kafka:9092 \
--describe \
--topic hits | \
sed -n '1s/.*PartitionCount: \([0-9][0-9]*\).*/\1/p'
}
/opt/kafka/bin/kafka-topics.sh \
--bootstrap-server kafka:9092 \
--create \
--if-not-exists \
--topic hits \
--partitions 2 \
--replication-factor 1
partition_count="$$(topic_partition_count)"
# Брокер разрешает автосоздание, хотя потребитель ClickHouse его не запрашивает.
# На сохранённом томе другой клиент мог уже создать hits с одним разделом.
if [[ "$$partition_count" == "1" ]]; then
/opt/kafka/bin/kafka-topics.sh \
--bootstrap-server kafka:9092 \
--alter \
--topic hits \
--partitions 2
partition_count="$$(topic_partition_count)"
fi
if [[ "$$partition_count" != "2" ]]; then
printf 'ОШИБКА: у топика hits разделов: %s, ожидалось: 2.\n' \
"$${partition_count:-неизвестно}" >&2
exit 1
fi
# DDL применяется с ноды 1 по порядку имён файлов после готовности всего кластера.
clickhouse-init:
image: clickhouse/clickhouse-server:26.3.17.56
restart: "no"
depends_on:
kafka-init:
condition: service_completed_successfully
clickhouse-01:
condition: service_healthy
clickhouse-02:
condition: service_healthy
environment:
LC_ALL: C
CLICKHOUSE_ETL_PASSWORD: ${CLICKHOUSE_ETL_PASSWORD:?Скопируйте .env.example в .env}
entrypoint: ["/bin/bash", "-ec"]
command:
- |
set -- /ddl/*.sql
if [[ ! -e "$$1" ]]; then
printf 'ОШИБКА: в /ddl нет файлов SQL.\n' >&2
exit 1
fi
for ddl_file do
printf 'Применяем %s.\n' "$${ddl_file##*/}"
clickhouse-client \
--host clickhouse-01 \
--user etl \
--password "$$CLICKHOUSE_ETL_PASSWORD" \
--multiquery \
--queries-file "$$ddl_file"
done
volumes:
- ./sql/ddl:/ddl:ro
# Стартовый мир: восемь модельных дней уезжают в топик при каждом подъёме.
# Что это за дни и каким мир обязан выйти — data/world-inventory.json.
#
# Число дней стоит здесь числом: YAML не читает Python, и одно из двух мест
# (второе — `STARTING_DAYS` в inventory.py) лишнее по построению. Правя одно,
# правьте второе — на страже тут никто не стоит: залей эта служба лишний
# день, он лёг бы за рамкой дат описи и остался бы незамеченным. Внутри YAML
# число одно на всех: то же говорит дагам пульта, где кончается стартовый мир.
#
# Повторный `make up` заливает мир заново, и это не оплошность: `WatchID` у
# событий те же, ReplacingMergeTree схлопнет повтор в ODS. Сырьё в STG при
# этом честно удвоится — свойство слоя, описанное в storage.md.
world-init:
<<: *generator-common
command: ["batch", "--day", "0", "--days", *world-starting-days]
# Тот же образ для ручных прогонов: `make generate-batch`, `make generate-live`.
# Под профилем — чтобы обычный подъём стенда её не трогал.
generator:
<<: *generator-common
profiles: ["generator"]
command: ["batch"]
postgres-metadata:
image: postgres:16-alpine
restart: unless-stopped
command:
- postgres
- -c
- shared_buffers=64MB
- -c
- max_connections=50
environment:
POSTGRES_USER: ${POSTGRES_ADMIN_USER:?Скопируйте .env.example в .env}
POSTGRES_PASSWORD: ${POSTGRES_ADMIN_PASSWORD:?Скопируйте .env.example в .env}
POSTGRES_DB: postgres
AIRFLOW_METADATA_USER: ${AIRFLOW_METADATA_USER:?Скопируйте .env.example в .env}
AIRFLOW_METADATA_PASSWORD: ${AIRFLOW_METADATA_PASSWORD:?Скопируйте .env.example в .env}
SUPERSET_METADATA_USER: ${SUPERSET_METADATA_USER:?Скопируйте .env.example в .env}
SUPERSET_METADATA_PASSWORD: ${SUPERSET_METADATA_PASSWORD:?Скопируйте .env.example в .env}
volumes:
- ./infra/postgres/init/10-metadata-databases.sh:/docker-entrypoint-initdb.d/10-metadata-databases.sh:ro
- postgres_metadata_data:/var/lib/postgresql/data
mem_limit: 256m
healthcheck:
test: ["CMD-SHELL", "pg_isready -U \"$${POSTGRES_USER}\" -d postgres >/dev/null"]
interval: 5s
timeout: 5s
retries: 20
start_period: 10s
airflow-init:
<<: *airflow-common
user: "0:0"
restart: "no"
depends_on:
postgres-metadata:
condition: service_healthy
clickhouse-01:
condition: service_healthy
# Не убирать: без зависимого успешный clickhouse-init считается
# упавшим для --wait.
clickhouse-init:
condition: service_completed_successfully
# По той же причине. Заливка стартового мира идёт последней в цепи
# разовых служб, и зависимого ей взять больше негде: долгоживущие службы
# данных не ждут, а `airflow-init` эту цепь и так замыкает.
world-init:
condition: service_completed_successfully
entrypoint: ["/bin/bash"]
command: ["/opt/airflow/init.sh"]
airflow-apiserver:
<<: *airflow-common
restart: unless-stopped
depends_on:
airflow-init:
condition: service_completed_successfully
command: api-server
ports:
- "127.0.0.1:${AIRFLOW_PORT:?Скопируйте .env.example в .env}:8080"
mem_limit: 512m
healthcheck:
test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8080/api/v2/monitor/health | python -c 'import json,sys; sys.exit(0 if json.load(sys.stdin)[\"metadatabase\"][\"status\"] == \"healthy\" else 1)'"]
interval: 10s
timeout: 5s
retries: 30
start_period: 30s
airflow-scheduler:
<<: *airflow-common
restart: unless-stopped
depends_on:
airflow-init:
condition: service_completed_successfully
command: scheduler
# Пульт мира зовёт генератор отдельным контейнером, а при LocalExecutor
# задачи исполняет сам планировщик — значит сокет докера нужен ему одному.
# Плата названа вслух в README и ADR 0009: доступ к сокету равен праву root
# на машине. Открывает дверь не монтирование, а группа: у сокета права 660
# и группа `docker`, чей GID на каждой машине свой и живёт в `.env`.
group_add:
- ${DOCKER_GID:?Скопируйте .env.example в .env}
# Тома перечислены заново: список службы общий не дополняет, а заменяет.
# Появится новый том у остальных служб Airflow — добавьте и сюда.
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./dags:/opt/airflow/dags:ro
- ./infra/airflow/init.sh:/opt/airflow/init.sh:ro
- airflow_logs:/opt/airflow/logs
- airflow_auth:/opt/airflow/auth
mem_limit: 640m
healthcheck:
test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8974/health >/dev/null"]
interval: 10s
timeout: 5s
retries: 30
start_period: 30s
airflow-dag-processor:
<<: *airflow-common
restart: unless-stopped
depends_on:
airflow-init:
condition: service_completed_successfully
airflow-apiserver:
condition: service_healthy
command: dag-processor
mem_limit: 384m
healthcheck:
test: ["CMD-SHELL", "curl -sf http://airflow-apiserver:8080/api/v2/monitor/health | python -c 'import json,sys; sys.exit(0 if json.load(sys.stdin)[\"dag_processor\"][\"status\"] == \"healthy\" else 1)'"]
interval: 10s
timeout: 5s
retries: 30
start_period: 30s
superset-init:
<<: *superset-common
restart: "no"
healthcheck:
disable: true
depends_on:
postgres-metadata:
condition: service_healthy
clickhouse-02:
condition: service_healthy
entrypoint: ["/bin/bash"]
command: ["/app/infra/init.sh"]
superset:
<<: *superset-common
restart: unless-stopped
depends_on:
superset-init:
condition: service_completed_successfully
ports:
- "127.0.0.1:${SUPERSET_PORT:?Скопируйте .env.example в .env}:8088"
mem_limit: 1g
healthcheck:
test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8088/health >/dev/null"]
interval: 10s
timeout: 5s
retries: 30
start_period: 30s
prometheus:
image: prom/prometheus:v3.13.2
restart: unless-stopped
depends_on:
clickhouse-keeper:
condition: service_healthy
clickhouse-01:
condition: service_healthy
clickhouse-02:
condition: service_healthy
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=7d
- --storage.tsdb.retention.size=512MB
volumes:
- ./infra/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
ports:
- "127.0.0.1:${PROMETHEUS_PORT:?Скопируйте .env.example в .env}:9090"
mem_limit: 512m
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:9090/-/ready >/dev/null 2>&1"]
interval: 10s
timeout: 5s
retries: 15
start_period: 10s
grafana:
image: grafana/grafana:13.1.1
restart: unless-stopped
depends_on:
prometheus:
condition: service_healthy
environment:
GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:?Скопируйте .env.example в .env}
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:?Скопируйте .env.example в .env}
GF_USERS_ALLOW_SIGN_UP: "false"
GF_NEWS_NEWS_FEED_ENABLED: "false"
GF_ANALYTICS_REPORTING_ENABLED: "false"
GF_ANALYTICS_CHECK_FOR_UPDATES: "false"
volumes:
- ./infra/grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro
- grafana_data:/var/lib/grafana
ports:
- "127.0.0.1:${GRAFANA_PORT:?Скопируйте .env.example в .env}:3000"
mem_limit: 512m
healthcheck:
test: ["CMD-SHELL", "curl -sf http://127.0.0.1:3000/api/health >/dev/null"]
interval: 10s
timeout: 5s
retries: 15
start_period: 15s
volumes:
clickhouse_keeper_data:
clickhouse_01_data:
clickhouse_02_data:
kafka_data:
postgres_metadata_data:
airflow_logs:
airflow_auth:
superset_home:
prometheus_data:
grafana_data: