Logo Craft Homelab Docs Нейросети Хостинг Обучение Контакты
Трендовые github проекты в нашем телеграм канале. Подпишись →
4 ноября 2026 г.

Один вечер на импорты, четыре месяца на остальное

Ingestion-контур в этом разборе — сервис на Apache Airflow, который запускает пайплайны загрузки метаданных в OpenMetadata. Поверх стандартного Airflow стоят плагин openmetadata-managed-apis (через него UI OpenMetadata деплоит и запускает DAG’и), собственные утилиты логирования и Docker-образ со строго зафиксированными зависимостями.

Переименования по официальному migration guide заняли один вечер и один коммит по одиннадцати файлам. Вся миграция заняла четыре месяца, несколько десятков коммитов и одну смену архитектурного решения на ходу. Время ушло на плагин, окружение задач, логи и зависимости, и в гайде этих проблем нет.

Хронология: два мажора одновременно

Репозиторий под Airflow 3 завели в начале марта. С первого коммита в нём лежали constraints-файлы для обеих линеек: 3.1.x и 2.10.5.

Две недели шла работа в ветке airflow-3: даги, плагин, логирование, типы. В конце марта в develop влилась параллельная ветка airflow-2.11 с фиксами старой линии, потому что продакшен всё ещё работал на двойке. В начале апреля выяснилось, что develop откатился к дагам в старом стиле, и рефакторинг, сделанный в середине марта, пришлось повторить по тем же файлам.

Весной параллельно латали сборку двойки: откаты транзитивных зависимостей и свежие constraints для 2.11.x датированы апрелем. Версию Airflow 3.1.8 зафиксировали в pins-файле только в июле.

Для живого контура такой параллельный период — норма. Его стоит сразу планировать отдельным этапом с собственным бюджетом времени.

DAG’и: простая механика, сделанная дважды

Airflow 2Airflow 3
импорты публичного APIairflow.sdk
стандартные операторыпровайдер standard
schedule_intervalschedule
DummyOperatorEmptyOperator
@once для дагов с ручным запускомschedule=None

Последнюю строку таблицы поиском и заменой не закрыть. В контуре были даги с @once, которые на деле запускались только вручную. Чтобы поведение после миграции совпало с ожидаемым, все такие даги пришлось просматривать глазами.

Дороже обошлась организационная проблема. Когда изменения из старой линии вливаются в ветку миграции, правки нового API в затронутых файлах теряются, и этого никто не видит до первого прогона. Защиты две: мерджить старую линию с оглядкой на новый API или гонять компиляцию дагов в CI после каждого такого мерджа.

Плагин на Flask: главная точка риска

openmetadata-managed-apis написан под Airflow 2: Flask-blueprint’ы, FAB-view, обращение к cached_app. В Airflow 3 веб-сервер и API-сервер разнесены, Flask-обвязка стала legacy-слоем, а cached_app исчез.

Первым сломался health check. Мониторинг и сервер OpenMetadata обращаются к /health в корне веб-сервера. В Airflow 3 этот путь занят штатным обработчиком: он регистрируется раньше blueprint’а плагина и отвечает в формате, которого OpenMetadata не ждёт.

Первое решение — monkey-patch таблицы роутов Flask после регистрации всех обработчиков. Патч срабатывал не во всех сценариях запуска, его пришлось переписать на другой хук жизненного цикла приложения. Заработало, но осталась зависимость от внутренностей Flask и порядка регистрации роутов, а значит, сломать это могло любое обновление.

Через пару недель патч заменили лёгким Flask-прокси перед API-сервером Airflow:

публичный порт → Flask-прокси
                  ├─ /health    → ответ в формате, который ждёт OpenMetadata
                  └─ остальное  → API-сервер Airflow на внутреннем порту

Такая схема работает без приватных деталей фреймворка. На переходный период в коде плагина остались ветвления под каждый мажор: выглядит некрасиво, зато фиксы выкатываются сразу на обе линии.

Если в проекте есть кастомные плагины на Flask или FAB, работу лучше начинать с них и закладывать время на перепроектирование. Даги подождут: их правки механические и быстрые.

Логирование: зелёные таски и пустые файлы

В контуре есть утилита, которая определяет, выполняется ли код внутри Airflow. Если нет, она применяет свою конфигурацию логирования для локальных запусков и тестов. Детектор опирался на набор переменных окружения, которые в Airflow 2 планировщик выставлял задачам.

В Airflow 3 задачи выполняются через Task SDK в отдельных сабпроцессах, и часть этих переменных до процесса задачи не доходит. Детектор решал, что код запущен вне Airflow, применял свою конфигурацию и отрывал штатные хендлеры задач.

Симптом коварный: в UI таски зелёные, а файлы логов попыток пустые. Ошибок нет, читать нечего. Диагностика заняла заметно больше времени, чем исправление, которое свелось к ослаблению эвристики: теперь хватает пары общих признаков окружения, конкретные переменные больше не требуются.

Заодно контур перевели с root-логгера на именованные логгеры и завели отдельные файловые хендлеры для крупных интеграций, потому что в новой схеме сообщения именованных логгеров перестали попадать в привычные места.

Любой код, который читает переменные окружения Airflow, нужно проверить заново: Task SDK сменил набор переменных без предупреждения. После первого прогона сразу откройте файлы логов попыток.

Типы и зависимости

Переезд на новый публичный API вылился в десяток коммитов с исправлением типов под mypy. Самым неудобным оказался пакет настроек, который в рантайме наполняется из отдельного конфигурационного модуля: mypy об этом не знает и выдаёт ошибку на каждое обращение к настройкам. Проблему закрыл стаб .pyi рядом с пакетом.

С зависимостями было хуже. Образ собирается с собственными constraints-файлами поверх официальных, и весной накопились конфликт версий на сборке, откат jinja2, откат h11 и PyJWT, удаление зависимости от устаревшего pkg_resources. Constraints для 2.11.x под разные версии Python добавлялись уже после основной работы над тройкой.

Способ здесь один: собрать образ, поймать конфликт, зафиксировать версию, повторить. Из этого цикла вытекают два правила:

  • constraints-файл должен соответствовать и версии Airflow, и версии Python, расхождение в любой из них даёт конфликты;
  • транзитивные зависимости после мажорного апгрейда нужно фиксировать явно, потому что ломаются они молча.

Чек-лист перед миграцией

  1. Заложить в план параллельный период на двух мажорах и договориться о правилах мерджа между линиями до начала работы.
  2. Начать с плагинов и логирования: там риск молчаливой поломки максимальный.
  3. Плагины на Flask/FAB вести отдельным эпиком. Monkey-patch годится только как временная мера.
  4. Перевести даги на airflow.sdk, провайдер standard и schedule, ручным запускам поставить schedule=None.
  5. Включить компиляцию дагов в CI после каждого мерджа из старой линии.
  6. Проверить код, который смотрит на переменные окружения Airflow.
  7. После первого прогона открыть файлы логов попыток.
  8. Подготовить стабы для runtime-магии до того, как mypy начнёт падать.
  9. Вести constraints под связку «версия Airflow + версия Python» и явно фиксировать транзитивные пакеты.

Что дальше с Airflow в OpenMetadata

В релизных заметках OpenMetadata 2.0 сказано, что Airflow как внутренний оркестратор объявлен deprecated уже в версии 2.1 в пользу нативного Kubernetes-оркестратора, который появился ещё в 1.12. Пайплайны ingestion могут выполняться как Kubernetes Jobs без Airflow: нужно переключить pipelineServiceClientConfig.type с airflow на k8s и пересоздать пайплайны.

Внешних установок Airflow это формально не касается, но тем, кто планирует переезд на тройку только ради OpenMetadata, стоит сначала оценить переход на Kubernetes Jobs. Отказ от привязки к внутренностям фреймворка и явно зафиксированные зависимости пригодятся при любом из этих вариантов.