Airflow 几乎是目前最主流的工作流调度平台,但说起安装和部署,很多人第一步就被 Python 依赖和数据库配置劝退了。我也一样,最早在一台 CentOS 上手动装 Airflow,Python 3.7 和 3.9 的虚拟环境、apache-airflow和一堆 provider 包的版本冲突,搞得我差点直接放弃。后来我彻底转向 Docker 方案,把整个 Airflow 塞进容器里跑,环境问题基本一劳永逸。这篇文章会把我在 Docker 中运行 Airflow 的完整过程写清楚:为什么这样做、docker-compose.yml 怎么拆解、启动命令怎么执行、之后又踩了哪些坑,适合第一次接触 Airflow 又不想被环境问题劝退的朋友参考。不管你用的是 Windows、macOS 还是 Linux,只要机器上先装好 Docker,下面的步骤基本能照搬;如果你已经在用 Kubernetes,也可以从这里理解单机 Docker 方案的边界。文章里不会有高大上的架构图,只有我踩过的坑和验证过的操作。
1. 为什么非要把 Airflow 塞进 Docker
1.1 依赖地狱:一个 Python 环境装不起来 Airflow
Airflow 本质是一个 Python 应用,但它的依赖复杂程度远超普通 Python 项目。它要连接数据库、处理调度、执行任务,所以会拉入 SQLAlchemy、Celery、croniter、jinja2 等一系列底层库;而你实际跑数仓任务时,又得装apache-airflow-providers-*这类 provider 包。这些包对 Airflow 核心版本有严格限制,经常出现 pip 解析依赖时列出几百行冲突信息的情况。
我在一台干净的 CentOS 上手动装apache-airflow[celery]==2.5.1时,和apache-airflow-providers-http的版本互相冲突,pip 提示需要 downgrade 某个第三方库,而那个库又被另一个包锁定。当时我花了一个下午在虚拟环境里反复重装,最后不得不删掉整个虚拟环境从零开始。这种经历,我相信不只我一个人有。
更麻烦的是,Airflow 的元数据库也需要选型。默认 SQLite 只适合本地随便看一眼,一旦 scheduler 和 webserver 同时读写,就会出现数据库锁定。生产环境要用 PostgreSQL 或 MySQL,而手动装数据库、初始化用户、配置连接串,又是一连串的坑。把这些全塞进 Docker 之后,数据库、Redis、Airflow 各组件的版本和配置都由镜像和 Compose 文件固定下来,问题一下子清爽了不少。
1.2 Docker 带来的三个实打实的好处
第一是环境一致性。以前团队里三个人跑同一套 Airflow,一个人的 Python 是 3.8,另一个人是 3.10,跑出来的 DAG 行为可能都不一样。改成 Docker 后,docker compose up -d拉起来的就是同一套镜像、同一个 Compose 配置,不会再出现"在我电脑上能跑,在你电脑上不行"这种鬼话。
第二是快速重置。学习阶段会频繁改配置、装 provider、坏掉数据。手动卸载重装需要很久,而 Docker 只需要删掉容器和数据卷就能回到干净状态。我在第一次接触 Airflow 时,至少重置过十次环境,每次都靠docker compose down -v一键清理,这让我敢大胆折腾。
第三是升级安全。Airflow 的版本升级不是小事情,元数据库需要执行 migration。在 Docker 方案里,我只需要改镜像 tag,重新执行初始化,观察迁移日志即可;出了问题就回退 tag,恢复备份。相比在物理机上升级,心理压力小得多。
1.3 官方镜像也不是银弹
很多人以为用了官方apache/airflow镜像就万事大吉,实际上你仍然需要做几个关键决策。
- 选择 Executor。默认
SequentialExecutor只能跑一个任务;LocalExecutor适合单机多任务;CeleryExecutor需要额外部署 Redis 和 worker。这个决策直接影响你要启动几个容器。 - 选择元数据库。官方 Compose 使用 PostgreSQL,但你也可以换成 MySQL;不同数据库的初始化方式有差异。
- 确定 dags 目录如何挂载。如果不把宿主机的 DAG 文件挂进容器,scheduler 根本看不到你的任务定义。
- 考虑是否加载官方示例 DAG。默认示例会占 UI 空间,也容易被新手误触发,生产环境建议关闭。
所以说,Docker 解决的是"环境一致",不解决"架构设计"。很多新手把这两件事混为一谈,结果 Docker 跑起来了,却不知道该启动哪些服务,最后还是一脸懵。
2. 环境准备:镜像源、Docker Desktop 和那台能跑起来的机器
2.1 Docker 版本与运行模式:优先 Linux 容器
我建议使用 Docker Engine 20.10 以上,或者 Docker Desktop 4.x 以上,这样 Compose v2 已经内置,直接用docker compose命令,不用再单独装docker-compose这个 Python 包。
在 Windows 上有一个非常容易踩的坑:Docker Desktop 默认可以切 Linux 容器和 Windows 容器,Airflow 镜像和绝大多数的镜像仓库里都是 Linux 镜像,所以你必须在 Linux 容器模式下运行。如果切到 Windows 容器模式,拉取镜像会直接失败。
另外,Windows 上 Docker Desktop 依赖 WSL2 作为后端。如果你是第一次装,常见错误是启动时提示类似virtualization support not detected或者 Docker Desktop 一直卡在 starting。这种现象九成是 BIOS 虚拟化没开,或者 Windows 的"虚拟机平台""适用于 Linux 的 Windows 子系统"功能没有启用。排查链路我在后面专门写一节,这里先记住:不要用 Hyper-V 旧模式,直接用 WSL2。
2.2 镜像下载慢:镜像加速器这步别跳过
从 Docker Hub 拉取apache/airflow这种大镜像,网络情况不好的时候非常痛苦,动不动就超时断连,而且断点续传体验很差。我第一次拉apache/airflow:2.8.1,那个镜像近 1GB,断了好几次,反复重试浪费了不少时间。
解决办法是在 Docker Desktop 的 Docker Engine 配置里加registry-mirrors,也就是给 Docker daemon 配置镜像加速地址。修改完配置后,Docker 会要求重启 daemon,重启后再执行docker pull hello-world验证拉取速度是否正常。
需要注意一点:网上能搜到的镜像加速地址五花八门,有的已经不可用,有的只对特定网络环境有效。配置后如果拉取还是超时,建议换一组地址,不要在一个上面死磕。企业内网环境的话,最好在内网自建一个 registry mirror,下载速度可以做到秒拉。
2.3 给 Airflow 的内存与 CPU 预算
官方 Compose 文件默认会启动至少 6 个服务:airflow-init、scheduler、webserver、worker、triggerer、flower,再加上 postgres 和 redis。听起来不多,但实际内存占用不低。
我自己实测过,空跑状态下整套环境稳定占 2 到 3 GB 内存。如果你的 DAG 任务里有 pandas、numpy 这类重量级依赖,worker 执行任务时内存还会往上蹿。所以 Docker Desktop 的资源设置里,内存建议至少给到 4GB,最好直接给 6 到 8GB。
如果内存给得太小,你很可能遇到一个经典现象:容器不断重启,docker compose ps显示状态是Restarting,日志里出现Killed字样。这就是 OOM,系统内存不足直接把 worker 进程杀掉了。
在 WSL2 模式下,Docker Desktop 会使用 WSL 的虚拟内存配置。如果你在用户目录下创建了.wslconfig文件来限制内存,记得别把上限设得太低,否则 Docker 虚拟机吃不到足够内存,问题更隐蔽。我建议至少留 3GB 给 WSL,全部资源够的话给 6GB 更稳妥。
3. docker-compose.yml 逐段拆解:每个服务到底是干嘛的
官方提供的 docker-compose.yaml 对新手来说字段太多,很多人直接docker compose up -d,然后发现 init 容器退出了、webserver 起不来。所以这一节我把每个服务拆开讲,搞清楚之后再启动,心里就有底了。
3.1 airflow-init:初始化数据库和账号
官方 Compose 里的第一个服务是 airflow-init,它执行的是两步核心操作:airflow db migrate(旧版本叫airflow db init)和创建初始用户。
控制它行为的是两个环境变量:_AIRFLOW_DB_MIGRATE和_AIRFLOW_WWW_USER_CREATE。当它们设置为 true 时,init 容器启动后会自动迁移数据库结构,并创建 UI 登录账号,默认账号密码是airflow / airflow。
注意,init 容器执行完就会退出,退出码是 0,这是正常现象,不是报错。如果你看到 init 容器一直在重启,多半是 PostgreSQL 还没就绪,或者数据库连接串配错了。官方 Compose 里已经加了 healthcheck 和 depends_on 条件,正常不会出问题;但如果你自己简化过 Compose,就有可能出现 init 在 postgres 还没起来时就去连库的情况。
3.2 webserver 和 scheduler:一对必须同时存活的双胞胎
webserver 提供 Web UI,默认监听 8080 端口,负责展示 DAG、触发任务、查看运行记录。scheduler 是 Airflow 真正的心脏,它周期性地扫描 dags 目录,解析 Python 文件,生成 DagRun 和 TaskInstance,然后把任务交给 executor 去执行。
新手最容易出现的情况是:只看到 webserver 起来了,浏览器能登录,但 DAG 列表一片空白。排查到最后发现 scheduler 容器一直没起来,或者起来了但因为日志目录没有写权限直接退出。这两个服务缺一不可,必须同时存活。
它们使用同一个 airflow 镜像,只是启动命令不同:webserver 执行airflow webserver,scheduler 执行airflow scheduler。在 Compose 里它们往往除了 command 和端口映射以外配置几乎一致,这也是官方 Compose 文件冗长的原因之一。
3.3 postgres 与 redis:元数据库与消息代理的选型
Airflow 的元数据库保存 DAG 定义、任务实例状态、连接信息、变量、权限等所有关键数据。官方 Compose 默认使用 PostgreSQL 13,这是比较稳妥的选择。
为什么不建议用默认 SQLite?因为 SQLite 不支持并发写入。scheduler 和 webserver 同时在写数据库时,SQLite 会锁库,导致任务状态更新失败。SQLite 只适合安装后第一分钟浏览一下界面,一旦要跑真实任务,就赶紧换成 PostgreSQL。
Redis 只有在CeleryExecutor下才需要,它作为 Celery 的消息代理,scheduler 把任务消息发到 Redis,worker 从 Redis 里取任务执行。如果你用的是LocalExecutor,其实不需要 Redis;但官方 Compose 默认就是 CeleryExecutor,所以会把 Redis 一起启动。如果你不想跑 worker 和 Redis,可以自己改成 LocalExecutor,后面我会给一个精简版配置。
3.4 airflow-worker 与 flower:CeleryExecutor 下的执行与监控
worker 容器运行airflow celery worker,真正执行任务的进程在这里。你触发的每个任务,最终都会由某个 worker 进程去执行,任务日志也会由 worker 写到共享的 logs 目录里。
flower 是 Celery 的监控界面,默认端口 5555。它可以看到 worker 数量、队列名称、任务消费情况等。调试阶段我经常打开 flower 看 worker 是否正常连接 Redis,如果 worker 一直显示 offline,说明它和 Redis 之间有问题。
如果你确定用不到分布式执行,可以把 worker 和 flower 两个服务从 Compose 里删掉,改用 LocalExecutor。减少服务数量意味着更少的内存占用、更少的故障点,单机场景下我更推荐这样干。但要注意,官方示例 DAG 里有些任务设置了queue属性,没有 worker 就跑不起来。
3.5 共享卷:dags、logs、plugins 三个目录的作用
./dags:/opt/airflow/dags:你的 DAG Python 文件放在这里,scheduler 和 worker 才能看到。修改 DAG 后一般不需要重启容器,scheduler 会按照配置定期扫描这个目录。./logs:/opt/airflow/logs:所有任务日志、调度日志都写在这里。如果不挂载,容器一删日志就没了,排查历史任务失败时非常被动。./plugins:/opt/airflow/plugins:存放自定义插件,比如自定义 Operator、Hook。不是每个项目都需要,但建议保留这个挂载点。
这三个目录都是 bind mount,也就是宿主机目录直接映射进容器。开发时非常方便,改完代码立刻生效。但到了生产环境,我更建议把 dags 代码构建进镜像,而不是依赖宿主机目录,这个后面专门讲。logs 目录和 postgres 数据卷则要考虑持久化和备份。
4. 完整启动流程与"跑起来"之后的验证
4.1 第一阶段:初始化
我习惯先把官方 Compose 文件下载下来,保证内容和当前 Airflow 版本匹配:
curl -LfO https://airflow.apache.org/docs/apache-airflow/stable/docker-compose.yaml然后创建.env文件,写入当前用户 UID,避免后续挂载目录权限问题:
echo -e "AIRFLOW_UID=$(id -u)" > .env第一次启动时,先单独执行初始化:
docker compose up airflow-init这个命令会启动 postgres、redis 和 init 容器。观察日志输出,如果出现类似Admin user airflow created、DB migrated的提示,说明数据库和账号都初始化好了。执行完成后 init 容器退出,状态码为 0,这是正常的。
如果之前已经初始化过,再次执行 init 时它会提示数据库已是最新,不会重复创建账号。这个幂等性设计对后面的版本升级很有帮助。
4.2 第二阶段:启动全部服务
初始化完成后,启动整套环境:
docker compose up -d然后用docker compose ps查看容器状态。正常情况下,postgres、redis、scheduler、webserver、worker 都应该是 Up 状态,init 是 Exited (0)。
第一次启动时 webserver 和 scheduler 会慢一些,因为它们要连接数据库、加载 provider 包。这段时间不要频繁重启容器,耐心等一两分钟再看。如果某个容器一直显示 Restarting,就需要看具体日志定位问题,排查思路我放在下一节。
4.3 第一次登录:账号、密码和 UI 界面检查
浏览器访问http://localhost:8080,账号密码是airflow / airflow。这是 init 容器通过环境变量创建的,生产环境务必第一时间在 UI 里改掉,或者初始化时通过环境变量指定强密码。
进入 UI 后,你会看到 DAG 列表。如果官方示例 DAG 没有关闭,列表里会有一堆example_bash_operator、example_python_operator之类的示例。验证的关键点有三个:
- DAG 列表能正常加载,说明 webserver 和数据库通信正常。
- 点击某个 DAG,能看到调度日历,说明 scheduler 已经解析了这个 DAG。
- 点进日志目录,能打开对应日志文件,说明 logs 挂载目录有写权限。
如果 DAG 列表空白,先看 scheduler 容器日志,再检查 dags 目录的挂载和权限。这两个原因占了 90% 的情况。
4.4 用官方示例 DAG 验证调度链路
我一般用example_bash_operator来做冒烟测试。进入 DAG 详情页,点击右上角的触发按钮,手动运行一次,然后观察 Task Instance 的状态变化。
正常链路是:queued -> running -> success。
- 卡在 queued:说明 scheduler 已经接受了任务,但 worker 没有及时领取。检查 worker 容器是否存活,以及 Redis 连接是否正常。
- 状态是 running 但一直不结束:查看 worker 容器日志,可能是任务本身卡住了,也可能 worker 内存不足。
- 日志里出现
Task exited with return code Negsignal.SIGKILL:通常是内存不够,worker 进程被系统 OOM Killer 杀掉。加内存或者降低并发度。
这步验证很重要。很多人看到 UI 能登录就觉得成功了大半,结果一跑任务就发现排队排死,最后才回头查 worker 和 Redis,反而耽误时间。
5. 实际操作里的常见坑:从 Windows 到生产环境的排查清单
5.1 Docker Desktop 启动失败:虚拟化和 Docker Engine 的排查链路
网上经常见到 Docker Desktop 启动失败的提问,我也在 Windows 上被折磨过。最典型的错误有两条。
第一条是virtualization support not detected。排查顺序是:
- 打开任务管理器,切到"性能"标签,看 CPU 一栏的"虚拟化"是否显示"已启用"。
- 如果显示"已禁用"或"不支持",重启电脑进 BIOS,打开 Intel VT-x 或 AMD-V。
- 在 Windows 的"启用或关闭 Windows 功能"里,勾选"虚拟机平台"和"适用于 Linux 的 Windows 子系统"。
- 安装 WSL2,执行
wsl --install,然后wsl --set-default-version 2。 - 重启 Docker Desktop。
第二条是Failed to connect to the Docker API at npipe:////./pipe/docker_engine。看到这个先别急着重装 Docker,它通常只是 Docker Engine 没起来。检查系统托盘里 Docker 图标是否处于运行状态,然后执行docker version。如果 client 有返回而 server 没有,说明 daemon 本身起不来,去看 WSL 发行版是否正常,或者 Docker Desktop 的日志文件。
这两个坑都不涉及业务逻辑,但能把人磨得没脾气。记住一个原则:先确认 Docker 本身能跑,再去折腾 Airflow。
5.2 容器疯狂重启 / DB 连不上
如果你在 airflow-init 或 scheduler 的日志里看到类似psycopg2.OperationalError: connection refused,说明应用连不上 PostgreSQL。很多人第一反应是数据库挂掉了,但真正原因可能是应用启动得太早,postgres 还没 ready。
官方 Compose 已经处理了这个问题:postgres 服务配置了 healthcheck,airflow-init 等服务的depends_on里明确写了condition: service_healthy。也就是说,postgres 必须通过健康检查后,依赖它的服务才会启动。
如果你自己改写了 Compose,把depends_on的 condition 去掉,就可能出现随机性的连接失败。所以排查时先看:
docker compose logs postgres确认 postgres 有没有正常完成初始化。再检查健康状态:
docker inspect --format='{{json .State.Health}}' <postgres容器名>看到"status":"healthy"才说明数据库真的 ready。还有一种情况是 postgres 数据卷损坏,或从旧版本升级导致数据目录不兼容,遇到这种我会先备份,然后删掉旧卷重建。这里提醒一句:没有备份就别轻易删卷,特别是已经存过真实任务状态的库。
5.3 权限问题:AIRFLOW_UID 导致的 Permission denied
把 DAG 文件放进宿主机./dags目录后,如果 scheduler 日志出现PermissionError: [Errno 13] Permission denied,大概率是 UID 不匹配。
Airflow 官方镜像默认以 airflow 用户运行,这个用户的 UID 通常是 50000。而你宿主机当前用户的 UID 不一定是 50000,于是 bind mount 的目录 owner 对不上,容器内用户自然没有写权限。
解决方法是把宿主机 UID 传给 Compose。在.env文件里加一行:
AIRFLOW_UID=1000Linux 上可以直接用命令生成:
echo -e "AIRFLOW_UID=$(id -u)" > .env改完.env后,需要重建容器:
docker compose down docker compose up -d如果实在不想设置 UID,也可以临时代用chmod -R 777 dags logs plugins解决,但这样所有用户都能写这几个目录,只适合本机测试环境。
5.4 时区问题:schedule_interval 为什么总对不上
我遇到过最迷惑的坑就是时区。DAG 里写的 schedule 是每天 8 点,结果任务是下午 4 点触发的,怎么看都不对。
Airflow 默认使用 UTC 时区,cron 表达式按 UTC 解析。如果你在北京时间 UTC+8 的时区,那么 UTC 8 点就是北京时间 16 点,正好差了 8 个小时。
修正方式分两步。第一步,在 Compose 的环境变量里设置 Airflow 默认时区:
environment: - AIRFLOW__CORE__DEFAULT_TIMEZONE=Asia/Shanghai - AIRFLOW__CORE__DEFAULT_UI_TIMEZONE=Asia/Shanghai第二步,在 DAG 定义里显式指定带时区的 start_date:
from datetime import datetime from zoneinfo import ZoneInfo start_date=datetime(2024, 1, 1, tzinfo=ZoneInfo("Asia/Shanghai"))要注意,修改时区不会自动重算已经生成的调度记录。已经产生的 DagRun 还是按旧时区记录,如果你希望全部按新时区显示,需要清掉历史调度记录重新来。这条经验是我在生产环境改造时踩出来的,改之前确实忘了还有历史数据这回事。
5.5 端口冲突:8080 被占用怎么办
启动时如果提示port is already allocated,说明 8080 端口已经被其他进程占用。这个很常见,尤其是机器上还跑着别的 Java 应用或轻量 Web 服务。
排查命令视系统不同而定:
# Linux / macOS lsof -i:8080 # Windows netstat -ano | findstr :8080找到占用进程后,要么停掉它,要么改掉 Airflow 的端口映射。修改方法是在 Compose 文件里把 webserver 的 ports 改成别的宿主机端口:
ports: - "8081:8080"改完访问http://localhost:8081即可。第 4 节的验证流程也相应改一下。
6. 从开发到部署:Docker 跑 Airflow 的进阶经验
6.1 让 DAG 代码脱离宿主机依赖
bind mount 很方便,但它也让环境多了一个隐形的宿主机依赖。你在这台机器上挂载了./dags,换一台机器呢?新同事 clone 代码后如果忘了挂载目录,scheduler 看到的就是空目录。
更规范的做法是把 DAG 代码和依赖打包进自定义镜像。先写一个扩展官方镜像的 Dockerfile:
FROM apache/airflow:2.8.1 COPY dags/ /opt/airflow/dags/ COPY requirements.txt /opt/airflow/ RUN pip install --no-cache-dir -r requirements.txt然后构建镜像:
docker build -t my-airflow:1.0.0 .这样整个团队拉镜像就能跑,不需要关心宿主机上有什么文件。DAG 代码的版本也能通过镜像 tag 管理,上线、回滚都更可控。开发阶段可以继续用 bind mount 热更新,但到了 CI/CD 或生产环境,我推荐镜像化。
6.2 生产环境的 Executor 选择与资源伸缩思路
单机 Docker 场景下,LocalExecutor往往是最省心的选择。它不需要 Redis,不需要 worker,scheduler 直接在本地进程里执行任务。任务量不大的话,运维成本比 CeleryExecutor 低很多。
使用LocalExecutor的精简 Compose 可以去掉 redis、worker、flower 三个服务,核心服务变成:
services: airflow-webserver: image: apache/airflow:2.8.1 command: airflow webserver environment: - AIRFLOW__CORE__EXECUTOR=LocalExecutor - AIRFLOW__DATABASE__SQL_ALCHEMY_CONN=postgresql+psycopg2://airflow:airflow@postgres/airflow ports: - "8080:8080" volumes: - ./dags:/opt/airflow/dags - ./logs:/opt/airflow/logs depends_on: postgres: condition: service_healthy等到需要横向扩容、任务量明显增多时,再切换CeleryExecutor。两者的对比如下:
| Executor | 适用场景 | 并发能力 | 依赖组件 | 运维复杂度 |
|---|---|---|---|---|
| SequentialExecutor | 本地代码演示 | 极低,单进程串行 | 无 | 最低 |
| LocalExecutor | 单机生产,任务量中等 | 高,进程级并行 | 数据库 | 低 |
| CeleryExecutor | 多机集群,任务量大 | 高,可横向扩展 | 数据库 + Redis + worker | 中 |
| KubernetesExecutor | K8s 集群,动态扩缩容 | 高,Pod 级隔离 | K8s 集群 | 高 |
不要一上来就上 CeleryExecutor,先想清楚你的任务量和团队运维能力。很多人跑着两台主机的 Celery 集群,实际任务量一台 LocalExecutor 就够了,还白白多了 Redis 和 flower 两个故障点。
6.3 更新 Airflow 版本时的升级与回退流程
直接改镜像 tag 然后docker compose up -d是升级的最大忌讳。Airflow 的元数据库结构会随版本变化,升级必须走 migration,而且 migration 失败了还能回退才算安全。
我的操作流程是:
- 备份 postgres 数据卷。
- 修改 Compose 里的镜像版本 tag。
- 执行
docker compose up airflow-init,观察迁移日志。 - 如果迁移成功,启动全部服务,验证 DAG 解析和任务执行。
- 如果迁移失败,恢复备份,把 tag 改回去,重新启动旧版本。
备份数据卷的命令可以参考:
docker run --rm \ -v <项目名>_postgres_data:/data \ -v $(pwd):/backup \ alpine tar czf /backup/pg_backup.tar.gz -C /data .恢复时反向解包即可。对生产环境,建议每个季度做一次备份恢复演练,确认备份真的能用。这不是吓唬人,我见过太多备份文件存在,但恢复时才发现缺了关键文件的情况。
6.4 日志持久化与元数据库备份
logs 目录是排查问题的第一现场,容器重建后日志丢失会让历史问题无从查起。开发环境无所谓,生产环境一定要把 logs 挂到持久存储上,并定期归档。
postgres 数据卷是整个 Airflow 的核心资产,里面存了 DAG 状态、连接信息、变量和运行历史。备份时要注意连接串里的密码不要明文出现在命令行历史里,建议把备份脚本和连接信息通过环境变量注入。定时备份可以用 cron 或 Airflow 自己的调度任务实现,但不要用docker exec硬连数据库,最好用pg_dump并生成带时间戳的压缩文件。
最后:几个我自己离不开的小技巧
最后分享几个实际用得上的操作习惯。第一个,想快速确认容器内 Airflow 版本和环境变量,不需要翻日志,直接执行:
docker compose exec webserver airflow info第二个,如果你用 VS Code 写 DAG,强烈建议装 Dev Containers 插件,直接 attach 进 webserver 或 scheduler 容器,这样写代码时有 Python 语法提示和 lint,比在宿主机写完再挂载进去体验好很多。第三个,连接信息、数据库密码这类敏感配置,用 Airflow 的 Connections 和 Variables 管理,不要硬编码在 DAG 文件里。第四个,日志量大之后,配置AIRFLOW__LOGGING__REMOTE_LOGGING把日志投递到对象存储,排查问题时不用一台台翻容器。
这些经验是我从第一次被依赖地狱逼疯,到现在能比较从容地维护 Docker 化 Airflow 环境过程中沉淀下来的。踩过坑才知道,环境问题看似繁琐,但只要理解了镜像、容器、挂载、权限这几件事,整套系统就会变得非常可控。希望这篇记录能让你少走一些弯路。