airflow
[TOC]
1. Airflow 是如何部署的?为什么日志在宿主机上?
我们的 Airflow 是直接安装在 Mac 宿主机上的 Python 进程,不是容器。
Mac 宿主机
├── Python 进程: airflow webserver (PID=80064, 直接跑在 Mac 上)
├── Python 进程: airflow scheduler (PID=79987)
└── /usr/local/install/mini-datalake/airflow/ ← 直接是宿主机目录
├── logs/ ← 日志直接写到宿主机文件系统
├── airflow.db
└── dags/
所以日志在宿主机目录完全正常,因为进程本身就跑在宿主机上。
如果 Airflow 是容器部署的,日志有两种方案:
docker exec airflow-scheduler cat /opt/airflow/logs/...- 或者把日志目录 bind mount 到宿主机(像 MinIO 的 data 目录那样)
2. 任务业务逻辑跑在哪?是否支持分布式?
我们当前的架构:
Mac 宿主机
└── airflow scheduler (单进程,SequentialExecutor)
└── fork 子进程执行 task(也在宿主机上)
├── generate_and_produce ← 宿主机跑
├── consume_to_ods ← 宿主机跑
├── etl_to_dws ← 宿主机跑,但会 docker exec 到 K8s Spark Pod
├── index_to_elasticsearch ← 宿主机跑,HTTP 调用 K8s ES
└── index_to_milvus ← 宿主机跑,gRPC 调用 Docker Milvus
受限于单机,task 函数本身都在宿主机执行,资源受宿主机限制。
生产环境的分布式部署架构:
┌─────────────────────────────┐
│ Airflow Metadata DB │
│ (PostgreSQL/MySQL) │
└──────────────┬──────────────-─┘
│ 所有组件共享同一个 DB
┌────────────────────────┼────────────────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Webserver │ │ Scheduler │ │ Worker │
│ (Web UI) │ │ (调度决策) │ │ Worker │
└─────────────┘ └──────┬──────┘ │ Worker │
│ 发任务 └──────┬──────┘
┌─────▼──────┐ │ 实际执行 task
│ 消息队列 │ ───────────────►│
│ (Redis/ │
│ RabbitMQ) │
└────────────┘
| Executor | 说明 | 我们现在 |
|---|---|---|
SequentialExecutor |
单进程串行,仅用于本地测试 | ✅ 当前 |
LocalExecutor |
多进程并行,单机 | 需要 PostgreSQL |
CeleryExecutor |
多 Worker 节点,真正分布式 | 需要 Redis + 多台机器 |
KubernetesExecutor |
每个 task 启动一个 K8s Pod | 需要 K8s 集群 |
KubernetesExecutor 是最适合我们项目的生产方案——每个 task 动态创建一个 Pod,跑完销毁,天然分布式且资源隔离。我们项目后续可以升级到这个。
3. Airflow 为什么是离线数据处理调度的首选?
核心优势:
① DAG 即代码(Code as Configuration)
t1 >> t2 >> t3 >> [t4, t5] >> t6
依赖关系用 Python 代码描述,可版本控制、可 Code Review,比 cron + 脚本的依赖关系清晰得多。
② 丰富的重试/容错机制
default_args = {
"retries": 3,
"retry_delay": timedelta(minutes=5),
"retry_exponential_backoff": True,
"execution_timeout": timedelta(hours=1),
}
单个 task 失败自动重试,不影响其他 task,失败后从断点续跑(不用重跑整个流水线)。
③ 时间分区天然对齐数仓
# ds_nodash = '20260414',自动从上下文获取
batch_id = context["ds_nodash"]
# catchup=True 时会自动补跑历史分区
数仓按天/小时分区,Airflow 的调度周期和分区概念完美对齐,回填历史数据一个开关搞定。
④ 完整的可观测性
- Web UI 直观看每个 task 的状态、日志、耗时、甘特图
- 任务卡死 → heartbeat timeout 自动告警
- 历史运行对比 → 发现数据量异常
⑤ 庞大的 Provider 生态
airflow-providers-amazon → S3/EMR/Glue
airflow-providers-google → BigQuery/GCS/Dataflow
airflow-providers-apache-spark → SparkSubmitOperator
airflow-providers-elasticsearch
airflow-providers-slack → 失败通知
不用自己写对接代码,开箱即用。
⑥ 对比其他方案:
| 方案 | 缺点 |
|---|---|
| Linux cron | 无依赖管理,失败不告警,无 UI |
| 自研调度 | 重复造轮子,缺重试/回填/监控 |
| Spark Structured Streaming | 流处理,不适合批处理分区逻辑 |
| Flink | 同上,学习曲线陡 |
| Oozie/Azkaban | 老旧,社区不活跃 |
适合 Airflow 的典型场景(正好是我们的项目):
- 每日定时从 Kafka 读取数据写入 MinIO ODS
- 按
dt=YYYYMMDD分区跑 Spark ETL - 清洗完写入 ES/Milvus 索引
- 导出训练集
不适合 Airflow 的场景:
- 秒级实时流处理(用 Flink/Spark Streaming)
- 简单的单步定时任务(直接 cron)
- 纯 API 服务编排(用 Temporal/Conductor)
airflow分布式部署代码如何同步
好问题,这两个问题触及了 Airflow 生产部署的核心设计。
1. 分布式部署时代码如何同步?在哪里写代码?
你只在一个地方写代码——DAGs 目录,所有节点共享同一份。
生产上有三种同步方案:
方案 A:Git Sync(最主流)
开发者本地
│ git push
▼
Git 仓库 (GitHub/GitLab)
│
├── Worker 节点 1: git-sync sidecar 容器每 30s pull
├── Worker 节点 2: git-sync sidecar 容器每 30s pull
├── Worker 节点 3: git-sync sidecar 容器每 30s pull
└── Scheduler: git-sync sidecar 容器每 30s pull
每个节点跑一个 git-sync 的 sidecar 容器,定时从 Git 仓库拉取 DAG 文件到本地 /dags 目录。你只需要 push 到 Git,所有节点自动同步。
K8s 部署时的 Pod 配置长这样:
containers:
- name: airflow-scheduler
image: apache/airflow:2.10
volumeMounts:
- name: dags
mountPath: /opt/airflow/dags
- name: git-sync # sidecar
image: registry.k8s.io/git-sync
env:
- name: GITSYNC_REPO
value: "https://github.com/yourorg/dags.git"
- name: GITSYNC_PERIOD
value: "30s"
volumeMounts:
- name: dags
mountPath: /git # 写到同一个共享 volume
volumes:
- name: dags
emptyDir: {} # scheduler 和 git-sync 共享
方案 B:共享文件系统(NFS / EFS)
所有节点挂载同一个 NFS/EFS 目录
/mnt/airflow/dags/ ← 你在这里写文件,所有节点实时看到
简单粗暴,延迟低,但 NFS 有单点故障风险。
方案 C:KubernetesExecutor(每个 task 是独立 Pod)
Scheduler (有 DAG 代码)
│ 触发 task
▼
动态创建 Pod (从镜像启动,DAG 代码已打包进镜像)
│ 跑完销毁
DAG 代码直接打包进 Docker 镜像,没有同步问题,但每次改代码需要重新 build 镜像。
实际工作流(Git Sync 方案):
1. 本地开发: vim dags/my_pipeline.py
2. 测试: airflow dags test my_pipeline
3. 提交: git commit && git push
4. 生效: 30 秒内所有节点自动同步
2. 本机为什么不用容器部署?
原因有三,但本质是:本地学习环境,容器部署 Airflow 的收益不抵成本。
原因 1:安装时 Airflow 已经在宿主机上了
pip3 install apache-airflow # 你最初装的时候就直接装宿主机了
当时我们看到已安装就直接用了,再容器化是额外工作量。
原因 2:容器化 Airflow 在本机有额外复杂度
如果用容器部署,需要解决:
问题 1: DAG 目录挂载
OrbStack 不支持单文件 bind mount(我们已踩过这个坑)
需要把整个 dags/ 目录 bind mount 进容器
问题 2: submit_etl.sh 需要在容器内 docker exec 进 Spark 容器
Airflow 容器内 → docker exec → Spark 容器
需要把 /var/run/docker.sock 挂载进 Airflow 容器(Docker in Docker)
这在 OrbStack 上路径不标准
问题 3: 网络互通
Airflow 容器需要访问 datalake bridge network(MinIO/Milvus)
又需要访问 K8s NodePort(ES/Spark)
需要额外的 network 配置
原因 3:宿主机部署在本地学习上反而更透明
| 宿主机进程 | 容器 | |
|---|---|---|
| 日志查看 | 直接 tail -f logs/ |
docker logs 或挂载 |
| 调试 | 直接 python3 dags/xxx.py |
需进入容器 |
| 文件编辑 | 直接编辑 | bind mount 或 docker cp |
| fork 问题排查 | ps aux 直接看 |
需 docker exec ps |
| 学习成本 | 低 | 高(还没到学这个的时候) |
生产环境一定要容器化,标准的生产 Airflow K8s 部署是:
# Helm chart: apache-airflow/airflow
helm install airflow apache-airflow/airflow \
--set executor=KubernetesExecutor \
--set dags.gitSync.enabled=true \
--set dags.gitSync.repo=https://github.com/yourorg/dags
一条命令部署好 Scheduler + Webserver + GitSync + PostgreSQL,所有组件都是 K8s Pod,这才是生产标准。我们这个项目后续可以作为 Phase 5 来实践。

浙公网安备 33010602011771号