Kubernetes Operator通过CRD分层设计实现工作流编排
一. 概述
本文采用了分层CRD的方式实现模块化操作,使高级别的CRD可以控制低级别的CRD。这种模式在云原生应用中非常有用,尤其是对于复杂的工作流程管理。
下面将实现一个模块化Operator示例,包括上层CRD和下层CRD的定义及控制器实现。
二. 设计概念
我们将创建两个CRD:
- WorkflowGroup (上层CRD) - 管理一组工作流的编排
- Workflow (下层CRD) - 处理单个工作流过程
这种设计允许我们定义一组相关的工作流程,并由上层CRD协调执行。
模块化设计优势
这种模块化设计有以下优势:
- 分层抽象:上层CRD提供了高级别的抽象,简化了运维操作
- 解耦工作流:不同类型的工作流可以独立定义和演进
- 可重用性:低级别的Workflow可以被不同的WorkflowGroup复用
- 自动化协调:顶层控制器负责整体流程,子控制器负责具体执行
- 灵活扩展:可以轻松添加新的工作流类型而不影响现有代码
三. 实现步骤
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 工作原理
- 所有者引用机制:当创建 Workflow 时,上述代码将 WorkflowGroup 设置为 Workflow 的所有者
- Controller 标志:
Controller: pointer.Bool(true)
表示这是一个控制器关系,这强化了级联删除行为 - 垃圾回收控制器:Kubernetes 内置的垃圾回收控制器会自动检测并处理这种关系
- 级联删除:当
WorkflowGroup
被删除时,Kubernetes 会自动删除所有将其设为所有者的Workflow
对象
7.3 技术说明
这种机制在 Kubernetes 中被称为"垃圾回收",有两种模式:
- 前台级联删除:先删除子资源,再删除父资源
- 后台级联删除:异步删除子资源
通过设置 Controller: true
采用了一种更强的依赖关系,确保子资源(Workflow
)的生命周期完全绑定到父资源(WorkflowGroup
)。
这种设计是 Kubernetes 资源管理的最佳实践,确保资源之间的依赖关系明确且自动处理,无需在控制器中编写额外的清理代码。
四. 总结
以上简单示例展示了如何使用Kubernetes Operator模式来实现模块化的工作流程管理。通过分层CRD的设计可以将复杂操作抽象为高级别的任务组,并自动分解为可管理的低级别任务。
可以基于此简单示例,根据实际需求进一步扩展功能,如添加更多的工作流类型、增强状态报告、添加错误处理和重试逻辑等。