XNOVA
← Все статьи

СТАТЬЯ / data-engineering

Apache Airflow: как создать первый DAG на Python

Пишем первый DAG в Apache Airflow 3.3.2: две задачи на Python, связь через TaskFlow, ручной запуск и проверка результата.

Автор: XNova

Допустим, у вас два шага обработки: сначала получить данные, затем посчитать результат. В обычном скрипте достаточно вызвать две функции подряд. Но когда процесс нужно запускать повторно, разбираться с ошибками и видеть, на каком шаге всё остановилось, хочется управлять этими действиями по отдельности. Для этого и пригодится Apache Airflow.

Чтобы понять его устройство, не будем сразу поднимать сложный конвейер с базой данных и очередями. Напишем DAG с двумя задачами: load_numbers подготовит список [1, 2, 3], а calculate_total получит его и вычислит сумму. Затем запустим процесс вручную и найдём total=6 в журнале второй задачи.

Всё, что потребуется, — знакомство с Python и терминалом. Пример рассчитан на Apache Airflow 3.3.2 и Python 3.12 в Linux, macOS или WSL2. В конце вы будете понимать, что именно делает DAG-файл, зачем нужен TaskFlow и как убедиться, что Airflow выполнил обе задачи.

Что такое Apache Airflow и DAG

Apache Airflow помогает управлять процессами из нескольких шагов. Вы задаёте задачи, порядок их выполнения и условия запуска; Airflow отслеживает отдельные выполнения и позволяет найти ошибку в конкретном шаге. Это удобно для обработки данных, регулярных отчётов и других работ, где важна последовательность действий.

Основа такого процесса — DAG, сокращение от Directed Acyclic Graph, то есть направленного ациклического графа. DAG описывает состав задач и зависимости между ними; содержимое задач задаёт Python-код. В нашем примере связь одна: сначала load_numbers, затем calculate_total. Направление показывает, кто от кого зависит; циклов быть не должно, иначе нельзя определить порядок выполнения. Подробнее устройство графа разобрано в документации Airflow.

Здесь полезно сразу различать три понятия:

  • DAG — описание всего процесса: его задачи и связи.
  • Task — отдельная задача внутри DAG. Мы создадим две такие задачи из обычных Python-функций.
  • DagRun — одно конкретное выполнение DAG. Внутри него у каждой задачи есть свой экземпляр — Task Instance, со статусом и журналом.

Представьте, что вы запускаете наш процесс дважды. Описание DAG остаётся одним и тем же, зато появляются два DagRun, и каждый хранит собственные результаты задач. Поэтому Airflow позволяет отличить «процесс описан правильно» от «в этот раз процесс выполнился успешно».

Установите Airflow для первого запуска

Airflow — это больше, чем библиотека с декораторами для Python. Чтобы запустить задачи и посмотреть их состояния, ему нужны служебная база данных и компоненты выполнения. Для обучения всё это можно поднять на одной машине без отдельного PostgreSQL или очереди сообщений.

Создайте рабочий каталог и виртуальное окружение Python 3.12. Команды ниже подходят для Linux и macOS; на Windows используйте WSL2:

mkdir airflow-first-dag
cd airflow-first-dag
python3.12 -m venv .venv
source .venv/bin/activate
export AIRFLOW_HOME="$PWD/.airflow"
CONSTRAINT_URL=https://raw.githubusercontent.com/apache/airflow/constraints-3.3.2/constraints-3.12.txt
python -m pip install "apache-airflow==3.3.2" --constraint "$CONSTRAINT_URL"
airflow version

Последняя команда должна показать 3.3.2. Файл constraints закрепляет совместимые версии библиотек для выбранного выпуска Airflow и Python. Поэтому мы используем его вместе с конкретной версией пакета, как рекомендует официальная инструкция по установке. Для установки понадобится интернет.

Обратите внимание на AIRFLOW_HOME: здесь Airflow будет хранить настройки, служебную базу и каталог с DAG-файлами. Мы задали путь внутри учебного проекта, чтобы он не смешивался с другими установками. Во втором терминале эту переменную нужно будет задать снова.

airflow standalone запускает локальную конфигурацию Airflow для разработки и обучения. Эту команду выполним после создания DAG. Согласно официальному Quick Start, интерфейс будет доступен по адресу http://localhost:8080. Airflow создаст локальную учётную запись; если пароль не показан в терминале, найдите его в $AIRFLOW_HOME/simple_auth_manager_passwords.json.generated. Это секрет вашего локального окружения — не добавляйте его в код или публикации.

Режим standalone рассчитан на знакомство с Airflow. Для рабочего сервиса потребуются отдельные решения по развёртыванию, хранению данных и безопасности.

Напишите первый DAG на Python

**В Airflow 3 публичные декораторы dag и task импортируются из airflow.sdk. С помощью @dag мы описываем весь процесс, а с помощью @task — каждый его шаг. Такой подход называется TaskFlow API. Он позволяет писать логику задач как обычные функции Python; пример есть в официальном руководстве TaskFlow.

Создайте в рабочем каталоге файл first_dag.py и добавьте код:

"""Minimal manually triggered TaskFlow DAG for Apache Airflow 3.3.2."""

from __future__ import annotations

import pendulum
from airflow.sdk import dag, task


@dag(
    dag_id="first_python_dag",
    schedule=None,
    start_date=pendulum.datetime(2026, 1, 1, tz="UTC"),
    catchup=False,
    tags=["tutorial"],
)
def first_python_dag() -> None:
    @task
    def load_numbers() -> list[int]:
        return [1, 2, 3]

    @task
    def calculate_total(numbers: list[int]) -> int:
        total = sum(numbers)
        print(f"total={total}")
        return total

    calculate_total(load_numbers())


first_python_dag()

Разберём важные строки. Декоратор @dag задаёт имя first_python_dag и настройки запуска. schedule=None оставляет DAG без автоматического расписания: первый запуск сделаем вручную. Дата start_date записана с часовым поясом UTC, чтобы не зависеть от настроек компьютера. catchup=False станет важен, если позже добавить расписание: он запрещает автоматически создавать накопившиеся пропущенные запуски.

Две функции пока выглядят как обычный Python: первая возвращает числа, вторая печатает сумму. Отличие проявится при запуске — Airflow выполнит их как отдельные задачи, а не как один непрерывный скрипт.

Наконец, строка first_python_dag() в конце файла создаёт объект DAG, когда Airflow читает модуль. В этот момент сумма ещё не вычисляется: тела задач выполняются позднее, при запуске процесса.

Переместите файл туда, где Airflow ищет определения:

mkdir -p "$AIRFLOW_HOME/dags"
cp first_dag.py "$AIRFLOW_HOME/dags/first_dag.py"
airflow config get-value core dags_folder

Проверьте, что последняя команда указывает на $AIRFLOW_HOME/dags. Если путь другой, используйте каталог из фактической конфигурации Airflow.

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

Как связаны две задачи TaskFlow

Вызов calculate_total(load_numbers()) задаёт связь двух задач через результат первой. При построении графа Airflow создаёт два объекта задачи: load_numbers находится выше по зависимости (upstream), calculate_total — ниже (downstream).

На уровне обычного Python запись выглядит как вызов одной функции внутри другой. Однако декоратор @task изменяет семантику вызова при объявлении DAG: строится граф, а реальное вычисление происходит позже. Отдельное выражение load_numbers() >> calculate_total() для передачи аргумента здесь не нужно.

Определение first_python_dag создаёт две связанные задачи; во время тестового DagRun load_numbers передаёт список через XCom в calculate_total, после чего обе задачи завершаются успешно и в журнал попадает total=6

Когда первая задача возвращает список [1, 2, 3], TaskFlow может передать его следующей через XCom — механизм обмена небольшими сериализуемыми результатами между экземплярами задач. Документация XCom предупреждает, что большие таблицы и файлы следует хранить иначе. Для учебного списка из трёх чисел XCom подходит.

Если убрать выражение calculate_total(load_numbers()) из объявления DAG, обе декорированные функции останутся в файле, но не сформируют этот граф задач. Поэтому в проверке важны не только имена функций, но и созданные связи.

Запустите DAG и проверьте результат

После копирования first_dag.py оставьте первый терминал в виртуальном окружении и запустите:

airflow standalone

Дайте локальным компонентам завершить начальную настройку. Откройте в браузере http://localhost:8080 и войдите с локальными учётными данными. Не размещайте пароль в публичных логах и скриншотах.

Откройте второй терминал в том же рабочем каталоге, повторно активируйте окружение и установите тот же AIRFLOW_HOME:

source .venv/bin/activate
export AIRFLOW_HOME="$PWD/.airflow"
airflow dags list
airflow dags list-import-errors
airflow tasks list first_python_dag

В списке DAG должен появиться first_python_dag, ошибок импорта для него быть не должно. Вывод списка задач должен содержать load_numbers и calculate_total. Это ещё проверка обнаружения и структуры, не выполненный расчёт.

Успешная загрузка DAG подтверждает разбор определения, но ещё не подтверждает выполнение задач. Для одного тестового запуска используйте команду Airflow 3.3.2 с явным путём к файлу:

airflow dags test first_python_dag --dagfile-path "$AIRFLOW_HOME/dags/first_dag.py"

Параметр --dagfile-path и смысл airflow dags test зафиксированы в официальном справочнике CLI. Команда предназначена для одного тестового DagRun. В ожидаемом успешном случае обе задачи перейдут в состояние success, а в журнале calculate_total будет строка:

total=6

После успешного выполнения обе Task Instance имеют статус success, а журнал calculate_total содержит total=6. Это подтверждает не только загрузку определения, но и передачу списка между задачами во время DagRun.

После выполнения найдите DagRun и журнал в интерфейсе Airflow. Если используете отдельный ручной запуск через интерфейс, проверьте его собственные состояния и логи: результат тестовой команды и состояние другого запуска не следует смешивать. Названия, коды завершения и лог на своей машине фиксируйте из реального запуска, а не из ожидаемого текста руководства.

Сама арифметика проверяется независимо: сумма 1 + 2 + 3 равна 6. Но только работающий Airflow подтвердит, что задачи действительно выполнились в нужном порядке и результат был передан через XCom.

Если DAG не появился или завершился с ошибкой

В Airflow полезно искать проблему в том порядке, в котором проходит запуск: сначала программа должна найти DAG, затем разобрать его задачи и только после этого выполнить их.

DAG нет в списке. Проверьте AIRFLOW_HOME в обоих терминалах и выполните airflow config get-value core dags_folder. В этом каталоге должен лежать first_dag.py. Если файл на месте, посмотрите вывод airflow dags list-import-errors — он помогает отличить ошибку импорта от ошибки расположения файла.

Airflow не может импортировать модуль. При ModuleNotFoundError сначала проверьте, активировано ли виртуальное окружение: python --version и airflow version должны показывать выбранный Python и Airflow 3.3.2. Исправив окружение, снова выполните airflow dags list.

В DAG нет второй задачи. Вспомните разницу между определением функции и её вызовом. Одна только функция с @task ещё не образует шаг графа. Проверьте, что внутри first_python_dag осталась строка calculate_total(load_numbers()). Затем посмотрите список задач или граф в интерфейсе.

DAG загрузился, но результат не появился. Откройте журнал именно упавшей Task Instance. Если ошиблась функция, сообщение будет в её журнале; если проблема в служебной базе или запуске, проверьте терминал с airflow standalone. Не спешите удалять metadata DB: так вы потеряете сведения, которые помогают понять причину. В руководстве по отладке описаны требования к запуску и сериализации DAG.

После исправления повторите действия по порядку: обнаружение DAG, две задачи, тестовый запуск, их состояния и строка total=6. Одна лишь проверка синтаксиса командой python -m py_compile не говорит, что задачи действительно отработали.

Что вы получили

Теперь у вас есть первый DAG на Python: load_numbers возвращает три числа, а calculate_total получает их и печатает total=6. Вы знаете, где Airflow ищет файл, как появляются задачи из @task и почему их порядок определяется связью в графе.

Самая полезная привычка после этого упражнения — смотреть на конкретный запуск, а не только на исходный Python-файл. DAG может успешно загрузиться, но задача всё равно может упасть во время выполнения. Состояния задач и их журналы показывают, на каком шаге возникла проблема.

Для следующего опыта замените [1, 2, 3] чтением небольшого локального файла и сохраните результат в отдельный файл. При этом продумайте, что произойдёт при повторном запуске: не создаст ли он дубликаты и сможет ли безопасно начаться заново. Именно такие вопросы становятся важны, когда учебный DAG превращается в реальный процесс обработки данных. Для производственного окружения дополнительно понадобятся настройки безопасности, хранения состояния и исполнения задач.

Источники