
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保真度最高但需要 Tokenbuild最灵活但可能漂移常规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_DEPENDENCIEStrue构建时完全不用 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否则复现出来的可能是新问题而不是老问题。四维对比维度路径 Aload 镜像路径 Bbuild 镜像路径 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-resetfalsetrue容器入口是否重置数据库ANSWER--answeryes是否自动应答交互提问测试变量控制测试执行范围变量名对应 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-sourcesskip是否把本地源码挂载进容器SKIP_ENVIRONMENT_INITIALIZATION--skip-environment-initializationfalseprek hooks 中为 true同左跳过测试环境初始化SKIP_IMAGE_UPGRADE_CHECK--skip-image-upgrade-checkfalseprek hooks 中为 true同左跳过镜像升级检查SKIP_SSH_SETUP无仅环境变量falseCodeSpaces 中为 true跳过为测试配置 SSH 服务器VERBOSE--verbosefalsetrue打印内部命令的详细信息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_idfrom_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限定只有CItrue且GITHUB_ACTIONStrue时才打印所以你本地跑 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命令路径 CIDE 全开改代码即所见。确认 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 buildcanary / 特殊 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),仅供参考