基于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
相关产品推荐
相关产品推荐

