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>
This commit is contained in:
@@ -50,7 +50,10 @@ make check-services
|
|||||||
Дальше, в рабочей петле, обычно хватает `make smoke`.
|
Дальше, в рабочей петле, обычно хватает `make smoke`.
|
||||||
|
|
||||||
`.env.example` — образец обязательной локальной настройки. В нём живут имя
|
`.env.example` — образец обязательной локальной настройки. В нём живут имя
|
||||||
экземпляра, внешние порты, учебные учётные данные и ключи. `compose.yaml` не
|
экземпляра, внешние порты, учебные учётные данные и ключи. Одно значение образца
|
||||||
|
верно не везде — `DOCKER_GID`: у группы `docker` на каждой машине свой номер, и
|
||||||
|
свой подскажет `getent group docker`. Пока он не сойдётся, стенд работает весь,
|
||||||
|
кроме пульта мира — о нём ниже. `compose.yaml` не
|
||||||
дублирует их умолчаниями: если значения нет в `.env`, Compose сразу предложит
|
дублирует их умолчаниями: если значения нет в `.env`, Compose сразу предложит
|
||||||
скопировать образец. Внутренние адреса, имена топиков и версии образов описывают
|
скопировать образец. Внутренние адреса, имена топиков и версии образов описывают
|
||||||
сам стенд, поэтому записаны литералами в `compose.yaml` и Dockerfile.
|
сам стенд, поэтому записаны литералами в `compose.yaml` и Dockerfile.
|
||||||
|
|||||||
@@ -376,6 +376,7 @@ services:
|
|||||||
group_add:
|
group_add:
|
||||||
- ${DOCKER_GID:?Скопируйте .env.example в .env}
|
- ${DOCKER_GID:?Скопируйте .env.example в .env}
|
||||||
# Тома перечислены заново: список службы общий не дополняет, а заменяет.
|
# Тома перечислены заново: список службы общий не дополняет, а заменяет.
|
||||||
|
# Появится новый том у остальных служб Airflow — добавьте и сюда.
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- ./dags:/opt/airflow/dags:ro
|
- ./dags:/opt/airflow/dags:ro
|
||||||
|
|||||||
+9
-11
@@ -28,8 +28,7 @@ from airflow.sdk import Param, Variable, dag, get_current_context, task
|
|||||||
|
|
||||||
# Факты стенда — образ генератора, сеть, адрес брокера, топик и размер
|
# Факты стенда — образ генератора, сеть, адрес брокера, топик и размер
|
||||||
# стартового мира — приходят окружением, и называет их compose: тот же, что
|
# стартового мира — приходят окружением, и называет их compose: тот же, что
|
||||||
# называет их разовой службе генератора. Генератор для пульта — отдельная и
|
# называет их разовой службе генератора.
|
||||||
# заменяемая сущность, и знает о нём даг ровно то, что здесь перечислено.
|
|
||||||
GENERATOR_IMAGE = os.environ["GENERATOR_IMAGE"]
|
GENERATOR_IMAGE = os.environ["GENERATOR_IMAGE"]
|
||||||
STAND_NETWORK = os.environ["STAND_NETWORK"]
|
STAND_NETWORK = os.environ["STAND_NETWORK"]
|
||||||
GENERATOR_ENVIRONMENT = {
|
GENERATOR_ENVIRONMENT = {
|
||||||
@@ -41,11 +40,10 @@ STARTING_DAYS = int(os.environ["WORLD_STARTING_DAYS"])
|
|||||||
# Позиция на оси: номер первого несыгранного дня. Переменной нет — мир в
|
# Позиция на оси: номер первого несыгранного дня. Переменной нет — мир в
|
||||||
# стартовом состоянии, и играть надо сразу за ним.
|
# стартовом состоянии, и играть надо сразу за ним.
|
||||||
#
|
#
|
||||||
# Позиция именно ставится, а не увеличивается на единицу. Наложись один прогон
|
# Позиция ставится, а не увеличивается. Наложись один прогон на другой, худшее
|
||||||
# на другой, худшее при таком правиле — сыгранный дважды день: номера событий
|
# при таком правиле — сыгранный дважды день, а повтор схлопнет
|
||||||
# детерминированы, и повтор схлопнет ReplacingMergeTree. Увеличение в том же
|
# ReplacingMergeTree. Увеличение молча съело бы день, и в мире осталась бы
|
||||||
# случае молча съело бы день, и в мире осталась бы дыра, которой никто не
|
# дыра, которой никто не заметит.
|
||||||
# заметит.
|
|
||||||
WORLD_POSITION = "world_position"
|
WORLD_POSITION = "world_position"
|
||||||
|
|
||||||
# Тик выключателя. Каденцию задаёт не он, а сама длина живого дня — около
|
# Тик выключателя. Каденцию задаёт не он, а сама длина живого дня — около
|
||||||
@@ -75,10 +73,9 @@ def _play(task_id: str, command: list[str]) -> DockerOperator:
|
|||||||
command=command,
|
command=command,
|
||||||
network_mode=STAND_NETWORK,
|
network_mode=STAND_NETWORK,
|
||||||
environment=GENERATOR_ENVIRONMENT,
|
environment=GENERATOR_ENVIRONMENT,
|
||||||
# Контейнер убирается за собой в любом исходе — вопреки имени
|
# Контейнер убирается в любом исходе: вывод генератора оператор уже
|
||||||
# значения: оператор сносит его в `finally`. Терять при этом нечего,
|
# перелил в журнал задачи, а мёртвые контейнеры копить незачем.
|
||||||
# вывод генератора он уже перелил в журнал задачи.
|
auto_remove="force",
|
||||||
auto_remove="success",
|
|
||||||
# По умолчанию оператор монтирует контейнеру временный каталог. Здесь
|
# По умолчанию оператор монтирует контейнеру временный каталог. Здесь
|
||||||
# это ловушка: путь он заводит внутри Airflow, а монтирует демон с
|
# это ловушка: путь он заводит внутри Airflow, а монтирует демон с
|
||||||
# хоста, где такого пути нет. Генератору временный каталог не нужен.
|
# хоста, где такого пути нет. Генератору временный каталог не нужен.
|
||||||
@@ -157,6 +154,7 @@ def world_live_day():
|
|||||||
dag_id="world_live",
|
dag_id="world_live",
|
||||||
schedule=LIVE_TICK,
|
schedule=LIVE_TICK,
|
||||||
start_date=START_DATE,
|
start_date=START_DATE,
|
||||||
|
catchup=False,
|
||||||
is_paused_upon_creation=True,
|
is_paused_upon_creation=True,
|
||||||
max_active_runs=1,
|
max_active_runs=1,
|
||||||
tags=TAGS,
|
tags=TAGS,
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ USER root
|
|||||||
|
|
||||||
# Официальный провайдер Kafka использует тот же confluent-kafka. Пробникам
|
# Официальный провайдер Kafka использует тот же confluent-kafka. Пробникам
|
||||||
# не нужны его подключения и обёртки, поэтому в образ добавлен сам клиент.
|
# не нужны его подключения и обёртки, поэтому в образ добавлен сам клиент.
|
||||||
|
#
|
||||||
|
# Провайдера docker, которым пульт мира зовёт генератор, ставить не нужно:
|
||||||
|
# база несёт его сама — 4.5.7 к Airflow 3.3.0, замер 13 августа 2026 года.
|
||||||
RUN uv pip install --python /home/airflow/.local/bin/python --no-cache \
|
RUN uv pip install --python /home/airflow/.local/bin/python --no-cache \
|
||||||
"clickhouse-connect==1.6.0" \
|
"clickhouse-connect==1.6.0" \
|
||||||
"confluent-kafka==2.15.0" \
|
"confluent-kafka==2.15.0" \
|
||||||
|
|||||||
Reference in New Issue
Block a user