Kubernetes Operator通过CRD分层设计实现工作流编排

一. 概述

本文采用了分层CRD的方式实现模块化操作,使高级别的CRD可以控制低级别的CRD。这种模式在云原生应用中非常有用,尤其是对于复杂的工作流程管理。

下面将实现一个模块化Operator示例,包括上层CRD和下层CRD的定义及控制器实现。

二. 设计概念

我们将创建两个CRD:

  1. WorkflowGroup (上层CRD) - 管理一组工作流的编排
  2. Workflow (下层CRD) - 处理单个工作流过程

这种设计允许我们定义一组相关的工作流程,并由上层CRD协调执行。

模块化设计优势

这种模块化设计有以下优势:

  1. 分层抽象:上层CRD提供了高级别的抽象,简化了运维操作
  2. 解耦工作流:不同类型的工作流可以独立定义和演进
  3. 可重用性:低级别的Workflow可以被不同的WorkflowGroup复用
  4. 自动化协调:顶层控制器负责整体流程,子控制器负责具体执行
  5. 灵活扩展:可以轻松添加新的工作流类型而不影响现有代码

三. 实现步骤

1.初始化项目

使用kubebuilder初始化项目:

mkdir -p workflow-operator && cd workflow-operator && go mod init github.com/rxg456/workflow-operator
kubebuilder init --domain rxg456.cn

2.创建CRD

2.1 创建下层CRD (Workflow)

kubebuilder create api --group workflow --version v1alpha1 --kind Workflow

2.2 创建上层CRD (WorkflowGroup)

kubebuilder create api --group workflow --version v1alpha1 --kind WorkflowGroup

3. 定义CRD

3.1 下层CRD定义 (Workflow)

workflow_types.go

// WorkflowStep 定义工作流中的步骤
type WorkflowStep struct {
    // 步骤名称
    Name string `json:"name"`
    // 步骤描述
    Description string `json:"description,omitempty"`
    // 步骤执行的命令
    Command string `json:"command,omitempty"`
    // 步骤依赖的其他步骤
    DependsOn []string `json:"dependsOn,omitempty"`
}

// Target 定义工作流执行的目标
type Target struct {
    // 目标名称
    Name string `json:"name"`
    // 目标IP地址
    IP string `json:"ip"`
    // 目标角色
    Role string `json:"role,omitempty"`
}

// WorkflowSpec 定义了 Workflow 的期望状态
type WorkflowSpec struct {
    // 工作流类型
    Type string `json:"type"`
    // 工作流步骤列表
    Steps []WorkflowStep `json:"steps"`
    // 工作流执行目标
    Targets []Target `json:"targets"`
    // 所属集群名称
    ClusterName string `json:"clusterName"`
    // 暂停工作流执行
    Paused bool `json:"paused,omitempty"`
}

// WorkflowStatus 定义 Workflow 的观察状态
type WorkflowStatus struct {
    // 工作流阶段
    Phase string `json:"phase,omitempty"`
    // 开始时间
    StartTime *metav1.Time `json:"startTime,omitempty"`
    // 完成时间
    CompletionTime *metav1.Time `json:"completionTime,omitempty"`
    // 步骤状态
    StepStatus map[string]string `json:"stepStatus,omitempty"`
    // 条件
    Conditions []metav1.Condition `json:"conditions,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.type`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"

// Workflow 是工作流定义
type Workflow struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    Spec   WorkflowSpec   `json:"spec,omitempty"`
    Status WorkflowStatus `json:"status,omitempty"`
}

3.2 上层CRD定义 (WorkflowGroup)

workflowgroup_types.go

// WorkflowGroupSpec 定义工作流组的期望状态
type WorkflowGroupSpec struct {
    // 工作流组类型
    Type string `json:"type"`
    // 目标集群名称
    ClusterName string `json:"clusterName"`
    // 顺序执行的工作流步骤
    WorkflowSteps []string `json:"workflowSteps"`
    // 目标节点
    Targets []Target `json:"targets"`
    // 暂停执行
    Paused bool `json:"paused,omitempty"`
}

// WorkflowReference 表示一个工作流引用
type WorkflowReference struct {
    // 工作流名称
    Name string `json:"name"`
    // 工作流类型
    Type string `json:"type"`
    // 工作流状态
    Status string `json:"status"`
}

// WorkflowGroupStatus 定义工作流组的观察状态
type WorkflowGroupStatus struct {
    // 阶段
    Phase string `json:"phase,omitempty"`
    // 创建的工作流列表
    Workflows []WorkflowReference `json:"workflows,omitempty"`
    // 开始时间
    StartTime *metav1.Time `json:"startTime,omitempty"`
    // 完成时间
    CompletionTime *metav1.Time `json:"completionTime,omitempty"`
    // 条件
    Conditions []metav1.Condition `json:"conditions,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.type`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"

// WorkflowGroup 是工作流组定义
type WorkflowGroup struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    Spec   WorkflowGroupSpec   `json:"spec,omitempty"`
    Status WorkflowGroupStatus `json:"status,omitempty"`
}

4. 实现控制器

4.1 下层控制器 (Workflow Controller)

workflow_controller.go

// +kubebuilder:rbac:groups=workflow.rxg456.cn,resources=workflows,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=workflow.rxg456.cn,resources=workflows/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=workflow.rxg456.cn,resources=workflows/finalizers,verbs=update

// Reconcile 处理 Workflow 资源
func (r *WorkflowReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	logger := log.FromContext(ctx)

	// 获取 Workflow 实例
	var workflow workflowv1alpha1.Workflow
	if err := r.Get(ctx, req.NamespacedName, &workflow); err != nil {
		if apierrors.IsNotFound(err) {
			// 已删除
			return ctrl.Result{}, nil
		}
		logger.Error(err, "无法获取 Workflow")
		return ctrl.Result{}, err
	}
	// 检查是否需要初始化状态
	if workflow.Status.Phase == "" {
		logger.Info("初始化工作流状态")
		now := metav1.Now()
		workflow.Status.Phase = "Pending"
		workflow.Status.StartTime = &now
		workflow.Status.StepStatus = make(map[string]string)

		// 初始化所有步骤状态为 Pending
		for _, step := range workflow.Spec.Steps {
			workflow.Status.StepStatus[step.Name] = "Pending"
		}

		if err := r.Status().Update(ctx, &workflow); err != nil {
			logger.Error(err, "无法更新工作流状态")
			return ctrl.Result{}, err
		}
		return ctrl.Result{Requeue: true}, nil
	}

	// 如果工作流已暂停,跳过处理
	if workflow.Spec.Paused {
		logger.Info("工作流被暂停", "workflow", workflow.Name)
		return ctrl.Result{}, nil
	}

	// 如果工作流已完成或失败,跳过处理
	if workflow.Status.Phase == "Completed" || workflow.Status.Phase == "Failed" {
		return ctrl.Result{}, nil
	}

	// 设置正在处理状态
	if workflow.Status.Phase == "Pending" {
		workflow.Status.Phase = "Running"
		if err := r.Status().Update(ctx, &workflow); err != nil {
			logger.Error(err, "无法更新工作流状态")
			return ctrl.Result{}, err
		}
		return ctrl.Result{Requeue: true}, nil
	}

	// 处理工作流步骤
	allStepsCompleted := true
	for _, step := range workflow.Spec.Steps {
		// 检查依赖步骤是否完成
		canExecute := true
		for _, dep := range step.DependsOn {
			if workflow.Status.StepStatus[dep] != "Completed" {
				canExecute = false
				break
			}
		}

		if !canExecute {
			allStepsCompleted = false
			continue
		}

		// 如果步骤处于待处理状态
		if workflow.Status.StepStatus[step.Name] == "Pending" {
			logger.Info("开始执行步骤", "step", step.Name)

			// 设置步骤状态为运行中
			workflow.Status.StepStatus[step.Name] = "Running"
			if err := r.Status().Update(ctx, &workflow); err != nil {
				logger.Error(err, "无法更新步骤状态")
				return ctrl.Result{}, err
			}

			// 在实际实现中,这里会执行实际的步骤逻辑请求二开的ansible API
			// 为了演示,模拟步骤执行
			go r.executeStep(&workflow, step, ctx)

			// 重新入队等待步骤完成
			return ctrl.Result{RequeueAfter: time.Second * 10}, nil
		} else if workflow.Status.StepStatus[step.Name] == "Running" {
			// 还有运行中的步骤
			allStepsCompleted = false
		}
	}

	// 检查是否所有步骤都已完成
	if allStepsCompleted && workflow.Status.Phase == "Running" {
		logger.Info("所有步骤已完成", "workflow", workflow.Name)
		now := metav1.Now()
		workflow.Status.Phase = "Completed"
		workflow.Status.CompletionTime = &now

		if err := r.Status().Update(ctx, &workflow); err != nil {
			logger.Error(err, "无法更新工作流状态")
			return ctrl.Result{}, err
		}
	}

	return ctrl.Result{RequeueAfter: time.Second * 30}, nil
}

// executeStep 模拟执行步骤
func (r *WorkflowReconciler) executeStep(workflow *workflowv1alpha1.Workflow, step workflowv1alpha1.WorkflowStep, ctx context.Context) {
	logger := log.FromContext(ctx)
	logger.Info("模拟执行步骤")

	// 模拟步骤执行时间
	time.Sleep(time.Second * 5)

	// 获取最新的工作流状态
	var updatedWorkflow workflowv1alpha1.Workflow
	if err := r.Get(ctx, types.NamespacedName{Name: workflow.Name, Namespace: workflow.Namespace}, &updatedWorkflow); err != nil {
		logger.Error(err, "获取工作流失败")
		return
	}

	// 更新步骤状态为完成
	updatedWorkflow.Status.StepStatus[step.Name] = "Completed"

	if err := r.Status().Update(ctx, &updatedWorkflow); err != nil {
		logger.Error(err, "更新步骤状态失败")
	}
}

4.2 上层控制器 (WorkflowGroup Controller)

workflowgroup_controller.go

// +kubebuilder:rbac:groups=workflow.rxg456.cn,resources=workflowgroups,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=workflow.rxg456.cn,resources=workflowgroups/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=workflow.rxg456.cn,resources=workflowgroups/finalizers,verbs=update
// +kubebuilder:rbac:groups=workflow.rxg456.cn,resources=workflows,verbs=get;list;watch;create;update;patch;delete

// 上面第四行赋予控制器对workflows资源的完全控制权

// Reconcile 处理 WorkflowGroup 资源
func (r *WorkflowGroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	logger := log.FromContext(ctx)

	// 获取 WorkflowGroup 实例
	var workflowGroup workflowv1alpha1.WorkflowGroup
	if err := r.Get(ctx, req.NamespacedName, &workflowGroup); err != nil {
		if apierrors.IsNotFound(err) {
			// 已删除
			return ctrl.Result{}, nil
		}
		logger.Error(err, "无法获取 WorkflowGroup")
		return ctrl.Result{}, err
	}

	// 初始化状态
	if workflowGroup.Status.Phase == "" {
		now := metav1.Now()
		workflowGroup.Status.Phase = "Pending"
		workflowGroup.Status.StartTime = &now

		if err := r.Status().Update(ctx, &workflowGroup); err != nil {
			logger.Error(err, "无法更新工作流组状态")
			return ctrl.Result{}, err
		}
		return ctrl.Result{Requeue: true}, nil
	}

	// 如果工作流组已暂停,跳过处理
	if workflowGroup.Spec.Paused {
		logger.Info("工作流组被暂停", "workflowGroup", workflowGroup.Name)
		return ctrl.Result{}, nil
	}

	// 如果工作流组已完成或失败,跳过处理
	if workflowGroup.Status.Phase == "Completed" || workflowGroup.Status.Phase == "Failed" {
		return ctrl.Result{}, nil
	}

	// 设置为处理中状态
	if workflowGroup.Status.Phase == "Pending" {
		workflowGroup.Status.Phase = "Running"
		if err := r.Status().Update(ctx, &workflowGroup); err != nil {
			logger.Error(err, "无法更新工作流组状态")
			return ctrl.Result{}, err
		}
		return ctrl.Result{Requeue: true}, nil
	}

	// 检查是否所有工作流都已创建
	allWorkflowsCreated := true
	for _, stepType := range workflowGroup.Spec.WorkflowSteps {
		workflowFound := false
		for _, wf := range workflowGroup.Status.Workflows {
			if wf.Type == stepType {
				workflowFound = true
				break
			}
		}

		if !workflowFound {
			allWorkflowsCreated = false

			// 创建新的工作流
			workflow := r.constructWorkflow(workflowGroup, stepType)
			if err := r.Create(ctx, workflow); err != nil {
				logger.Error(err, "创建工作流失败", "type", stepType)
				return ctrl.Result{}, err
			}

			// 更新工作流组状态
			workflowRef := workflowv1alpha1.WorkflowReference{
				Name:   workflow.Name,
				Type:   stepType,
				Status: "Pending",
			}
			workflowGroup.Status.Workflows = append(workflowGroup.Status.Workflows, workflowRef)

			if err := r.Status().Update(ctx, &workflowGroup); err != nil {
				logger.Error(err, "更新工作流组状态失败")
				return ctrl.Result{}, err
			}

			return ctrl.Result{Requeue: true}, nil
		}
	}

	// 检查所有工作流的状态
	if allWorkflowsCreated {
		allCompleted := true
		anyFailed := false

		for i, wfRef := range workflowGroup.Status.Workflows {
			var workflow workflowv1alpha1.Workflow
			if err := r.Get(ctx, types.NamespacedName{Name: wfRef.Name, Namespace: workflowGroup.Namespace}, &workflow); err != nil {
				if !errors.IsNotFound(err) {
					logger.Error(err, "获取工作流失败", "workflow", wfRef.Name)
					return ctrl.Result{}, err
				}
				// 工作流已被删除,将状态设为失败
				workflowGroup.Status.Workflows[i].Status = "Failed"
				anyFailed = true
			} else {
				// 更新工作流引用状态
				workflowGroup.Status.Workflows[i].Status = workflow.Status.Phase

				// 检查工作流是否已完成
				if workflow.Status.Phase != "Completed" {
					allCompleted = false
				}

				// 检查工作流是否失败
				if workflow.Status.Phase == "Failed" {
					anyFailed = true
				}
			}
		}

		statusChanged := false

		// 更新状态
		if anyFailed && workflowGroup.Status.Phase != "Failed" {
			workflowGroup.Status.Phase = "Failed"
			now := metav1.Now()
			workflowGroup.Status.CompletionTime = &now
			statusChanged = true
		} else if allCompleted && workflowGroup.Status.Phase != "Completed" {
			workflowGroup.Status.Phase = "Completed"
			now := metav1.Now()
			workflowGroup.Status.CompletionTime = &now
			statusChanged = true
		}

		if statusChanged {
			if err := r.Status().Update(ctx, &workflowGroup); err != nil {
				logger.Error(err, "更新工作流组状态失败")
				return ctrl.Result{}, err
			}
		}
	}

	return ctrl.Result{RequeueAfter: time.Second * 30}, nil
}

// constructWorkflow 创建新的工作流
func (r *WorkflowGroupReconciler) constructWorkflow(group workflowv1alpha1.WorkflowGroup, workflowType string) *workflowv1alpha1.Workflow {
	// 创建唯一的工作流名称
	workflowName := fmt.Sprintf("%s-%s-%s", group.Name, workflowType, uuid.New().String()[:8])

	// 根据工作流类型创建不同的步骤
	var steps []workflowv1alpha1.WorkflowStep

	switch workflowType {
	case "pre-check":
		steps = []workflowv1alpha1.WorkflowStep{
			{
				Name:        "validate-nodes",
				Description: "验证节点配置",
				Command:     "validate-nodes.sh",
			},
			{
				Name:        "check-network",
				Description: "检查网络连接",
				Command:     "check-network.sh",
				DependsOn:   []string{"validate-nodes"},
			},
		}
	case "setup":
		steps = []workflowv1alpha1.WorkflowStep{
			{
				Name:        "install-packages",
				Description: "安装必要软件包",
				Command:     "install-packages.sh",
			},
			{
				Name:        "configure-nodes",
				Description: "配置节点",
				Command:     "configure-nodes.sh",
				DependsOn:   []string{"install-packages"},
			},
		}
	case "post-check":
		steps = []workflowv1alpha1.WorkflowStep{
			{
				Name:        "verify-installation",
				Description: "验证安装结果",
				Command:     "verify-installation.sh",
			},
		}
	}

	// 创建工作流对象
	workflow := &workflowv1alpha1.Workflow{
		ObjectMeta: metav1.ObjectMeta{
			Name:      workflowName,
			Namespace: group.Namespace,
			Labels: map[string]string{
				"workflow.rxg456.cn/group-name":    group.Name,
				"workflow.rxg456.cn/workflow-type": workflowType,
			},
			OwnerReferences: []metav1.OwnerReference{
				{
					APIVersion: group.APIVersion,
					Kind:       group.Kind,
					Name:       group.Name,
					UID:        group.UID,
					Controller: pointer.Bool(true),
				},
			},
		},
		Spec: workflowv1alpha1.WorkflowSpec{
			Type:        workflowType,
			Steps:       steps,
			Targets:     group.Spec.Targets,
			ClusterName: group.Spec.ClusterName,
		},
	}

	return workflow
}

5. 部署测试

不演示生产环境!

make manifests  # 生成 CRD YAML
make install    # 部署 CRD 本地集群
make run        # 本地运行 Operator

6. CRD示例

6.1 WorkflowGroup示例

apiVersion: workflow.rxg456.cn/v1alpha1
kind: WorkflowGroup
metadata:
  name: workflowgroup-sample
  namespace: default
spec:
  type: cluster-expansion
  clusterName: test-cluster
  workflowSteps:
    - pre-check
    - setup
    - post-check
  targets:
    - name: node-1
      ip: 192.168.1.101
      role: Master
    - name: node-2
      ip: 192.168.1.102
      role: Worker
    - name: node-3
      ip: 192.168.1.103
      role: Worker

6.2 生成的Workflow示例

─➤  kubectl get Workflow
NAME                                       TYPE         PHASE       AGE
workflowgroup-sample-post-check-2ea7f13b   post-check   Completed   11s
workflowgroup-sample-pre-check-150e5473    pre-check    Completed   11s
workflowgroup-sample-setup-ca19681d        setup        Completed   11s

当应用上面的WorkflowGroup后,控制器会自动创建类似下面3个的Workflow:

apiVersion: workflow.rxg456.cn/v1alpha1
kind: Workflow
metadata:
  name: workflowgroup-sample-pre-check-150e5473
  namespace: default
  ownerReferences:
  - apiVersion: workflow.rxg456.cn/v1alpha1
    controller: true
    kind: WorkflowGroup
    name: workflowgroup-sample
    uid: 451dd666-07dd-4697-9e43-9eee393e0453
  uid: 9c7ee474-c914-4e83-9c3c-a8c4f164859b
spec:
  clusterName: test-cluster
  steps:
  - command: validate-nodes.sh
    description: 验证节点配置
    name: validate-nodes
  - command: check-network.sh
    dependsOn:
    - validate-nodes
    description: 检查网络连接
    name: check-network
  targets:
  - ip: 192.168.1.101
    name: node-1
    role: Master
  - ip: 192.168.1.102
    name: node-2
    role: Worker
  - ip: 192.168.1.103
    name: node-3
    role: Worker
  type: pre-check
apiVersion: workflow.rxg456.cn/v1alpha1
kind: Workflow
metadata:
  name: workflowgroup-sample-setup-ca19681d
  namespace: default
  ownerReferences:
  - apiVersion: workflow.rxg456.cn/v1alpha1
    controller: true
    kind: WorkflowGroup
    name: workflowgroup-sample
    uid: 451dd666-07dd-4697-9e43-9eee393e0453
  uid: 35aa7655-52cf-4490-8c78-79442eefad47
spec:
  clusterName: test-cluster
  steps:
  - command: install-packages.sh
    description: 安装必要软件包
    name: install-packages
  - command: configure-nodes.sh
    dependsOn:
    - install-packages
    description: 配置节点
    name: configure-nodes
  targets:
  - ip: 192.168.1.101
    name: node-1
    role: Master
  - ip: 192.168.1.102
    name: node-2
    role: Worker
  - ip: 192.168.1.103
    name: node-3
    role: Worker
  type: setup
apiVersion: workflow.rxg456.cn/v1alpha1
kind: Workflow
metadata:
  name: workflowgroup-sample-post-check-2ea7f13b
  namespace: default
  ownerReferences:
  - apiVersion: workflow.rxg456.cn/v1alpha1
    controller: true
    kind: WorkflowGroup
    name: workflowgroup-sample
    uid: 451dd666-07dd-4697-9e43-9eee393e0453
  uid: ca09ad0b-026b-4c6b-b319-62d797a26baf
spec:
  clusterName: test-cluster
  steps:
  - command: verify-installation.sh
    description: 验证安装结果
    name: verify-installation
  targets:
  - ip: 192.168.1.101
    name: node-1
    role: Master
  - ip: 192.168.1.102
    name: node-2
    role: Worker
  - ip: 192.168.1.103
    name: node-3
    role: Worker
  type: post-check

7. Kubernetes所有者引用机制解析

WorkflowGroup 删除时会同时删除对应的 Workflow,这是由 Kubernetes 的所有者引用(Owner References)机制控制的,而非控制器代码中的显式删除逻辑。

7.1 关键代码

func (r *WorkflowGroupReconciler) constructWorkflow(...) 方法中,这段代码设置了关键的所有者引用关系:

            OwnerReferences: []metav1.OwnerReference{
                {
                    APIVersion: group.APIVersion,
                    Kind:       group.Kind,
                    Name:       group.Name,
                    UID:        group.UID,
                    Controller: pointer.Bool(true),
                },
            },

7.2 工作原理

  1. 所有者引用机制:当创建 Workflow 时,上述代码将 WorkflowGroup 设置为 Workflow 的所有者
  2. Controller 标志Controller: pointer.Bool(true) 表示这是一个控制器关系,这强化了级联删除行为
  3. 垃圾回收控制器:Kubernetes 内置的垃圾回收控制器会自动检测并处理这种关系
  4. 级联删除:当WorkflowGroup被删除时,Kubernetes 会自动删除所有将其设为所有者的 Workflow 对象

7.3 技术说明

这种机制在 Kubernetes 中被称为"垃圾回收",有两种模式:

  • 前台级联删除:先删除子资源,再删除父资源
  • 后台级联删除:异步删除子资源

通过设置 Controller: true 采用了一种更强的依赖关系,确保子资源(Workflow)的生命周期完全绑定到父资源(WorkflowGroup)。

这种设计是 Kubernetes 资源管理的最佳实践,确保资源之间的依赖关系明确且自动处理,无需在控制器中编写额外的清理代码。

四. 总结

以上简单示例展示了如何使用Kubernetes Operator模式来实现模块化的工作流程管理。通过分层CRD的设计可以将复杂操作抽象为高级别的任务组,并自动分解为可管理的低级别任务。

可以基于此简单示例,根据实际需求进一步扩展功能,如添加更多的工作流类型、增强状态报告、添加错误处理和重试逻辑等。

以上代码示例:https://github.com/rxg456/workflow-operator

posted @ 2025-03-09 16:51  rxg456  阅读(81)  评论(0)    收藏  举报