生产级编排AI工作流套件:Flyte全面使用指南 — Core concepts Workflows

生产级编排AI工作流套件:Flyte全面使用指南 — Core concepts Workflows

Flyte 是一个开源编排器,用于构建生产级数据和机器学习流水线。它以 Kubernetes 作为底层平台,注重可扩展性和可重复性。借助 Flyte,用户团队可以使用 Python SDK 构建流水线,并将其无缝部署在云端和本地环境中,从而实现分布式处理和高效的资源利用。

文中内容仅限技术学习与代码实践参考,市场存在不确定性,技术分析需谨慎验证,不构成任何投资建议。

Flyte

工作流

在我们之前关于工作流的讨论中,主要聚焦于使用@fl.workflow修饰的顶层工作流。实际上,这些更准确地应称为标准工作流,以便与 Flyte 中存在的其他工作流类型区分:

本节我们将深入探讨所有这些工作流类型的基础知识,包括它们的语法结构、组成要素和运行时行为特征。

标准工作流

标准工作流通过使用@fl.workflow装饰器修饰的Python函数定义。该函数使用领域特定语言(DSL)编写,这种Python语法子集用于描述在Flyte上部署和执行的有向无环图(DAG)。标准工作流定义只能包含以下语法:

标准工作流的求值过程

当标准工作流在Python环境中本地运行时,它作为普通Python函数执行。但当注册到Flyte时,顶层@fl.workflow装饰的函数将按以下方式求值:

  • 工作流的输入会被具体化为延迟求值的promise对象,并传播到下游任务和子工作流
  • 所有通过@fl.task@fl.dynamic@eager装饰的函数调用返回的值也会被具体化为延迟求值的promise对象

生成的这种结构用于构建有向无环图(DAG),并将所需容器部署到集群。这些promise的实际求值发生在任务(或动态/即时工作流)在各自容器中执行时。

条件构造

由于标准工作流不能直接包含Python的if语句,因此提供了特殊的conditional构造来定义工作流中的条件逻辑。详见条件语句

链式操作符

当Flyte为标准工作流构建DAG时,它通过将值从一个任务传递到另一个任务来确定任务间的依赖关系。

在某些情况下,可能需要定义不基于任务输出传递的任务间依赖关系。此时可以使用链式操作符>>来定义任务依赖。详见链式Flyte实体

工作流装饰器参数

@fl.workflow装饰器可接受以下参数:

  • failure_policy: 使用flytekit.WorkflowFailurePolicy中的选项
  • interruptible: 指示从此工作流启动的任务是否默认可中断。参见可中断实例
  • on_failure: 失败时调用此工作流或任务。指定的工作流必须具有与当前工作流相同的参数签名,并额外包含名为error的参数
  • docs: 工作流的描述实体

子工作流与子启动计划

在 Flyte 中,可以从一个工作流内部调用另一个工作流。父工作流可以通过两种方式调用子工作流:作为子工作流或通过子启动计划

两种情况下,子工作流都需要正常定义和注册,并作为独立实体存在于系统中。

如果父工作流直接调用子工作流函数来触发子工作流,则称为子工作流。子工作流的 DAG 将直接嵌入父工作流的 DAG 中,共享相同的执行 ID 和执行上下文,成为父工作流执行的一部分。

如果父工作流通过调用子工作流的启动计划来触发子工作流,则称为子启动计划。这会生成一个具有独立执行 ID 和执行上下文的新顶级工作流执行。该执行在系统中显示为独立的顶级实体,唯一的区别是其触发方式来自另一个工作流而非命令行或 UI。

示例代码:

import flytekit as fl

@fl.workflow
def sub_wf(a: int, b: int) -> int:
    return t(a=a, b=b)

# 获取sub_wf的默认启动计划,命名为sub_wf_lp
sub_wf_lp = fl.LaunchPlan.get_or_create(sub_wf)

@fl.workflow
def main_wf():
    # 直接调用sub_wf
    # 生成嵌入式子工作流
    sub_wf(a=3, b=4)

    # 通过默认启动计划sub_wf_lp调用sub_wf
    # 生成独立子工作流
    sub_wf_lp(a=1, b=2)

何时使用子工作流

子工作流允许在工作流与其子流程之间管理并行性,因为它们与父工作流共享相同的执行上下文。因此,子工作流的所有节点都遵循父工作流的整体约束。

以下示例演示如何计算斜率、截距和对应的 y 值:

import flytekit as fl

@fl.task
def slope(x: list[int], y: list[int]) -> float:
    sum_xy = sum([x[i] * y[i] for i in range(len(x))])
    sum_x_squared = sum([x[i] ** 2 for i in range(len(x))])
    n = len(x)
    return (n * sum_xy - sum(x) * sum(y)) / (n * sum_x_squared - sum(x) ** 2)

@fl.task
def intercept(x: list[int], y: list[int], slope: float) -> float:
    mean_x = sum(x) / len(x)
    mean_y = sum(y) / len(y)
    intercept = mean_y - slope * mean_x
    return intercept

@fl.workflow
def slope_intercept_wf(x: list[int], y: list[int]) -> (float, float):
    slope_value = slope(x=x, y=y)
    intercept_value = intercept(x=x, y=y, slope=slope_value)
    return (slope_value, intercept_value)

@fl.task
def regression_line(val: int, slope_value: float, intercept_value: float) -> float:
    return (slope_value * val) + intercept_value  # y = mx + c

@fl.workflow
def regression_line_wf(val: int = 5, x: list[int] = [-3, 0, 3], y: list[int] = [7, 4, -2]) -> float:
    slope_value, intercept_value = slope_intercept_wf(x=x, y=y)
    return regression_line(val=val, slope_value=slope_value, intercept_value=intercept_value)

slope_intercept_wf 计算回归线的斜率和截距,随后 regression_line_wf 触发该子工作流并计算 y 值。

工作流可以嵌套包含子工作流的多层结构。即使各个工作流都能独立运行,也可以轻松组合构建复杂流程:

import flytekit as fl

@fl.workflow
def nested_regression_line_wf() -> float:
    return regression_line_wf()

何时使用子启动计划

当无法通过动态工作流映射任务实现大型复杂工作流时,子启动计划可提供解决方案。动态工作流和映射任务共享上下文和 Kubernetes 资源定义,而子启动计划触发的工作流则使用独立上下文,作为独立的顶级实体执行,可实现更好的并行性和扩展性。

以下示例展示通过启动计划多次调用工作流:

import flytekit as fl

@fl.task
def my_task(a: int, b: int, c: int) -> int:
    return a + b + c

@fl.workflow
def my_workflow(a: int, b: int, c: int) -> int:
    return my_task(a=a, b=b, c=c)

my_workflow_lp = fl.LaunchPlan.get_or_create(my_workflow)

@fl.workflow
def wf() -> list[int]:
    return [my_workflow_lp(a=i, b=i, c=i) for i in [1, 2, 3]]

动态工作流

动态工作流是指在运行时计算其有向无环图(DAG)的动态工作流。这类工作流中的任务使用动态输入在运行时执行,其声明方式与标准工作流相似,采用Python风格的领域特定语言来定义任务间依赖或新工作流。

关键区别在于动态工作流在运行时进行评估,意味着输入参数会首先具体化并传递给动态工作流(类似于任务行为)。动态工作流的返回值是Promise对象,可由后续任务具体化。

动态工作流可视为任务与工作流的结合体,用于在运行时动态决定工作流参数,其编译和执行均发生在运行时。

适用场景

动态工作流在以下场景中至关重要:

  • 处理条件逻辑
  • 在运行时修改代码逻辑
  • 动态调整特征提取参数

定义动态工作流

使用@fl.dynamic装饰器定义动态工作流。在此上下文中,每个任务调用或Task派生类都会通过Promise进行延迟求值,而非立即具体化实际值。虽然可以嵌套其他@fl.dynamic@fl.workflow结构,但由于惰性求值特性,直接与任务/工作流输出交互受限。建议将交互逻辑分离到新任务中处理。

字符统计示例

以下示例演示如何统计两个字符串的公共字符数量:

字符索引计算任务:

import flytekit as fl

@fl.task
def return_index(character: str) -> int:
    if character.islower():
        return ord(character) - ord("a")
    else:
        return ord(character) - ord("A")

频率列表更新任务:

@fl.task
def update_list(freq_list: list[int], list_index: int) -> list[int]:
    freq_list[list_index] += 1
    return freq_list

公共字符计数任务:

@fl.task
def derive_count(freq1: list[int], freq2: list[int]) -> int:
    count = 0
    for i in range(26):
        count += min(freq1[i], freq2[i])
    return count

动态工作流定义:

@fl.dynamic
def count_characters(s1: str, s2: str) -> int:
    # s1和s2应为可访问字符串
    
    # 初始化两个各包含26个槽位的空列表,对应每个字母(大小写不敏感)
    freq1 = [0] * 26
    freq2 = [0] * 26

    # 遍历s1的字符
    for i in range(len(s1)):
        # 计算当前字符在字母表中的索引
        index = return_index(character=s1[i])
        # 更新s1的频率列表
        freq1 = update_list(freq_list=freq1, list_index=index)
        # index和freq1作为Promise对象不可直接访问

    # 遍历s2的字符
    for i in range(len(s2)):
        # 计算当前字符在字母表中的索引
        index = return_index(character=s2[i])
        # 更新s2的频率列表
        freq2 = update_list(freq_list=freq2, list_index=index)
        # index和freq2作为Promise对象不可直接访问

    # 比较两个频率列表计算公共字符数
    return derive_count(freq1=freq1, freq2=freq2)

触发工作流:

@fl.workflow
def start_wf(s1: str, s2: str) -> int:
    return count_characters(s1=s1, s2=s2)

if __name__ == "__main__":
    print(start_wf(s1="Pear", s2="Earth"))

核心优势

灵活性

动态工作流简化了流水线构建过程,可根据项目需求灵活设计工作流结构,这种灵活性是静态工作流无法实现的。

减轻对etcd的压力

静态工作流的CRD(自定义资源定义)和状态存储在Kubernetes的etcd数据库中。由于etcd存在数据大小限制,需控制静态工作流的内存占用。

动态工作流将工作流规范(包括节点/任务定义和连接关系)卸载到对象存储,仅节点状态存储在etcd中,有效缓解存储压力。

动态工作流 vs 映射任务

动态任务在大规模扇出场景存在元数据存储开销,而映射任务通过避免存储元数据,在类似场景中效率更高。

递归实现示例

归并排序案例展示如何通过动态工作流实现递归(Flyte对递归深度有限制以防止系统稳定性问题):

from typing import Tuple

import flytekit as fl

@fl.task
def split(numbers: list[int]) -> tuple[list[int], list[int]]:
    length = len(numbers)
    return (
        numbers[0 : int(length / 2)],
        numbers[int(length / 2) :]
    )

@fl.task
def merge(sorted_list1: list[int], sorted_list2: list[int]) -> list[int]:
    result = []
    while len(sorted_list1) > 0 and len(sorted_list2) > 0:
        # 比较两个数组当前元素大小
        if sorted_list1[0] < sorted_list2[0]:
            result.append(sorted_list1.pop(0))
        else:
            result.append(sorted_list2.pop(0))
    
    # 合并剩余元素
    result.extend(sorted_list1)
    result.extend(sorted_list2)
    return result

@fl.task
def sort_locally(numbers: list[int]) -> list[int]:
    return sorted(numbers)

@fl.dynamic
def merge_sort_remotely(numbers: list[int], threshold: int) -> list[int]:
    split1, split2 = split(numbers=numbers)
    sorted1 = merge_sort(numbers=split1, threshold=threshold)
    sorted2 = merge_sort(numbers=split2, threshold=threshold)
    return merge(sorted_list1=sorted1, sorted_list2=sorted2)

@fl.dynamic
def merge_sort(numbers: list[int], threshold: int=5) -> list[int]:
    if len(numbers) <= threshold:
        return sort_locally(numbers=numbers)
    else:
        return merge_sort_remotely(numbers=numbers, threshold=threshold)

通过@fl.dynamic注解,merge_sort_remotely转换为执行计划,生成包含四个独立节点的工作流。这些节点可在不同主机上运行,Flyte确保数据引用正确传递,并在最大并行度下维护执行顺序。动态工作流在此场景中至关重要,因为merge_sort的触发次数在编译时未知。

Eager Workflows

本功能目前处于实验阶段,API 可能会发生破坏性变更。

Eager workflows 允许您创建能够运行时访问中间任务或子工作流输出的工作流。

静态和动态工作流都存在一个关键限制:虽然它们分别提供编译时和运行时类型安全,但在表达异步执行图方面都缺乏灵活性。而使用 Python 开发者熟悉的 asyncio 等库可以更灵活地实现异步执行。

与静态和动态工作流不同,eager workflows 允许通过 asyncio API 使用所有 Python 语法结构。以下示例展示了如何使用 @eager 装饰器定义基础 eager workflow:

import flytekit as fl
from flytekit.experimental import eager


@fl.task
def add_one(x: int) -> int:
    return x + 1


@fl.task
def double(x: int) -> int:
    return x * 2


@eager
async def simple_eager_workflow(x: int) -> int:
    out = await add_one(x=x)
    if out < 0:
        return -1
    return await double(x=out)

如上所示,我们定义了名为 simple_eager_workflowasync 函数。通过 @eager 装饰器,我们可以在父 eager workflow 中即时获取任务、静态子工作流或其他 eager 子工作流的输出并进行操作。

simple_eager_workflow 函数中,我们通过 await 获取 add_one 任务输出并赋值给 out 变量。如果 out 为负数则返回 -1,否则返回 double 处理后的结果。

与静态/动态工作流不同,这里的 out 变量是实际的 Python 整数(x + 1 的结果),而非 Promise 对象。

Eager Workflows 工作原理

当使用 @eager 装饰函数时,在父 eager workflow 执行期间,任何被调用的 @fl.task@fl.workflow@eager 装饰函数都会成为 awaitable 对象。注意:这过程自动完成,不需要在定义被调用任务/工作流时使用 async 关键字。

Eager workflows 通过 Python 原生 asyncio 接口实现了高度灵活的执行图定义。代价是失去了静态工作流的编译时类型安全和动态工作流的部分优势。

我们利用 Python 原生 async 能力实现了:

  1. 即时获取任务/子工作流输出,无需创建新 Pod 即可操作,并能灵活决定工作流图形状
  2. 提供 Flyte 原生的并发实现替代方案。虽然 Flyte 内置并发能力(无依赖关系的任务/子工作流自动并行执行),但 eager workflows 通过 Python 原生方式实现,缺点是失去了静态编译工作流的优势(如编译时分析和数据溯源)

动态工作流 类似,eager workflows 本质上是任务。主要区别在于:动态工作流在运行时使用具体化输入编译静态工作流,而 eager workflows 完全不编译工作流,而是通过 FlyteRemote 和 Python asyncio API 在 await 协程时即时触发任务/子工作流执行。这意味着 eager workflows 可以将任务/子工作流输出作为 Python 对象在运行时环境中使用。

Eager Workflows 用例

本节介绍 eager workflows 的典型用例(部分无法通过静态/动态工作流实现):

操作任务/子工作流输出

最大优势是可将任务/子工作流输出作为 Python 值操作:

@eager
async def another_eager_workflow(x: int) -> int:
    out = await add_one(x=x)

    # out 是 Python 整数
    out = out - 1

    return await double(x=out)

由于 out 是实际 Python 整数(而非 Promise),我们可以在运行时直接操作,这在静态/动态工作流中无法实现。

Python 条件判断

simple_eager_workflow 示例所示,可在 eager workflow 中使用常规 Python 条件语句:

@fl.task
def gt_100(x: int) -> bool:
    return x > 100


@eager
async def eager_workflow_with_conditionals(x: int) -> int:
    out = await add_one(x=x)

    if out < 0:
        return -1
    elif await gt_100(x=out):
        return 100
    else:
        out = await double(x=out)

    assert out >= -1
    return out

此示例演示了在 Python 运行时检查 out 是否为负数,同时在 elif 语句中使用 gt_100 任务(将在独立任务中执行)。

循环

收集多个任务/子工作流输出到列表:

import asyncio


@eager
async def eager_workflow_with_for_loop(x: int) -> int:
    outputs = []

    for i in range(x):
        outputs.append(add_one(x=i))

    outputs = await asyncio.gather(*outputs)
    return await double(x=sum(outputs))

静态子工作流

可在 eager workflow 中调用静态工作流:

@fl.workflow
def subworkflow(x: int) -> int:
    out = add_one(x=x)
    return double(x=out)


@eager
async def eager_workflow_with_static_subworkflow(x: int) -> int:
    out = await subworkflow(x=x)
    assert out == (x + 1) * 2
    return out

Eager 子工作流

支持在父 eager workflow 中嵌套 eager 子工作流:

@eager
async def eager_subworkflow(x: int) -> int:
    return await add_one(x=x)


@eager
async def nested_eager_workflow(x: int) -> int:
    out = await eager_subworkflow(x=x)
    return await double(x=out)

异常捕获

通过 EagerException 捕获异常:

from flytekit.experimental import EagerException


@fl.task
def raises_exc(x: int) -> int:
    if x <= 0:
        raise TypeError
    return x


@eager
async def eager_workflow_with_exception(x: int) -> int:
    try:
        return await raises_exc(x=x)
    except EagerException:
        return -1

尽管 raises_exc 任务抛出 TypeError,但 eager_workflow_with_exception 运行时将抛出 EagerException,需要在 try...except 块中指定捕获该异常类型。这是当前 @eager 实现的限制。

执行 Eager Workflows

与多数 Flyte 组件类似,eager workflows 支持本地和远程执行。

本地执行

通过常规 async 函数调用方式本地执行:

if __name__ == "__main__":
    result = asyncio.run(simple_eager_workflow(x=5))
    print(f"Result: {result}")  # 输出 "Result: 12"

使用 asyncio.run 执行 eager workflow,便于本地调试开发。

远程 Flyte 执行

底层实现中,@eager 工作流使用 FlyteRemote 对象触发任务、静态工作流和 eager workflows 执行。

要在 Flyte 集群执行,需要配置 FlyteRemote 对象和通过客户端密钥认证的 secrets 配置:

import flytekit as fl

@eager(
    remote=fl.FlyteRemote.auto(
        default_project="flytesnacks",
        default_domain="development",
    ),
    client_secret_group="<my_client_secret_group>",
    client_secret_key="<my_client_secret_key>",
)
async def eager_workflow_remote(x: int) -> int:
    ...

其中 config.yaml 包含 Flyte 配置文件,my_client_secret_groupmy_client_secret_key 是 Flyte 实例配置的密钥组和密钥。

本地 Flyte 集群执行

使用 flytectl demo start 启动的本地集群不需要密钥认证:

import flytekit as fl

@eager(
    remote=fl.FlyteRemote.for_sandbox(
        default_project="flytesnacks",
        default_domain="development",
    )
)
async def eager_workflow_sandbox(x: int) -> int:
    out = await add_one(x=x)
    if out < 0:
        return -1
    return await double(x=out)

执行时,Flyte 会使用 FlyteRemote 对象中指定的 default_projectdefault_domain 下的最新版本任务/工作流。因此需要预先注册所有在 eager workflow 中调用的实体。

注册与运行

正确配置代码后,需使用 pyflyte register 注册所有相关任务和子工作流:

$ pyflyte --config <path/to/config.yaml> register \
        --project <project> \
        --domain <domain> \
        --image <image> \
        path/to/eager_workflows.py

然后通过 pyflyte run 运行:

$ pyflyte --config <path/to/config.yaml> run \
        --project <project> \
        --domain <domain> \
        --image <image> \
        path/to/eager_workflows.py simple_eager_workflow --x 10

由于 eager workflows 本质是任务,pyflyte run 无法自动识别其内部调用的任务/子工作流,必须预先注册。

UI 中的 Eager Workflows

作为实验性功能,当前 UI 没有 eager workflows 的专属视图。注册后可在任务列表中查看,执行时内部调用的任务/子工作流不会显示在节点图或时间轴视图中(因为 Flyte 无法预先获知执行图形状)。但执行完成后可通过 Decks 查看所有执行过的任务/子工作流列表。

限制

当前实验阶段的限制包括:

  • 无法调用 动态工作流map taskslaunch plans
  • 上下文管理器 仅对本地执行函数有效(无法影响在独立 Pod 执行的任务/子工作流)
  • 所有 Flyte 任务/工作流异常都会被捕获并作为 EagerException 抛出
  • 所有任务/子工作流输出都会具体化为 Python 值(包括 FlyteFileFlyteDirectoryStructuredDatasetpandas.DataFrame 等类型会完整下载到运行 eager workflow 的 Pod,无法增量下载或流式处理大数据集)
  • 调用的 Flyte 实体必须与 eager workflow 注册在同一项目和域,且执行最新版本
  • UI 暂不支持专属视图,但可通过任务列表访问,执行图可通过 Flyte Decks 查看

命令式工作流

工作流通常通过将 @fl.workflow 装饰器应用于 Python 函数来创建。在编译过程中,这涉及处理函数体并利用后续对底层任务的调用来建立和记录工作流结构。这是_声明式_方法,适用于手动设计工作流的情况。

然而,当需要以编程方式构建工作流时,命令式风格更为合适。例如,如果任务已预先定义,它们的执行顺序和依赖关系可能已以文本形式指定(可能来自遗留系统的迁移)。在这种情况下,您需要编排这些任务。这正是 Flyte 命令式工作流的用武之地,它允许您以编程方式构建工作流。

示例

首先定义 slopeintercept 任务:

import flytekit as fl

@fl.task
def slope(x: list[int], y: list[int]) -> float:
    sum_xy = sum([x[i] * y[i] for i in range(len(x))])
    sum_x_squared = sum([x[i] ** 2 for i in range(len(x))])
    n = len(x)
    return (n * sum_xy - sum(x) * sum(y)) / (n * sum_x_squared - sum(x) ** 2)


@fl.task
def intercept(x: list[int], y: list[int], slope: float) -> float:
    mean_x = sum(x) / len(x)
    mean_y = sum(y) / len(y)
    intercept = mean_y - slope * mean_x
    return intercept

创建命令式工作流:

imperative_wf = Workflow(name="imperative_workflow")

为工作流添加输入参数:

imperative_wf.add_workflow_input("x", list[int])
imperative_wf.add_workflow_input("y", list[int])

如需为工作流输入设置默认值,可创建启动计划

添加需要在工作流中触发的任务:

node_t1 = imperative_wf.add_entity(slope, x=imperative_wf.inputs["x"], y=imperative_wf.inputs["y"])
node_t2 = imperative_wf.add_entity(
    intercept, x=imperative_wf.inputs["x"], y=imperative_wf.inputs["y"], slope=node_t1.outputs["o0"]
)

最后添加工作流输出:

imperative_wf.add_workflow_output("wf_output", node_t2.outputs["o0"])

可通过以下方式本地执行工作流:

if __name__ == "__main__":
    print(f"运行 imperative_wf() {imperative_wf(x=[-3, 0, 3], y=[7, 4, -2])}")

也可以提供输入列表并从工作流获取输出列表:

wf_input_y = imperative_wf.add_workflow_input("y", list[str])
node_t3 = wf.add_entity(some_task, a=[wf.inputs["x"], wf_input_y])

wf.add_workflow_output(
    "list_of_outputs",
    [node_t1.outputs["o0"], node_t2.outputs["o0"]],
    python_type=list[str],
)

启动工作流

单个工作流视图(例如通过工作流列表中选择工作流进入),您可以点击右上角的Launch Workflow。这将打开工作流的New Execution对话框:

新执行对话框设置

顶部可配置:

左侧包含以下配置部分:

  • Inputs:工作流函数的输入参数将显示为可填写的字段
  • Settings
    • Execution name:自定义执行名称。若未指定,将自动生成名称
    • Overwrite cached outputs:布尔值。设为True时,本次执行将覆盖之前计算的所有缓存输出
    • Raw output data config:存储原始输出数据的远程路径前缀。默认情况下,工作流输出将写入内置元数据存储。您也可以在组织、项目域或单个执行级别指定自定义输出位置。本字段用于在工作流执行级别指定此设置。若填写本字段,将覆盖更高级别的设置。参数应为可写资源的URL(例如http://s3.amazonaws.com/my-bucket/)。详见原始数据存储
    • Max parallelism:可并行执行的工作流节点数量。若未指定,使用项目/域默认值。设为0表示无限制
    • Force interruptible:三态设置,用于覆盖本次特定执行的工作流可中断设置。未设置时使用工作流的默认设置。若启用则本次执行使用interruptible=True,若禁用则使用interruptible=False。详见可中断实例
    • Service account:本次执行使用的服务账号。若未指定则使用默认账号
  • Environment variables:本次工作流执行中任务可用的环境变量
  • Labels:应用于执行资源的标签
  • Notifications:为本次工作流执行配置的通知
  • Debug:用于调试的工作流执行详细信息

点击Launch即可启动工作流执行。系统将跳转至执行视图

查看工作流

工作流列表

工作流列表显示当前项目和域中的所有工作流:

工作流列表

您可以通过以下方式操作:

  • 通过名称搜索列表
  • 筛选仅显示已归档的工作流(使用归档图标归档图标 进行归档)

列表中的每个条目都提供以下基本信息:

  • 最后执行时间:该工作流最近一次执行的时间
  • 最近10次执行:显示最近10次执行的状态
  • 输入:工作流的输入类型
  • 输出:工作流的输出类型
  • 描述:工作流的功能说明

点击列表条目可跳转至具体工作流视图。

工作流视图

工作流视图提供特定工作流的详细信息:

工作流视图
该视图包含:

工作流版本列表

工作流版本列表显示该工作流的所有历史版本,并包含工作流结构的图形化展示:
工作流版本列表

工作流与任务描述

Flyte 支持使用 docstrings 进行代码文档注释。这些文档字符串会存储在控制平面,并在 UI 中为每个工作流或任务显示。

查看工作流执行

执行列表展示了项目与域组合下的所有执行记录。每个执行代表工作流(包含子工作流和独立任务)全部或部分的单次运行。可通过左侧导航栏的 Executions 链接访问该功能。

执行列表

域设置

本部分展示针对当前项目-域组合配置的域级设置,包含:

  • Security Context
  • Labels
  • Annotations
  • Raw output data config
  • Max parallelism

项目中的所有执行

对于当前项目与域中的每个执行,可查看以下信息:

  • 最近100次执行的统计图表
  • 开始时间:点击可查看单个执行详情
  • 工作流/任务:本次执行中运行的独立工作流独立任务
  • 版本:执行中使用的工作流或任务版本
  • 启动计划:用于启动该执行的启动计划
  • 调度:用于启动该执行的调度策略(如有)
  • 执行ID:执行唯一标识符
  • 状态:执行状态,可能值为 QUEUEDRUNNINGSUCCEEDEDFAILEDUNKNOWN
  • 持续时间:执行总耗时

执行视图

当启动工作流/任务或选择已完成执行时,将显示执行视图。每个执行代表工作流(包含子工作流和独立任务)全部或部分的单次运行。

执行视图

执行通常代表整个工作流的运行。但由于工作流由任务(有时包含子工作流)构成,且Flyte会独立缓存这些组件的输出结果,因此单独执行任务或子工作流有时更具实际意义。

执行视图顶部显示执行的详细概要信息,底部提供三个标签页展示不同维度的执行信息:节点时间线

节点

节点标签页是执行视图的默认界面,显示构成该执行的Flyte节点列表(Flyte节点可以是任务或(子)工作流)。

选择列表中的条目将打开右侧面板,展示该节点的详细信息:

执行视图 - 节点

侧面板顶部显示节点详细信息及重新运行任务按钮。下方包含四个标签页:执行输入输出任务

执行标签页提供该节点的执行详情,并支持以下操作:

  • 任务级监控:点击 View Utilization 可查看任务级监控信息
  • 日志:点击 Logs 下方的文本可查看日志,详见日志查看

输入输出标签页分别显示传入/传出该节点的数据。当节点为任务(而非子工作流)时,任务标签页将展示任务定义结构。

标签页以有向无环图形式展示执行的视觉化表示:

图

时间线

时间线标签页显示执行中各任务的时间分布可视化:

时间线

风险提示与免责声明
本文内容基于公开信息研究整理,不构成任何形式的投资建议。历史表现不应作为未来收益保证,市场存在不可预见的波动风险。投资者需结合自身财务状况及风险承受能力独立决策,并自行承担交易结果。作者及发布方不对任何依据本文操作导致的损失承担法律责任。市场有风险,投资须谨慎。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

船长Q

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值