Apache Airflow 完整实战指南:用 Breeze 在本地复现 CI 作业
【免费下载链接】airflowApache Airflow - A platform to programmatically author, schedule, and monitor workflows项目地址: https://gitcode.com/GitHub_Trending/ai/airflow
凌晨两点,CI 红了。你打开日志,只有两行报错:任务退出码非零,堆栈断在半截。重跑?大概率还是红。翻历史?上一版约束文件已经变了。这种时候最缺的不是运气,而是一条能在本地复现 CI 的路。Airflow 的答案是Breeze:一条命令,把 CI 里那个容器原样搬到你的机器上。
Breeze 是什么:三句话说清
Breeze是 Airflow 官方的 Python 封装器,底层全是 docker 命令。它解决一个问题:CI 环境和开发机环境天然不一致,而 Airflow 的 CI 哲学恰恰相反——无论测试与集成基础设施多复杂,任何一条失败的检查都必须能在本地重放。所有 CI 作业本身就是一条条breeze命令,所以复现 CI 不需要魔法,只需要跑同一串命令。这套设计哲学贯穿后文三条路径:镜像、参数、环境变量,全部与 CI 同源。
三条复现路径怎么选
三条路径对应三种成本:load保真度最高但需要 Token,build最灵活但可能漂移,常规breeze最省事但要求你已检出 PR 分支。先看完三条路,再对照表格做选择。
路径 A:按 Run ID 拉取 CI 产出的镜像
适用场景:你只想原封不动地进入失败那次运行的现场,不关心本地代码。
breeze ci-image load --from-run会下载该次 GitHub Actions 运行产出的镜像工件并加载到本地:
breeze ci-image load --from-run 12538475388 --python 3.11 --github-token <token>运行成功后,本地多了一张与 CI 完全相同的镜像。Run ID 在 Actions 运行列表里直接可见;如果只有 PR 号没有 Run ID,把--from-run换成--from-pr 12345即可。
加载后进入容器,关键是--mount-sources skip:不挂载本地源码,容器里呈现的就是 CI 运行时的原始内容:
breeze shell --mount-sources skip [OPTIONS][OPTIONS] 照抄 CI 日志中该作业的 flag 与环境变量。进入后你就可以交互式地重跑失败测试,无需检出失败 PR 的源码。
但这里有个坑:该功能目前仅支持 AMD 架构机器,ARM 架构(如 Apple M 系列)拉下来也无法运行,文档注明这一点即将改变。另一个限制是必须提供--github-token,缺了会直接报错退出(见ci_image_commands.py#L608-L613)。
路径 B:本地构建 CI 镜像
适用场景:镜像工件拉不到(过期、无 Token、ARM 机器),但你手里有对应的分支代码。
检出失败 PR 的分支,然后:
breeze ci-image build产出的是"当前时刻"的镜像。与路径 A 的区别全在依赖解析上:build 在你运行这一刻重新拉取 PyPI 上的包,而 Airflow 每天发布大量包,CI 构建时锁定的版本与你构建时的版本很可能不同。constraints 文件一旦更新,差异更明显。
canary 构建还有一层陷阱:部分 PR 与 canary 构建使用--upgrade-to-newer-dependencies(对应UPGRADE_TO_NEWER_DEPENDENCIES=true),构建时完全不用 constraints 文件。你要重建这类镜像,必须同样传入这个 flag,否则装出来的依赖集和 CI 不是一回事。相关控制项还有--airflow-constraints-location、--airflow-constraints-mode-ci、--platform(多平台构建传列表)、--push、--docker-cache,完整清单见 dev/breeze/doc/ci/02_images.md。
但正因依赖随时间漂移,本地 build 出的镜像可能和 CI 那张不一样,文档也明确说ci-image load才是更可靠的复现方式。build 是退路,不是首选。
路径 C:检出分支后直接跑常规 breeze 命令
适用场景:你要边调试边改代码,需要 IDE 参与。
检出了 PR 分支之后,日常开发用的breeze命令就能直接复现 CI 环境,无需重建镜像——即使 CI 用了新发布的依赖也一样。你可以像平时开发 Airflow 那样编辑本地文件,保存后容器内立即生效。
breeze test-quality-gate [OPTIONS][OPTIONS] 的取值规则不变:照抄 CI 作业日志里的 flag 和环境变量。但注意,这条路径下你的源码是"活的":CI 运行时的那个精确 commit 与本地工作区内容可能已经分叉,你要保证工作区与失败 commit 对齐(git checkout <sha>),否则复现出来的可能是新问题而不是老问题。
四维对比
| 维度 | 路径 A:load 镜像 | 路径 B:build 镜像 | 路径 C:检出分支跑 breeze |
|---|---|---|---|
| 保真度 | 最高,逐字节复用 CI 工件 | 依赖会随时间漂移 | 环境同源,源码以本地为准 |
| 是否需要检出源码 | 不需要 | 需要 | 必须 |
| 是否需要 GitHub Token | 需要--github-token | 不需要 | 不需要 |
| 适用架构 | 目前仅 AMD | 任意(本地构建) | 任意 |
选项与环境变量速查
CI 作业传给breeze的配置分两类:--flags和环境变量,复现时两者都要看。以下按 dev/breeze/doc/ci/07_running_ci_locally.md 分组,本地运行时可用环境变量,也可以转成breeze shell的命令行 flag。
基础变量:控制 breeze 基本行为
| 变量名 | 对应 CLI 选项 | 本地默认 | CI 默认 | 一句话说明 |
|---|---|---|---|---|
PYTHON_MAJOR_MINOR_VERSION | --python | 使用的 Python 主/次版本 | ||
BACKEND | --backend | 测试使用的后端数据库 | ||
INTEGRATION | --integration | 测试使用的集成组件 | ||
DB_RESET | --db-reset/--no-db-reset | false | true | 容器入口是否重置数据库 |
ANSWER | --answer | yes | 是否自动应答交互提问 |
测试变量:控制测试执行范围
| 变量名 | 对应 CLI 选项 | 本地默认 | CI 默认 | 一句话说明 |
|---|---|---|---|---|
RUN_DB_TESTS_ONLY | --run-db-tests-only | 数据库测试作业中为 true | 只跑数据库测试 | |
SKIP_DB_TESTS | --skip-db-tests | 非数据库测试作业中为 true | 跳过数据库测试 |
容器初始化与主机变量:决定"环境长什么样"
容器初始化变量决定容器内的环境准备;主机与 GIT 变量由 Breeze 在本地运行时自动填充,跨环境复现时可手动覆盖。
| 变量名 | 对应 CLI 选项 | 本地默认 | CI 默认 | 一句话说明 |
|---|---|---|---|---|
MOUNT_SOURCES | --mount-sources | skip | 是否把本地源码挂载进容器 | |
SKIP_ENVIRONMENT_INITIALIZATION | --skip-environment-initialization | false(prek hooks 中为 true) | 同左 | 跳过测试环境初始化 |
SKIP_IMAGE_UPGRADE_CHECK | --skip-image-upgrade-check | false(prek hooks 中为 true) | 同左 | 跳过镜像升级检查 |
SKIP_SSH_SETUP | 无(仅环境变量) | false | CodeSpaces 中为 true | 跳过为测试配置 SSH 服务器 |
VERBOSE | --verbose | false | true | 打印内部命令的详细信息 |
HOST_USER_ID/HOST_GROUP_ID | 无 | 宿主机 UID / GID | 宿主机用户的 ID,保证文件权限 | |
HOST_OS | 无 | 从系统推导 | linux | 宿主机操作系统 |
COMMIT_SHA | 无 | GITHUB_SHA | 构建所基于的提交 SHA |
源码深潜:为什么 load 比 build 可靠
现象:load 命令只认三种输入
你用ci-image load时只能三选一:本地已有 tar 文件、--from-run、--from-pr。传--from-run却不给 token,命令直接报错退出,没有"降级尝试"。
源码依据:ci_image_commands.py#L587-L618。platform.replace("/", "_")把linux/amd64拼成linux_amd64,再拼出工件文件名ci-image-save-v3-{platform}-{python}.tar;随后from_run走download_artifact_from_run_id,from_pr走download_artifact_from_pr(定义在 dev/breeze/src/airflow_breeze/utils/github.py),最后执行docker image load -i <tar>。#L638-L643处默认删除下载的 tar,并调用mark_image_as_rebuilt打标记,防止后续 breeze 命令误判"镜像过期需要重建"。
实际影响:load 的输入是 CI 那次运行已经解析完依赖、构建完毕的成品。你在本地执行的只是"下载 + 加载",没有任何依赖解析环节。这就是它保真度最高的根本原因——漂移窗口为零。
现象:CI 日志里自带"本地复现指令"
CI 作业日志末尾常有一块带分隔线的HOW TO REPRODUCE LOCALLY文本,里面的命令可以直接复制到本地。
源码依据:dev/breeze/src/airflow_breeze/utils/reproduce_ci.py#L62-L134。build_reproduction_command_from_context遍历命令定义的每个参数,用 click 的ctx.get_parameter_source()判断来源,只输出 COMMANDLINE / ENVIRONMENT / PROMPT 三种显式来源的值,取默认值的参数全部省略;--flag/--no-flag成对选项只输出被显式设置的一侧。#L192-L196的should_print_local_reproduction限定只有CI=true且GITHUB_ACTIONS=true时才打印,所以你本地跑 breeze 不会看到这块输出。
实际影响:你在日志里看到的复现命令是程序按"当时实际生效的参数"自动生成的,不是手写文案。这意味着可以放心整段复制,包括那些你没意识到的环境变量参数——它们会被还原成对应的 flag 出现。
现象:skip 模式下容器内容与 CI 完全一致
breeze shell --mount-sources skip进去后,改本地文件对容器毫无影响。第一次用会有点不习惯,但这正是它的设计目的。
源码依据:dev/breeze/src/airflow_breeze/params/shell_params.py#L417-L426。挂载模式决定追加哪份 compose 覆盖文件:MOUNT_SELECTED(默认)挂本地选中目录,MOUNT_ALL挂全部,MOUNT_REMOVE移除源码挂载。而MOUNT_SKIP不在任何挂载分支里——它不追加挂载配置,容器直接使用镜像内自带的源码。#L697处还会把MOUNT_SOURCES的值写入容器环境变量,供容器内初始化逻辑感知当前挂载方式。
实际影响:想复现"CI 那一刻"就用 skip;想调试自己的改动就切回默认挂载。同一个镜像,两种入口,别混用,否则你会对着容器里一份代码、本地另一份代码怀疑人生。
排查决策清单
按"先试成本最低的方案"排列,从上到下依次降级:
- 抄作业:打开失败 CI 日志,定位
HOW TO REPRODUCE LOCALLY区块,把整段命令原样复制到本地终端。 - 有分支就直接跑:已检出 PR 分支时,直接执行日志中的常规
breeze命令(路径 C),IDE 全开,改代码即所见。 - 确认 commit 对齐:
git checkout <CI 中的 COMMIT_SHA>,排除"复现出来的其实是新问题"。 - 拉 CI 原始镜像:AMD 机器执行
breeze ci-image load --from-run <run_id> --python <版本> --github-token <token>,然后breeze shell --mount-sources skip [OPTIONS]进入精确现场。 - 镜像拉不到才自建:
breeze ci-image build,canary / 特殊 PR 追加--upgrade-to-newer-dependencies,并接受"依赖可能与 CI 存在差异"这一前提。 - 对照变量表逐项核对:用上面的速查表比对
[OPTIONS]与环境变量(尤其DB_RESET、MOUNT_SOURCES、RUN_DB_TESTS_ONLY),缺一个变量,复现就可能差一截。 - 验证修复后再看 CI:本地把失败测试跑绿,提交;若 CI 仍红,回到第 4 步用新 Run ID 再拉一次镜像,因为新运行可能踩中新的依赖版本。
这套闭环让"CI 红了"变成一件可以在工位旁十分钟定位的事,而不是对着云上的日志盲猜。
【免费下载链接】airflowApache Airflow - A platform to programmatically author, schedule, and monitor workflows项目地址: https://gitcode.com/GitHub_Trending/ai/airflow
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考