Flyte工作流(Workflow)平台调研(八)——FlytePropeller核心源码走读

系列文章:

Flyte工作流(Workflow)平台调研(一)——整体架构

Flyte工作流(Workflow)平台调研(二)——核心概念说明

Flyte工作流(Workflow)平台调研(三)——核心组件原理

Flyte工作流(Workflow)平台调研(四)——服务部署

Flyte工作流(Workflow)平台调研(五)——扩展集成

Flyte工作流(Workflow)平台调研(六)——跟Ray框架对比

Flyte工作流(Workflow)平台调研(七)——FlyteAdmin核心源码走读

Flyte工作流(Workflow)平台调研(八)——FlytePropeller核心源码走读


正文:

前言

上一篇文章中整体介绍了Flyte项目,并且详细分析和走读了FlyteAdmin的源代码,解读了一个工作流到FlyteAdmin后端的业务转换流程。本文要详细分析和走读FlytePropeller的源代码,帮助大家了解工作流到了FlytePropeller后,在K8S中生效执行的全过程。

FlytePropeller源码

整体介绍

概述

FlytePropeller 是 Flyte 工作流执行框架的核心组件之一,负责管理和执行 Flyte 工作流。它通过与 Kubernetes 紧密集成,实现对FlyteAdmin提交过来的复杂任务 DAG(有向无环图)的调度和执行,并确保任务执行的可靠性和可扩展性。

上一篇文章讲到FlyteAdmin接收到工作流(Workflow)后,经过校验、转换等一系列操作把用户请求的工作流(Workflow)转化为一个可以被FlytePropeller识别和执行的FlyteWorkflow对象,这个对象是一个K8S的CRD(Custom Resource Definition)资源实例。本文就会详细介绍,FlytePropeller是怎么实现一个工作流专用的K8S Operator,通过 Kubernetes CRD 和控制器机制实现工作流的动态调度和执行。

项目架构

项目结构如下:

Flyte工作流(Workflow)平台调研(八)——FlytePropeller核心源码走读_第1张图片

代码的整体逻辑架构为:

Flyte工作流(Workflow)平台调研(八)——FlytePropeller核心源码走读_第2张图片

代码包功能简介:

  • compiler包,是提供给FlyteAdmin调用的功能,负责把用户的工作流(Workflow)请求转换为FlytePropeller能执行的CRD实例对象
  • apis包,定义FlytePropeller这个Operator的CRD,并且提供创建工作流CR的K8S API
  • client包,负责监听K8S中工作流CRD的CR生命周期变化,然后给controller下发调度资源的指令
  • controller包,FlytePropeller的核心功能,包括 Kubernetes 的控制器逻辑,监听 Workflow CR 的事件。以及工作流状态的调度、更新和同步逻辑。
  • plugins包,FlytePropeller 的插件系统,负责处理不同类型任务的执行逻辑。并提供了标准接口,方便扩展自定义任务类型。
  • webhook包,拦截对K8S原生资源(如Pod和Secrets)的操作请求,适配各种K8S集群(如原生K8S、AWS和GCP等云上的K8S集群)
  • leaderelection包,FlytePropeller 的多个实例(Pod)需要通过选主机制确保只有一个实例作为主节点,负责调度和管理工作流。其他 Pod 处于备用状态,等待接替主节点的角色,以实现高可用性和调度逻辑的一致性。

  • utils包,代码的公共工具包,避免循环调用

下面分别介绍代码的核心原理、功能和逻辑实现。

代码详解

核心原理

Kubernetes CRD 驱动

  • FlytePropeller 基于 Kubernetes 的 CRD(Custom Resource Definition)设计。每个 Flyte 工作流被表示为一个 Workflow CR 对象,存储在 Kubernetes 中。
  • FlytePropeller 作为控制器,监听和管理这些 CR 的生命周期,负责调度和执行其中定义的任务。

工作流 DAG 执行

  • Flyte 工作流以 DAG 的形式定义任务之间的依赖关系。
  • FlytePropeller 遍历 DAG,根据任务的依赖顺序逐步调度任务的执行。

任务容器化

  • 每个任务以容器的形式运行,利用 Kubernetes 的原生调度功能确保资源隔离和负载均衡。
  • FlytePropeller 会通过 Flyte plugins 调用不同类型的任务(如 Spark、Python 等),确保任务环境的灵活性。

状态管理与持久化

  • FlytePropeller 负责跟踪工作流的状态,包括任务的开始、执行和完成。
  • 工作流的状态数据会定期保存到 Flyte 的元数据存储(如K8S集群中的 etcd 或 FlyteAdmin的PostgreSQL),支持失败恢复和重试。

controller包功能详解

controller 包是 Flyte 项目中核心的控制器逻辑实现部分,负责管理和调度 Flyte 工作流的执行。它的主要职责是根据用户定义的 Flyte 工作流描述,将任务分解为更小的单元并调度执行,同时追踪其状态,确保任务的正确性和一致性。它的设计基于 Kubernetes 控制器模式,具有高扩展性和可靠性。

1. 从控制器的启动来看控制器的主要职责

flytepropeller/pkg/controller/controller.go是一个 Flyte 控制器的完整实现,它主要用于管理 Flyte 工作流(FlyteWorkflow)的生命周期。控制器与 Kubernetes 集群的资源交互,以实现工作流的自动化管理。下面是对该控制器实现的原理总结。

Flyte 控制器负责管理和执行 Flyte 工作流的任务,确保资源的一致性与正确性。具体来说,它通过监听 Kubernetes 中 FlyteWorkflow 资源的变化,自动执行相应的操作,如启动工作流、更新工作流状态、清理不再需要的资源等。

控制器启动的主要流程:

  • 初始化 Kubernetes 客户端和 FlyteWorkflow 客户端。
  • 创建必要的资源(如 CRD)。
  • 初始化存储、事件管理器、垃圾回收器等核心组件。
  • 设置领导选举,确保单实例工作。
  • 创建工作流和节点执行器。
  • 注册事件处理器,监听资源变化。
  • 启动控制器,开始处理工作流任务。

启动代码如下:

// 负责初始化控制器的所有依赖与核心组件
func New(ctx context.Context, cfg *config.Config, kubeClientset kubernetes.Interface, flytepropellerClientset clientset.Interface,
	flyteworkflowInformerFactory informers.SharedInformerFactory, informerFactory k8sInformers.SharedInformerFactory,
	kubeClient executors.Client, scope promutils.Scope) (*Controller, error) {

	adminClient, signalClient, authOpts, err := getAdminClient(ctx)
	if err != nil {
		logger.Errorf(ctx, "failed to initialize Admin client, err :%s", err.Error())
		return nil, err
	}

	sCfg := storage.GetConfig()
	if sCfg == nil {
		logger.Errorf(ctx, "Storage configuration missing.")
	}
    // 创建一个数据存储,用于管理工作流的元数据
	store, err := storage.NewDataStore(sCfg, scope.NewSubScope("metastore"))
	if err != nil {
		return nil, errors.Wrapf(err, "Failed to create Metadata storage")
	}
    // 负责将事件推送到 Flyte 的外部系统
	logger.Info(ctx, "Setting up event sink and recorder")
	eventSink, err := events.ConstructEventSink(ctx, events.GetConfig(ctx), scope.NewSubScope("event_sink"))
	if err != nil {
		return nil, errors.Wrapf(err, "Failed to create EventSink [%v], error %v", events.GetConfig(ctx).Type, err)
	}
    // 初始化垃圾回收器(GC)负责清理过期或无效的工作流
	gc, err := NewGarbageCollector(cfg, scope, clock.RealClock{}, kubeClientset.CoreV1().Namespaces(), flytepropellerClientset.FlyteworkflowV1alpha1())
	if err != nil {
		logger.Errorf(ctx, "failed to initialize GC for workflows")
		return nil, errors.Wrapf(err, "failed to initialize WF GC")
	}

	eventRecorder, err := utils.NewK8sEventRecorder(ctx, kubeClientset, controllerAgentName, cfg.PublishK8sEvents)
	if err != nil {
		logger.Errorf(ctx, "failed to event recorder %v", err)
		return nil, errors.Wrapf(err, "failed to initialize resource lock.")
	}
	controller := &Controller{
		metrics:    newControllerMetrics(scope),
		recorder:   eventRecorder,
		gc:         gc,
		numWorkers: cfg.Workers,
	}
    // 设置领导选举,确保在分布式环境中,只有一个控制器实例处于活动状态(通过 Kubernetes 的资源锁实现)
	lock, err := leader.NewResourceLock(kubeClientset.CoreV1(), kubeClientset.CoordinationV1(), eventRecorder, cfg.LeaderElection)
	if err != nil {
		logger.Errorf(ctx, "failed to initialize resource lock.")
		return nil, errors.Wrapf(err, "failed to initialize resource lock.")
	}

	if lock != nil {
		logger.Infof(ctx, "Creating leader elector for the controller.")
		controller.leaderElector, err = leader.NewLeaderElector(lock, cfg.LeaderElection, controller.onStartedLeading, func() {
			logger.Fatal(ctx, "Lost leader state. Shutting down.")
		})

		if err != nil {
			logger.Errorf(ctx, "failed to initialize leader elector.")
			return nil, errors.Wrapf(err, "failed to initialize leader elector.")
		}
	}

	// WE are disabling this as the metrics have high cardinality. Metrics seem to be emitted per pod and this has problems
	// when we create new pods
	// Set Client Metrics Provider
	// setClientMetricsProvider(scope.NewSubScope("k8s_client"))

	// obtain references to shared index informers for FlyteWorkflow.
	flyteworkflowInformer := flyteworkflowInformerFactory.Flyteworkflow().V1alpha1().FlyteWorkflows()
	controller.flyteworkflowSynced = flyteworkflowInformer.Informer().HasSynced

	podTemplateInformer := informerFactory.Core().V1().PodTemplates()

	// set default namespace for pod template store
	podNamespace, found := os.LookupEnv(podNamespaceEnvVar)
	if !found {
		podNamespace = podDefaultNamespace

你可能感兴趣的:(AI架构与工具学习之路,工作流,Workflow,AI编程,python,人工智能)