You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Operator-SDK(Go)定义含多Workload类型的K8s CRD及类型转换

使用Operator-SDK实现带动态Workload模板的自定义Operator

1. 定义支持多Workload类型的CRD Spec

核心思路是用runtime.RawExtension存储动态Workload对象,它能兼容任意K8s原生API对象并保留完整JSON结构,配合controller-gen注释生成正确的CRD。

代码实现

package api/v1alpha1

import (
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime"
)

// FooSpec 定义Foo资源的期望状态
type FooSpec struct {
    // Template 可传入Deployment、DaemonSet或StatefulSet对象
    // +kubebuilder:pruning:PreserveUnknownFields
    Template runtime.RawExtension `json:"template"`

    // 可选:预定义需要创建的Secret列表
    Secrets []SecretSpec `json:"secrets,omitempty"`
    // 可选:预定义需要创建的ConfigMap列表
    ConfigMaps []ConfigMapSpec `json:"configMaps,omitempty"`
}

// SecretSpec 自定义Secret的元数据和内容
type SecretSpec struct {
    Name string            `json:"name"`
    Data map[string][]byte `json:"data,omitempty"`
    StringData map[string]string `json:"stringData,omitempty"`
}

// ConfigMapSpec 自定义ConfigMap的元数据和内容
type ConfigMapSpec struct {
    Name string            `json:"name"`
    Data map[string]string `json:"data,omitempty"`
}

// FooStatus 定义Foo资源的实际状态
type FooStatus struct {
    Ready bool `json:"ready,omitempty"`
}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status

// Foo 是foos API的Schema定义
type Foo struct {
    metav1.TypeMeta   `json:",inline"`
    metav1.ObjectMeta `json:"metadata,omitempty"`

    Spec   FooSpec   `json:"spec,omitempty"`
    Status FooStatus `json:"status,omitempty"`
}

//+kubebuilder:object:root=true

// FooList 是Foo资源的列表定义
type FooList struct {
    metav1.TypeMeta `json:",inline"`
    metav1.ListMeta `json:"metadata,omitempty"`
    Items           []Foo `json:"items"`
}

func init() {
    SchemeBuilder.Register(&Foo{}, &FooList{})
}

关键说明

  • // +kubebuilder:pruning:PreserveUnknownFields必须添加在Template字段上,防止controller-gen生成CRD时修剪未知字段,保证Workload对象的完整性。

2. 在Reconciliation Loop中转换Template为实际Workload对象

分四步完成解析、类型转换、预处理和状态更新:

步骤1:解析RawExtension为Unstructured对象

先将RawExtension的JSON数据解析为unstructured.Unstructured,方便判断Workload类型:

import (
    "encoding/json"
    "fmt"
    "context"

    "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    appsv1 "k8s.io/api/apps/v1"
    corev1 "k8s.io/api/core/v1"
    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/client"

    myv1alpha1 "your-operator/api/v1alpha1"
)

func (r *FooReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    var foo myv1alpha1.Foo
    if err := r.Get(ctx, req.NamespacedName, &foo); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    // 解析Template为Unstructured对象
    var workloadUnstructured unstructured.Unstructured
    if err := json.Unmarshal(foo.Spec.Template.Raw, &workloadUnstructured); err != nil {
        return ctrl.Result{}, err
    }

步骤2:判断类型并转换为对应结构体

根据Unstructured对象的Kind字段,转换为Deployment/DaemonSet/StatefulSet的具体结构体:

var workload runtime.Object
    gvk := workloadUnstructured.GroupVersionKind()

    switch gvk.Kind {
    case "Deployment":
        var deploy appsv1.Deployment
        if err := r.Scheme.Convert(&workloadUnstructured, &deploy, nil); err != nil {
            return ctrl.Result{}, err
        }
        workload = &deploy
    case "DaemonSet":
        var ds appsv1.DaemonSet
        if err := r.Scheme.Convert(&workloadUnstructured, &ds, nil); err != nil {
            return ctrl.Result{}, err
        }
        workload = &ds
    case "StatefulSet":
        var sts appsv1.StatefulSet
        if err := r.Scheme.Convert(&workloadUnstructured, &sts, nil); err != nil {
            return ctrl.Result{}, err
        }
        workload = &sts
    default:
        return ctrl.Result{}, fmt.Errorf("不支持的Workload类型: %s", gvk.Kind)
    }

步骤3:预处理Workload(挂载资源+设置Pending状态)

先给Workload挂载预创建的Secret/ConfigMap,再通过不存在的nodeSelector让Pod暂时处于Pending状态:

// 给Pod模板挂载Secret/ConfigMap
    switch w := workload.(type) {
    case *appsv1.Deployment:
        // 添加Secret挂载
        w.Spec.Template.Spec.Volumes = append(w.Spec.Template.Spec.Volumes, corev1.Volume{
            Name: "my-secret",
            VolumeSource: corev1.VolumeSource{
                Secret: &corev1.SecretVolumeSource{
                    SecretName: "my-secret",
                },
            },
        })
        // 设置阻止调度的nodeSelector,让Pod处于Pending
        if w.Spec.Template.Spec.NodeSelector == nil {
            w.Spec.Template.Spec.NodeSelector = make(map[string]string)
        }
        w.Spec.Template.Spec.NodeSelector["foo.bar.com/operator-ready"] = "false"
    case *appsv1.DaemonSet:
        // 同理处理DaemonSet的ConfigMap挂载和Pending设置
        w.Spec.Template.Spec.Volumes = append(w.Spec.Template.Spec.Volumes, corev1.Volume{
            Name: "my-configmap",
            VolumeSource: corev1.VolumeSource{
                ConfigMap: &corev1.ConfigMapVolumeSource{
                    LocalObjectReference: corev1.LocalObjectReference{
                        Name: "my-configmap",
                    },
                },
            },
        })
        w.Spec.Template.Spec.NodeSelector = map[string]string{"foo.bar.com/operator-ready": "false"}
    case *appsv1.StatefulSet:
        // 同理处理StatefulSet
        w.Spec.Template.Spec.Volumes = append(w.Spec.Template.Spec.Volumes, corev1.Volume{
            Name: "my-secret",
            VolumeSource: corev1.VolumeSource{
                Secret: &corev1.SecretVolumeSource{
                    SecretName: "my-secret",
                },
            },
        })
        w.Spec.Template.Spec.NodeSelector = map[string]string{"foo.bar.com/operator-ready": "false"}
    }

    // 设置Workload的命名空间和OwnerReference,绑定到CR
    switch w := workload.(type) {
    case *appsv1.Deployment:
        w.Namespace = foo.Namespace
        ctrl.SetControllerReference(&foo, w, r.Scheme)
        if err := r.Create(ctx, w); err != nil {
            return ctrl.Result{}, err
        }
    case *appsv1.DaemonSet:
        w.Namespace = foo.Namespace
        ctrl.SetControllerReference(&foo, w, r.Scheme)
        if err := r.Create(ctx, w); err != nil {
            return ctrl.Result{}, err
        }
    case *appsv1.StatefulSet:
        w.Namespace = foo.Namespace
        ctrl.SetControllerReference(&foo, w, r.Scheme)
        if err := r.Create(ctx, w); err != nil {
            return ctrl.Result{}, err
        }
    }

步骤4:完成Reconciliation后解除Pending状态

当所有前置资源创建完成,更新CR状态为Ready,并移除Workload的阻止调度条件:

// 更新CR状态为就绪
    foo.Status.Ready = true
    if err := r.Status().Update(ctx, &foo); err != nil {
        return ctrl.Result{}, err
    }

    // 移除nodeSelector,允许Pod调度
    switch w := workload.(type) {
    case *appsv1.Deployment:
        delete(w.Spec.Template.Spec.NodeSelector, "foo.bar.com/operator-ready")
        if err := r.Update(ctx, w); err != nil {
            return ctrl.Result{}, err
        }
    case *appsv1.DaemonSet:
        delete(w.Spec.Template.Spec.NodeSelector, "foo.bar.com/operator-ready")
        if err := r.Update(ctx, w); err != nil {
            return ctrl.Result{}, err
        }
    case *appsv1.StatefulSet:
        delete(w.Spec.Template.Spec.NodeSelector, "foo.bar.com/operator-ready")
        if err := r.Update(ctx, w); err != nil {
            return ctrl.Result{}, err
        }
    }

    return ctrl.Result{}, nil
}

内容的提问来源于stack exchange,提问作者TheCoder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 23:55:54