refactor(compose): отделены локальные настройки от фактов стенда

- Зачем:
  - устранены дублирование значений и тихая подстановка неполной настройки.
- Что:
  - версии образов и внутренняя топология закреплены рядом с местом использования.
  - имя экземпляра, внешние порты, учётные данные и ключи сделаны обязательными настройками .env.
  - быстрый старт, Dockerfile и статическая проверка приведены к новой границе.
- Проверка:
  - make config-test.
  - docker build для образов Airflow и Superset без аргументов.
  - make up; make smoke; make check-clickhouse; make check-services.
This commit is contained in:
2026-08-08 20:50:35 +03:00
parent 5d012cc222
commit 704c139d5a
6 changed files with 68 additions and 87 deletions
+5 -15
View File
@@ -1,39 +1,29 @@
# Имя и внешние порты меняют для второй копии стенда или при конфликте.
COMPOSE_PROJECT_NAME=clickstream-data-platform COMPOSE_PROJECT_NAME=clickstream-data-platform
CLICKHOUSE_IMAGE=clickhouse/clickhouse-server:26.3.17.56
CLICKHOUSE_01_HTTP_PORT=28123 CLICKHOUSE_01_HTTP_PORT=28123
CLICKHOUSE_01_TCP_PORT=29000 CLICKHOUSE_01_TCP_PORT=29000
CLICKHOUSE_02_HTTP_PORT=28124 CLICKHOUSE_02_HTTP_PORT=28124
CLICKHOUSE_02_TCP_PORT=29001 CLICKHOUSE_02_TCP_PORT=29001
KAFKA_IMAGE=apache/kafka:4.3.1
KAFKA_EXTERNAL_PORT=29092 KAFKA_EXTERNAL_PORT=29092
# Разовая служба генератора. Адрес брокера здесь внутренний: команда идёт
# из сети Compose.
KAFKA_BOOTSTRAP_SERVERS=kafka:9092
KAFKA_TOPIC=hits
PROMETHEUS_IMAGE=prom/prometheus:v3.13.2
PROMETHEUS_PORT=29090 PROMETHEUS_PORT=29090
GRAFANA_IMAGE=grafana/grafana:13.1.1
GRAFANA_PORT=23000 GRAFANA_PORT=23000
AIRFLOW_PORT=28080
SUPERSET_PORT=28088
# Учётные данные и ключи учебного стенда. Для VPS замените значения образца.
GRAFANA_ADMIN_USER=admin GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASSWORD=admin GRAFANA_ADMIN_PASSWORD=admin
POSTGRES_IMAGE=postgres:16-alpine
POSTGRES_ADMIN_USER=postgres POSTGRES_ADMIN_USER=postgres
POSTGRES_ADMIN_PASSWORD=postgres-local POSTGRES_ADMIN_PASSWORD=postgres-local
AIRFLOW_METADATA_USER=airflow AIRFLOW_METADATA_USER=airflow
AIRFLOW_METADATA_PASSWORD=airflow-local AIRFLOW_METADATA_PASSWORD=airflow-local
SUPERSET_METADATA_USER=superset SUPERSET_METADATA_USER=superset
SUPERSET_METADATA_PASSWORD=superset-local SUPERSET_METADATA_PASSWORD=superset-local
AIRFLOW_IMAGE=apache/airflow:3.3.0
AIRFLOW_PORT=28080
AIRFLOW_ADMIN_USER=admin AIRFLOW_ADMIN_USER=admin
AIRFLOW_ADMIN_PASSWORD=airflow AIRFLOW_ADMIN_PASSWORD=airflow
AIRFLOW_JWT_SECRET=local-only-airflow-jwt-secret-do-not-use AIRFLOW_JWT_SECRET=local-only-airflow-jwt-secret-do-not-use
AIRFLOW_API_SECRET_KEY=local-only-airflow-api-secret-do-not-use AIRFLOW_API_SECRET_KEY=local-only-airflow-api-secret-do-not-use
AIRFLOW_FERNET_KEY=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= AIRFLOW_FERNET_KEY=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
SUPERSET_IMAGE=apache/superset:6.1.0
SUPERSET_PORT=28088
SUPERSET_ADMIN_USER=admin SUPERSET_ADMIN_USER=admin
SUPERSET_ADMIN_PASSWORD=superset SUPERSET_ADMIN_PASSWORD=superset
SUPERSET_SECRET_KEY=local-only-superset-secret-do-not-use-outside-this-stand SUPERSET_SECRET_KEY=local-only-superset-secret-do-not-use-outside-this-stand
+10 -17
View File
@@ -35,9 +35,10 @@ Prometheus, Grafana и общая база Postgres для метаданных.
`make typecheck`, `make test` — и сборкам `make docs` и `make inventory` нужен `make typecheck`, `make test` — и сборкам `make docs` и `make inventory` нужен
`uv`. `uv`.
Стенд запускается без `.env`: Сначала скопируйте настройки стенда:
```bash ```bash
cp .env.example .env
make up make up
make smoke make smoke
make check-clickhouse make check-clickhouse
@@ -49,19 +50,11 @@ make check-services
`make check-services` — что Airflow запускает DAG, а Superset ходит в базу. `make check-services` — что Airflow запускает DAG, а Superset ходит в базу.
Дальше, в рабочей петле, обычно хватает `make smoke`. Дальше, в рабочей петле, обычно хватает `make smoke`.
Чтобы изменить образы, порты или учебные учётные данные, скопируйте образец: `.env.example` — образец обязательной локальной настройки. В нём живут имя
экземпляра, внешние порты, учебные учётные данные и ключи. `compose.yaml` не
```bash дублирует их умолчаниями: если значения нет в `.env`, Compose сразу предложит
cp .env.example .env скопировать образец. Внутренние адреса, имена топиков и версии образов описывают
make up сам стенд, поэтому записаны литералами в `compose.yaml` и Dockerfile.
make smoke
```
`.env.example` — справочник, а не настройка: в нём перечислены все переменные,
которые читает `compose.yaml`, с теми же значениями по умолчанию. Стенд его не
читает и на согласованность не проверяет, поэтому расхождение с `compose.yaml`
обнаружит только читатель. Меняя подстановку `${VAR:-значение}` в
`compose.yaml`, поправьте образец тем же коммитом.
Учётные данные Postgres и Grafana применяются при создании их томов. Учётные данные Postgres и Grafana применяются при создании их томов.
После первого запуска меняйте их только вместе с `make clean`: команда удалит После первого запуска меняйте их только вместе с `make clean`: команда удалит
@@ -189,8 +182,8 @@ make generate-live GENERATOR_DAY=3 GENERATOR_SPEED=1000
| `GENERATOR_LIMIT` | потолок событий на прогон; только `generate-batch` | нет: день целиком | | `GENERATOR_LIMIT` | потолок событий на прогон; только `generate-batch` | нет: день целиком |
| `GENERATOR_SPEED` | ускорение модельного времени; только `generate-live` | ×60, задано в генераторе | | `GENERATOR_SPEED` | ускорение модельного времени; только `generate-live` | ×60, задано в генераторе |
Куда отправлять, обе цели берут из `.env`: `KAFKA_BOOTSTRAP_SERVERS` (внутри Куда отправлять, обеим целям задаёт сам стенд: внутри сети Compose это брокер
сети Compose это `kafka:9092`) и `KAFKA_TOPIC` (`hits`). `kafka:9092` и топик `hits`.
Образ цели не пересобирают: нет образа — Compose соберёт его сам, есть — Образ цели не пересобирают: нет образа — Compose соберёт его сам, есть —
возьмёт как есть. Пересобрать намеренно, после правки `generator/Dockerfile` возьмёт как есть. Пересобрать намеренно, после правки `generator/Dockerfile`
@@ -240,7 +233,7 @@ uv run --project generator python -m clickstream_generator batch \
- `superset` — интерфейс и подготовленное подключение ClickHouse; - `superset` — интерфейс и подготовленное подключение ClickHouse;
- `prometheus` и `grafana` — сбор и просмотр встроенных метрик ClickHouse. - `prometheus` и `grafana` — сбор и просмотр встроенных метрик ClickHouse.
Порты доступны только с локальной машины: С настройками из `.env.example` порты доступны только с локальной машины:
- нода 1 — `http://127.0.0.1:28123`, нативный порт `29000`; - нода 1 — `http://127.0.0.1:28123`, нативный порт `29000`;
- нода 2 — `http://127.0.0.1:28124`, нативный порт `29001`; - нода 2 — `http://127.0.0.1:28124`, нативный порт `29001`;
+40 -44
View File
@@ -1,4 +1,4 @@
name: ${COMPOSE_PROJECT_NAME:-clickstream-data-platform} name: ${COMPOSE_PROJECT_NAME:?Скопируйте .env.example в .env}
x-clickhouse-nofile: &clickhouse-nofile x-clickhouse-nofile: &clickhouse-nofile
nofile: nofile:
@@ -6,7 +6,7 @@ x-clickhouse-nofile: &clickhouse-nofile
hard: 262144 hard: 262144
x-clickhouse-common: &clickhouse-common x-clickhouse-common: &clickhouse-common
image: ${CLICKHOUSE_IMAGE:-clickhouse/clickhouse-server:26.3.17.56} image: clickhouse/clickhouse-server:26.3.17.56
restart: unless-stopped restart: unless-stopped
environment: environment:
# Стенд локальный: нодам нужен общий пользователь без пароля для Distributed. # Стенд локальный: нодам нужен общий пользователь без пароля для Distributed.
@@ -28,24 +28,22 @@ x-airflow-common: &airflow-common
image: clickstream-airflow:local image: clickstream-airflow:local
build: build:
context: ./infra/airflow context: ./infra/airflow
args:
AIRFLOW_BASE_IMAGE: ${AIRFLOW_IMAGE:-apache/airflow:3.3.0}
environment: environment:
AIRFLOW__CORE__EXECUTOR: LocalExecutor AIRFLOW__CORE__EXECUTOR: LocalExecutor
AIRFLOW__CORE__PARALLELISM: "4" AIRFLOW__CORE__PARALLELISM: "4"
AIRFLOW__CORE__LOAD_EXAMPLES: "false" AIRFLOW__CORE__LOAD_EXAMPLES: "false"
AIRFLOW__CORE__AUTH_MANAGER: airflow.api_fastapi.auth.managers.simple.simple_auth_manager.SimpleAuthManager AIRFLOW__CORE__AUTH_MANAGER: airflow.api_fastapi.auth.managers.simple.simple_auth_manager.SimpleAuthManager
AIRFLOW__CORE__SIMPLE_AUTH_MANAGER_USERS: "${AIRFLOW_ADMIN_USER:-admin}:admin" 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__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__EXECUTION_API_SERVER_URL: http://airflow-apiserver:8080/execution/
AIRFLOW__CORE__FERNET_KEY: ${AIRFLOW_FERNET_KEY:-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=} AIRFLOW__CORE__FERNET_KEY: ${AIRFLOW_FERNET_KEY:?Скопируйте .env.example в .env}
AIRFLOW__API_AUTH__JWT_SECRET: ${AIRFLOW_JWT_SECRET:-local-only-airflow-jwt-secret-do-not-use} AIRFLOW__API_AUTH__JWT_SECRET: ${AIRFLOW_JWT_SECRET:?Скопируйте .env.example в .env}
AIRFLOW__API_AUTH__JWT_ISSUER: airflow AIRFLOW__API_AUTH__JWT_ISSUER: airflow
AIRFLOW__API__SECRET_KEY: ${AIRFLOW_API_SECRET_KEY:-local-only-airflow-api-secret-do-not-use} AIRFLOW__API__SECRET_KEY: ${AIRFLOW_API_SECRET_KEY:?Скопируйте .env.example в .env}
AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: "postgresql+psycopg2://${AIRFLOW_METADATA_USER:-airflow}:${AIRFLOW_METADATA_PASSWORD:-airflow-local}@postgres-metadata:5432/airflow" 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__SCHEDULER__ENABLE_HEALTH_CHECK: "true"
AIRFLOW_ADMIN_USER: ${AIRFLOW_ADMIN_USER:-admin} AIRFLOW_ADMIN_USER: ${AIRFLOW_ADMIN_USER:?Скопируйте .env.example в .env}
AIRFLOW_ADMIN_PASSWORD: ${AIRFLOW_ADMIN_PASSWORD:-airflow} AIRFLOW_ADMIN_PASSWORD: ${AIRFLOW_ADMIN_PASSWORD:?Скопируйте .env.example в .env}
volumes: volumes:
- ./dags:/opt/airflow/dags:ro - ./dags:/opt/airflow/dags:ro
- ./infra/airflow/init.sh:/opt/airflow/init.sh:ro - ./infra/airflow/init.sh:/opt/airflow/init.sh:ro
@@ -71,21 +69,19 @@ x-generator-common: &generator-common
# Адрес брокера и имя топика — факты стенда, и называет их стенд. # Адрес брокера и имя топика — факты стенда, и называет их стенд.
# Остальное (день, зерно, число дней, предел пачки) приходит аргументами # Остальное (день, зерно, число дней, предел пачки) приходит аргументами
# от того, кто запускает: у службы нет позиции на оси мира. # от того, кто запускает: у службы нет позиции на оси мира.
KAFKA_BOOTSTRAP_SERVERS: ${KAFKA_BOOTSTRAP_SERVERS:-kafka:9092} KAFKA_BOOTSTRAP_SERVERS: kafka:9092
KAFKA_TOPIC: ${KAFKA_TOPIC:-hits} KAFKA_TOPIC: hits
x-superset-common: &superset-common x-superset-common: &superset-common
image: clickstream-superset:local image: clickstream-superset:local
build: build:
context: ./infra/superset context: ./infra/superset
args:
SUPERSET_BASE_IMAGE: ${SUPERSET_IMAGE:-apache/superset:6.1.0}
environment: environment:
SUPERSET_CONFIG_PATH: /app/pythonpath/superset_config.py SUPERSET_CONFIG_PATH: /app/pythonpath/superset_config.py
SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY:-local-only-superset-secret-do-not-use-outside-this-stand} SUPERSET_SECRET_KEY: ${SUPERSET_SECRET_KEY:?Скопируйте .env.example в .env}
SUPERSET__SQLALCHEMY_DATABASE_URI: "postgresql+psycopg2://${SUPERSET_METADATA_USER:-superset}:${SUPERSET_METADATA_PASSWORD:-superset-local}@postgres-metadata:5432/superset" 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:-admin} SUPERSET_ADMIN_USER: ${SUPERSET_ADMIN_USER:?Скопируйте .env.example в .env}
SUPERSET_ADMIN_PASSWORD: ${SUPERSET_ADMIN_PASSWORD:-superset} SUPERSET_ADMIN_PASSWORD: ${SUPERSET_ADMIN_PASSWORD:?Скопируйте .env.example в .env}
volumes: volumes:
- ./infra/superset/superset_config.py:/app/pythonpath/superset_config.py:ro - ./infra/superset/superset_config.py:/app/pythonpath/superset_config.py:ro
- ./infra/superset/init.sh:/app/infra/init.sh:ro - ./infra/superset/init.sh:/app/infra/init.sh:ro
@@ -94,7 +90,7 @@ x-superset-common: &superset-common
services: services:
clickhouse-keeper: clickhouse-keeper:
image: ${CLICKHOUSE_IMAGE:-clickhouse/clickhouse-server:26.3.17.56} image: clickhouse/clickhouse-server:26.3.17.56
restart: unless-stopped restart: unless-stopped
user: clickhouse user: clickhouse
ulimits: *clickhouse-nofile ulimits: *clickhouse-nofile
@@ -118,8 +114,8 @@ services:
<<: *clickhouse-common <<: *clickhouse-common
hostname: clickhouse-01 hostname: clickhouse-01
ports: ports:
- "127.0.0.1:${CLICKHOUSE_01_HTTP_PORT:-28123}:8123" - "127.0.0.1:${CLICKHOUSE_01_HTTP_PORT:?Скопируйте .env.example в .env}:8123"
- "127.0.0.1:${CLICKHOUSE_01_TCP_PORT:-29000}:9000" - "127.0.0.1:${CLICKHOUSE_01_TCP_PORT:?Скопируйте .env.example в .env}:9000"
volumes: volumes:
- ./infra/clickhouse/config.d/cluster.xml:/etc/clickhouse-server/config.d/cluster.xml:ro - ./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/prometheus.xml:/etc/clickhouse-server/config.d/prometheus.xml:ro
@@ -131,8 +127,8 @@ services:
<<: *clickhouse-common <<: *clickhouse-common
hostname: clickhouse-02 hostname: clickhouse-02
ports: ports:
- "127.0.0.1:${CLICKHOUSE_02_HTTP_PORT:-28124}:8123" - "127.0.0.1:${CLICKHOUSE_02_HTTP_PORT:?Скопируйте .env.example в .env}:8123"
- "127.0.0.1:${CLICKHOUSE_02_TCP_PORT:-29001}:9000" - "127.0.0.1:${CLICKHOUSE_02_TCP_PORT:?Скопируйте .env.example в .env}:9000"
volumes: volumes:
- ./infra/clickhouse/config.d/cluster.xml:/etc/clickhouse-server/config.d/cluster.xml:ro - ./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/prometheus.xml:/etc/clickhouse-server/config.d/prometheus.xml:ro
@@ -140,17 +136,17 @@ services:
- clickhouse_02_data:/var/lib/clickhouse - clickhouse_02_data:/var/lib/clickhouse
kafka: kafka:
image: ${KAFKA_IMAGE:-apache/kafka:4.3.1} image: apache/kafka:4.3.1
hostname: kafka hostname: kafka
restart: unless-stopped restart: unless-stopped
ports: ports:
- "127.0.0.1:${KAFKA_EXTERNAL_PORT:-29092}:29092" - "127.0.0.1:${KAFKA_EXTERNAL_PORT:?Скопируйте .env.example в .env}:29092"
environment: environment:
KAFKA_NODE_ID: "1" KAFKA_NODE_ID: "1"
KAFKA_PROCESS_ROLES: "broker,controller" KAFKA_PROCESS_ROLES: "broker,controller"
KAFKA_CONTROLLER_QUORUM_VOTERS: "1@kafka:9093" KAFKA_CONTROLLER_QUORUM_VOTERS: "1@kafka:9093"
KAFKA_LISTENERS: "INTERNAL://:9092,CONTROLLER://:9093,EXTERNAL://:29092" KAFKA_LISTENERS: "INTERNAL://:9092,CONTROLLER://:9093,EXTERNAL://:29092"
KAFKA_ADVERTISED_LISTENERS: "INTERNAL://kafka:9092,EXTERNAL://localhost:${KAFKA_EXTERNAL_PORT:-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_LISTENER_SECURITY_PROTOCOL_MAP: "INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT"
KAFKA_INTER_BROKER_LISTENER_NAME: "INTERNAL" KAFKA_INTER_BROKER_LISTENER_NAME: "INTERNAL"
KAFKA_CONTROLLER_LISTENER_NAMES: "CONTROLLER" KAFKA_CONTROLLER_LISTENER_NAMES: "CONTROLLER"
@@ -175,7 +171,7 @@ services:
start_period: 30s start_period: 30s
kafka-init: kafka-init:
image: ${KAFKA_IMAGE:-apache/kafka:4.3.1} image: apache/kafka:4.3.1
restart: "no" restart: "no"
depends_on: depends_on:
kafka: kafka:
@@ -218,7 +214,7 @@ services:
# DDL применяется с ноды 1 по порядку имён файлов после готовности всего кластера. # DDL применяется с ноды 1 по порядку имён файлов после готовности всего кластера.
clickhouse-init: clickhouse-init:
image: ${CLICKHOUSE_IMAGE:-clickhouse/clickhouse-server:26.3.17.56} image: clickhouse/clickhouse-server:26.3.17.56
restart: "no" restart: "no"
depends_on: depends_on:
kafka-init: kafka-init:
@@ -270,7 +266,7 @@ services:
command: ["batch"] command: ["batch"]
postgres-metadata: postgres-metadata:
image: ${POSTGRES_IMAGE:-postgres:16-alpine} image: postgres:16-alpine
restart: unless-stopped restart: unless-stopped
command: command:
- postgres - postgres
@@ -279,13 +275,13 @@ services:
- -c - -c
- max_connections=50 - max_connections=50
environment: environment:
POSTGRES_USER: ${POSTGRES_ADMIN_USER:-postgres} POSTGRES_USER: ${POSTGRES_ADMIN_USER:?Скопируйте .env.example в .env}
POSTGRES_PASSWORD: ${POSTGRES_ADMIN_PASSWORD:-postgres-local} POSTGRES_PASSWORD: ${POSTGRES_ADMIN_PASSWORD:?Скопируйте .env.example в .env}
POSTGRES_DB: postgres POSTGRES_DB: postgres
AIRFLOW_METADATA_USER: ${AIRFLOW_METADATA_USER:-airflow} AIRFLOW_METADATA_USER: ${AIRFLOW_METADATA_USER:?Скопируйте .env.example в .env}
AIRFLOW_METADATA_PASSWORD: ${AIRFLOW_METADATA_PASSWORD:-airflow-local} AIRFLOW_METADATA_PASSWORD: ${AIRFLOW_METADATA_PASSWORD:?Скопируйте .env.example в .env}
SUPERSET_METADATA_USER: ${SUPERSET_METADATA_USER:-superset} SUPERSET_METADATA_USER: ${SUPERSET_METADATA_USER:?Скопируйте .env.example в .env}
SUPERSET_METADATA_PASSWORD: ${SUPERSET_METADATA_PASSWORD:-superset-local} SUPERSET_METADATA_PASSWORD: ${SUPERSET_METADATA_PASSWORD:?Скопируйте .env.example в .env}
volumes: volumes:
- ./infra/postgres/init/10-metadata-databases.sh:/docker-entrypoint-initdb.d/10-metadata-databases.sh:ro - ./infra/postgres/init/10-metadata-databases.sh:/docker-entrypoint-initdb.d/10-metadata-databases.sh:ro
- postgres_metadata_data:/var/lib/postgresql/data - postgres_metadata_data:/var/lib/postgresql/data
@@ -326,7 +322,7 @@ services:
condition: service_completed_successfully condition: service_completed_successfully
command: api-server command: api-server
ports: ports:
- "127.0.0.1:${AIRFLOW_PORT:-28080}:8080" - "127.0.0.1:${AIRFLOW_PORT:?Скопируйте .env.example в .env}:8080"
mem_limit: 512m mem_limit: 512m
healthcheck: 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)'"] 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)'"]
@@ -387,7 +383,7 @@ services:
superset-init: superset-init:
condition: service_completed_successfully condition: service_completed_successfully
ports: ports:
- "127.0.0.1:${SUPERSET_PORT:-28088}:8088" - "127.0.0.1:${SUPERSET_PORT:?Скопируйте .env.example в .env}:8088"
mem_limit: 1g mem_limit: 1g
healthcheck: healthcheck:
test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8088/health >/dev/null"] test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8088/health >/dev/null"]
@@ -397,7 +393,7 @@ services:
start_period: 30s start_period: 30s
prometheus: prometheus:
image: ${PROMETHEUS_IMAGE:-prom/prometheus:v3.13.2} image: prom/prometheus:v3.13.2
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
clickhouse-keeper: clickhouse-keeper:
@@ -415,7 +411,7 @@ services:
- ./infra/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro - ./infra/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus - prometheus_data:/prometheus
ports: ports:
- "127.0.0.1:${PROMETHEUS_PORT:-29090}:9090" - "127.0.0.1:${PROMETHEUS_PORT:?Скопируйте .env.example в .env}:9090"
mem_limit: 512m mem_limit: 512m
healthcheck: healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:9090/-/ready >/dev/null 2>&1"] test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:9090/-/ready >/dev/null 2>&1"]
@@ -425,14 +421,14 @@ services:
start_period: 10s start_period: 10s
grafana: grafana:
image: ${GRAFANA_IMAGE:-grafana/grafana:13.1.1} image: grafana/grafana:13.1.1
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
prometheus: prometheus:
condition: service_healthy condition: service_healthy
environment: environment:
GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin} GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:?Скопируйте .env.example в .env}
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin} GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:?Скопируйте .env.example в .env}
GF_USERS_ALLOW_SIGN_UP: "false" GF_USERS_ALLOW_SIGN_UP: "false"
GF_NEWS_NEWS_FEED_ENABLED: "false" GF_NEWS_NEWS_FEED_ENABLED: "false"
GF_ANALYTICS_REPORTING_ENABLED: "false" GF_ANALYTICS_REPORTING_ENABLED: "false"
@@ -441,7 +437,7 @@ services:
- ./infra/grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro - ./infra/grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro
- grafana_data:/var/lib/grafana - grafana_data:/var/lib/grafana
ports: ports:
- "127.0.0.1:${GRAFANA_PORT:-23000}:3000" - "127.0.0.1:${GRAFANA_PORT:?Скопируйте .env.example в .env}:3000"
mem_limit: 512m mem_limit: 512m
healthcheck: healthcheck:
test: ["CMD-SHELL", "curl -sf http://127.0.0.1:3000/api/health >/dev/null"] test: ["CMD-SHELL", "curl -sf http://127.0.0.1:3000/api/health >/dev/null"]
+1 -2
View File
@@ -1,5 +1,4 @@
ARG AIRFLOW_BASE_IMAGE FROM apache/airflow:3.3.0
FROM ${AIRFLOW_BASE_IMAGE}
USER root USER root
+1 -2
View File
@@ -1,5 +1,4 @@
ARG SUPERSET_BASE_IMAGE FROM apache/superset:6.1.0
FROM ${SUPERSET_BASE_IMAGE}
USER root USER root
+11 -7
View File
@@ -11,23 +11,27 @@ cleanup() {
trap cleanup EXIT trap cleanup EXIT
"${COMPOSE_CMD[@]}" --project-directory "$ROOT_DIR" config --quiet "${COMPOSE_CMD[@]}" \
config_json="$("${COMPOSE_CMD[@]}" --project-directory "$ROOT_DIR" config --format json)" --project-directory "$ROOT_DIR" \
--env-file "$ROOT_DIR/.env.example" \
config --quiet
config_json="$(
"${COMPOSE_CMD[@]}" \
--project-directory "$ROOT_DIR" \
--env-file "$ROOT_DIR/.env.example" \
config --format json
)"
# Контекст сборки образов — корень репозитория, и локальный `.env` с настоящими # Контекст сборки образов — корень репозитория, и локальный `.env` с настоящими
# паролями уехал бы в слой образа молча. Это единственная здешняя ошибка, о # паролями уехал бы в слой образа молча. Это единственная здешняя ошибка, о
# которой никто не узнает, пока образ не окажется у чужого. # которой никто не узнает, пока образ не окажется у чужого.
for private_path in '.env' '.env.*' '*.pem' '*.key' '*.crt' 'secrets/' 'credentials/'; do for private_path in '.env' '.env.*' '*.pem' '*.key' '*.crt' 'secrets/' 'credentials/'; do
grep -qxF "$private_path" "$ROOT_DIR/.dockerignore" grep -qxF "$private_path" "$ROOT_DIR/.dockerignore"
done done
grep -qx 'ARG SUPERSET_BASE_IMAGE' "$ROOT_DIR/infra/superset/Dockerfile"
jq -e ' jq -e '
.services.superset.image == "clickstream-superset:local" and .services.superset.image == "clickstream-superset:local"
(.services.superset.build.args.SUPERSET_BASE_IMAGE | length > 0)
' >/dev/null <<<"$config_json" ' >/dev/null <<<"$config_json"
grep -qx 'ARG AIRFLOW_BASE_IMAGE' "$ROOT_DIR/infra/airflow/Dockerfile"
jq -e ' jq -e '
.services["airflow-init"].image == "clickstream-airflow:local" and .services["airflow-init"].image == "clickstream-airflow:local" and
(.services["airflow-init"].build.args.AIRFLOW_BASE_IMAGE | length > 0) and
all( all(
.services[]; .services[];
((.environment // {}) | has("_PIP_ADDITIONAL_REQUIREMENTS") | not) ((.environment // {}) | has("_PIP_ADDITIONAL_REQUIREMENTS") | not)