Flyte 2正式GA:告别DAG,纯Python原生编排全面重写

Flyte 2 正式 GA:一次彻底的架构重写
Union AI 团队近日宣布了 Flyte 2 的正式发布(GA)。作为一个基于 Apache 2.0 协议的开源项目,Flyte 2 并非在原有代码基础上的迭代升级,而是一次彻底的重写。团队移除了旧版的 DSL(领域特定语言),取消了强制构建 DAG(有向无环图)的要求,甚至将 Propeller 等核心组件也从架构中剥离出去。
这里有必要解释两个关键概念。DSL(Domain-Specific Language,领域特定语言)是为特定应用领域设计的编程语言或配置语法,区别于 Python、Java 等通用编程语言。在工作流编排领域,DSL 通常用于描述任务之间的依赖关系和执行顺序。典型的例子包括 Airflow 的 DAG 定义语法、Argo Workflows 的 YAML 模板,以及早期 Flyte 自身要求用户使用的 Protobuf-based 接口。DSL 的优势在于约束性强、可静态分析,但代价是表达能力受限且学习成本高。DAG(Directed Acyclic Graph,有向无环图)则是一种数据结构,其中节点代表任务,有向边代表依赖关系,"无环"意味着不存在循环依赖。Apache Airflow 等工具要求用户将所有工作流逻辑表达为 DAG 结构,这虽然在概念上清晰,但对于需要动态分支、条件循环或运行时决策的场景而言,会迫使开发者采用大量 workaround 来绕过 DAG 的静态限制。
而 Propeller 是 Flyte 1.x 架构中的核心执行引擎,作为 Kubernetes Operator 运行在集群中,负责监控工作流的执行状态、调度任务 Pod、处理重试逻辑等。Kubernetes Operator 是一种软件扩展模式,通过自定义控制器(Controller)监听和管理自定义资源(Custom Resource),将特定领域的运维知识编码为自动化逻辑。Propeller 通过不断轮询 CRD(Custom Resource Definition)的状态来驱动工作流推进——这种 Reconciliation Loop 模式虽然符合 Kubernetes 的声明式哲学,但在高并发场景下频繁的 API Server 调用和 etcd 写入会成为性能瓶颈。CRD 本身存储在 etcd 中,而 etcd 的单条记录大小限制为 1.5MB,当工作流包含数百个节点时,状态信息的序列化可能接近这一上限。虽然 Propeller 在生产环境中表现稳健,但其与 Kubernetes API 的深度耦合以及基于 CRD 的状态管理模式,在大规模并发场景下确实构成了架构层面的天花板。Flyte 2 移除 Propeller 意味着采用了全新的执行调度机制,可能转向基于事件驱动或独立数据库的状态管理方案。
这样的大刀阔斧背后有多重考量,但官方明确指出,最核心的驱动力是开发者体验。在传统的工作流编排工具中,数据科学家、机器学习工程师和研究人员往往被迫将自己的工作拆解成符合 DAG 结构的形式,并额外学习一套 DSL。这对于本就专注于算法与实验的人来说,是一道不小的门槛。
从 DAG 到 .task 装饰器:降低编排门槛
编排逻辑回归纯 Python
Flyte 2 最直观的变化,是编排方式的极简化。过去需要显式声明节点依赖、构建 DAG 的繁琐流程,如今被一个简单的 .task 装饰器所取代——加上装饰器,任务即可纳入编排体系。
这种装饰器模式在 Python 生态中已有成熟先例:Flask 用 @app.route 将函数注册为 HTTP 端点,pytest 用 @pytest.fixture 声明测试依赖,Celery 用 @app.task 将函数转化为分布式任务。装饰器的优势在于零侵入性——被装饰的函数仍然是合法的 Python 函数,可以在本地直接调用和调试,无需启动整个编排框架。
更重要的是,由于整个框架基于纯 Python 实现,开发者可以直接使用语言原生的控制流。官方特别强调,try/except、循环(loops)、以及 asyncio 异步编程都能"开箱即用"。asyncio 是 Python 3.4+ 引入的异步编程框架,基于事件循环(Event Loop)和协程(Coroutine)实现非阻塞并发。在工作流编排场景中,asyncio 允许开发者以自然的 await 语法表达任务间的并发等待关系,而无需显式声明并行分支——例如 results = await asyncio.gather(task_a(), task_b(), task_c()) 即可表达三个任务的并行执行,这与传统 DAG 编排中需要预先定义并行节点的方式形成鲜明对比。这意味着复杂的分支逻辑、错误处理和并发控制不再需要通过特定的 DSL 抽象来表达,而是回归到工程师最熟悉的编程范式。
从 OOM 崩溃中恢复:细粒度容错控制
这种纯 Python 的设计带来了一个颇具实用价值的能力:开发者可以在代码层面从 OOM(内存溢出)导致的 Pod 崩溃中恢复。
OOM(Out of Memory)是 Kubernetes 环境中最常见的任务失败原因之一。当容器内存使用超过 Pod 的资源限制(resource limits)时,Linux 内核的 OOM Killer 会强制终止进程,Kubernetes 随即将 Pod 标记为 OOMKilled 状态。从技术细节来看,Linux OOM Killer 是内核内存管理子系统的最后防线——当 cgroup 的内存用量触及 memory.limit_in_bytes 时,内核直接向容器主进程发送 SIGKILL 信号,这是一个不可捕获、不可忽略的信号,进程没有任何清理机会。Kubernetes 区分 requests(调度保证,用于 scheduler 决定 Pod 放置在哪个节点)和 limits(硬上限,由 cgroup 强制执行),Pod 的 QoS 类别(Guaranteed/Burstable/BestEffort)决定了其在节点内存压力下被驱逐的优先级。在传统编排系统中,OOM 导致的 Pod 崩溃通常只能触发整个任务的重试,这对于已经运行数小时的大规模训练任务来说代价极高。
Flyte 2 允许在 Python 代码层面捕获这类故障并执行恢复逻辑(如切换到更大内存的环境、减少批处理大小、启用梯度检查点后在更多步骤中分摊内存等),本质上是将故障恢复的控制权从平台层下放到了应用层。其实现机制可能是:当检测到 OOMKilled 退出码(137)时,框架在新 Pod 中重新加载任务上下文,并将 OOM 事件作为可处理的异常传递给用户代码,而非直接触发整个工作流的失败。在传统基于 DAG 的编排系统中,任务失败往往意味着整条流水线的中断,而 Flyte 2 让开发者拥有更细粒度的容错控制能力,这对于长时间运行的训练任务尤为关键。
环境(Environments):声明式资源管理
项目作者从自身的 Kubernetes 背景出发,指出了一个长期存在的痛点:应用清单(application manifest)中资源声明与容器镜像定义的割裂与繁琐。
在标准的 Kubernetes 工作流中,开发者需要编写 YAML 格式的 Pod Spec 或 Deployment 清单来声明资源需求(requests/limits)、挂载卷、环境变量等,同时还需要维护 Dockerfile 来定义容器镜像中的依赖。这两者之间的割裂是实际工程中的重大痛点:修改一个 Python 依赖可能需要重新构建镜像、推送到 Registry、更新清单中的镜像标签,整个流程可能耗时数分钟到数十分钟。对于 ML 工程师而言,这个问题更为突出——一次实验可能只需要添加一个新的 pip 包或将 GPU 从 T4 切换到 A100,但传统流程要求经过完整的 CI/CD 管线才能实现这种简单变更。
Flyte 2 引入了"环境"(Environments)这一概念作为解法。开发者可以为任意流水线定义任意数量的环境,在运行时,Pod 会按照指定的配置被自动置备,包括:
- CPU / RAM / GPU 等计算资源
- 操作系统层面的软件包
- Python 依赖包
这种声明式的环境管理,将资源配置与业务逻辑更紧密地绑定在一起,类似于 Heroku 式的开发者体验与 Kubernetes 底层能力的结合——平台自动处理镜像构建和 Pod 配置。在技术实现上,这可能借鉴了 Cloud Native Buildpacks 或 Nix 等确定性构建技术:系统根据用户声明的依赖列表自动派生出可复现的容器镜像,利用层缓存(Layer Caching)加速增量构建。核心挑战在于 ML 场景中 CUDA、cuDNN、NCCL 等系统级依赖的版本兼容性矩阵极其复杂(例如 PyTorch 2.1 需要 CUDA 11.8 或 12.1,而不同版本的 cuDNN 可能导致数值结果差异),声明式环境需要正确解析这些约束。官方认为,这在**实验阶段(experimentation mode)**能够显著提升迭代速度——研究人员无需在 YAML 清单与代码之间反复切换,即可快速调整运行环境并验证想法。
数据血缘与版本控制:保障可复现性
对于机器学习和数据工程场景而言,可复现性至关重要。Flyte 2 在这一点上提供了较为完整的支持:所有的数据输入、输出以及被执行的代码,都会被捕获并版本化存储到对象存储(object storage)中。
对象存储(Object Storage)如 AWS S3、Google Cloud Storage 或 MinIO,以扁平的键值对方式存储任意大小的二进制对象,天然适合存储数据集、模型文件和执行快照。与块存储(EBS)或文件存储(EFS/NFS)相比,对象存储的优势在于近乎无限的扩展性、内置的版本控制(S3 Versioning)和低廉的存储成本,但代价是更高的访问延迟和不支持随机写入。
数据血缘(Data Lineage)则追踪数据从源头到最终输出的完整变换路径,包括哪些代码处理了数据、输入数据的版本、执行时的环境配置等。在机器学习场景中,数据血缘是实现实验可复现性的基础——没有它,团队往往无法回答"这个模型是用什么数据训练的"、"上周的实验结果为何无法重现"或"哪些下游模型受到了这次数据修复的影响"等关键问题。MLflow(侧重实验跟踪和模型注册)、DVC(Data Version Control,用 Git-like 语义管理大文件)、Weights & Biases(实验可视化和协作)等工具也提供类似能力,但 Flyte 2 将其内置于编排层,避免了额外集成的成本和数据同步的一致性问题。
这意味着每一次执行都具备完整的血缘追溯能力,团队可以清晰地知道某个结果是由哪个版本的代码、在何种数据输入下产生的。对于需要满足合规审计(如金融领域的模型风险管理 SR 11-7、医疗领域的 FDA 对 AI/ML 设备的监管要求)、复现实验或排查问题的团队来说,这是一项不可或缺的基础能力。
产品定位:Kubeflow 与 Airflow 的替代方案
从产品定位上看,Flyte 2 明确将自己置于 Kubeflow、Airflow 等工作流编排工具的竞争赛道中。
Apache Airflow 最初由 Airbnb 于 2014 年开发并于 2016 年进入 Apache 孵化器,核心设计面向数据管道调度,使用 Python 编写 DAG 定义但执行逻辑依赖外部系统(如 Spark、数据库),其 Executor 模型(Celery/Kubernetes/Local)决定了任务的实际运行方式。Airflow 2.x 引入了 TaskFlow API 简化了任务间数据传递,但其核心架构仍然基于 Scheduler 定期扫描 DAG 文件、Metadata DB 存储状态的模式,DAG 解析时的静态性这一根本限制并未改变。Kubeflow 则是 Google 主导的 ML 平台项目,包含 Pipelines(基于 Argo Workflows 的 DAG 引擎)、Katib(超参调优,支持贝叶斯优化、网格搜索等算法)、KFServing(现更名为 KServe,基于 Knative 的模型服务)等组件,深度依赖 Kubernetes 原语如 PVC、Service、Istio 等。部署一套完整的 Kubeflow 可能需要 30+ 个微服务和大量的 CRD。
除这两者外,MLOps 编排赛道还有多个值得关注的竞争者:Prefect(同样强调纯 Python 和去 DAG 化,但其 Cloud 版本是托管服务而非 K8s 原生)、Dagster(以软件定义资产 Software-Defined Assets 为核心抽象,更偏数据工程)、Metaflow(Netflix 开源,强调从笔记本到生产的平滑过渡,但深度依赖 AWS 生态)、以及 ZenML(ML 流水线的抽象层,可对接多种编排后端)。
这个赛道长期存在几个核心矛盾:
- 表达能力 vs. 学习成本:Airflow 的 DAG 模型直观,但对复杂动态逻辑的支持有限(其 DAG 在解析时是静态的,运行时动态生成任务需要借助较为 hack 的方式实现,如使用
trigger_dagrun或在 DAG 文件中通过 Variable 查询来动态生成任务);而灵活性更强的方案往往学习曲线陡峭。 - Kubernetes 原生 vs. 开发者友好:Kubeflow 深度绑定 K8s,功能强大但组件过多、运维复杂度高,资源与镜像的管理对算法人员并不友好——一个数据科学家不应该需要理解 PersistentVolumeClaim、Istio VirtualService 或 RBAC 策略才能运行一个训练任务。
Flyte 2 试图通过"纯 Python + 装饰器 + 环境抽象"的组合来同时化解这两个矛盾:既保留了对 Kubernetes 资源的精细控制(高级用户仍可指定 tolerations、node selectors、GPU 型号等),又将复杂性隐藏在了简洁的 Python API 之下。这种分层抽象的设计哲学——让简单事情简单做、复杂事情可能做——是平台工程领域的最佳实践。
观察与思考:编排工具的演进方向
工作流编排领域从来不缺工具,但真正的竞争壁垒往往不在功能列表,而在于开发者是否愿意用它来完成日常工作。Flyte 2 移除 DSL、拥抱原生 Python 的策略,反映出一个明显的行业趋势:编排工具正在从"要求用户适应工具"转向"让工具适应用户的习惯"。
这一趋势在更广泛的基础设施工具领域也有体现——Pulumi 用通用编程语言(TypeScript、Python、Go、C#)替代 Terraform 的 HCL(HashiCorp Configuration Language)、AWS CDK 用 TypeScript/Python 替代 CloudFormation 的 YAML,Gradle 用 Groovy/Kotlin 替代 Maven 的 XML,都是同一方向的探索。核心逻辑是:当目标用户已经精通某种通用语言时,为他们设计专用语法只会增加认知负担而非降低复杂性。通用语言带来的额外好处还包括:成熟的 IDE 支持(自动补全、类型检查、调试器)、丰富的第三方库生态、以及现有的测试框架和 CI/CD 工具链可以直接复用。
当然,一次彻底的重写也意味着风险——移除 Propeller 等成熟组件、重构核心架构,对已有 Flyte 1.x 用户的迁移成本、生态兼容性以及生产环境的稳定性都提出了新的考验。历史上,软件项目的"大重写"(Big Rewrite)成功率并不高——Netscape 的重写导致了市场份额的丧失,而 Firefox Quantum 的渐进式重写则取得了成功。Flyte 2 选择一次性重写而非渐进式演进,说明团队认为旧架构的技术债务已经积累到无法通过增量改进来解决的程度。这些问题的答案,仍需要时间和实际的大规模落地来验证。
对于正在评估 MLOps 编排方案、且对 Airflow 的 DAG 约束或 Kubeflow 的复杂性感到困扰的团队而言,Flyte 2 至少提供了一个值得认真考察的新选项。项目已在 www.flyte.org 开放,感兴趣的开发者可以进一步了解。
核心要点
相关推荐

AI专业选电脑:MacBook还是NVIDIA笔记本?深度对比指南
AI专业大学生选电脑深度分析:MacBook Air M5搭配远程GPU vs NVIDIA独显笔记本,从CUDA支持、便携性、续航、性价比等维度全面对比,附实操建议。

自托管LLM技术栈:从终端统一管理本地AI集群的完整指南
深入解析如何自托管LLM技术栈,涵盖推理引擎选型、模型管理、向量数据库配置等核心组件,探讨从终端统一管理本地AI集群的实践方案、硬件要求与技术挑战。

Hugging Face工程师用AI Agent自动化团队工作全流程实战
Hugging Face机器学习工程师Niels分享如何用AI Agent自动化Community Science Team的核心工作,从确定性Workflow到自主Agent的架构演进,涵盖技术栈选择、部署方案与真实成效。