跳到主要内容

开发流程与模式

本文以一个完整的 Application CRD 为例,介绍从资源定义到 Reconciler 实现的标准开发流程,以及 Finalizer、Status 更新等常见开发模式。

CRD 定义

定义 Spec 与 Status

// api/v1alpha1/application_types.go

// ApplicationSpec 定义期望状态
type ApplicationSpec struct {
// Image 是应用的容器镜像
Image string `json:"image"`

// Replicas 是期望副本数,默认为 1
// +kubebuilder:default=1
// +kubebuilder:validation:Minimum=0
Replicas int32 `json:"replicas,omitempty"`

// Enabled 控制是否启用该应用
// +kubebuilder:default=true
Enabled bool `json:"enabled,omitempty"`
}

// ApplicationStatus 定义实际状态
type ApplicationStatus struct {
// Ready 表示 Deployment 是否就绪
Ready bool `json:"ready,omitempty"`

// ReadyReplicas 当前就绪的副本数
ReadyReplicas int32 `json:"readyReplicas,omitempty"`

// Conditions 记录状态变更历史
Conditions []metav1.Condition `json:"conditions,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Ready",type="boolean",JSONPath=".status.ready"
// +kubebuilder:printcolumn:name="Image",type="string",JSONPath=".spec.image"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
type Application struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec ApplicationSpec `json:"spec,omitempty"`
Status ApplicationStatus `json:"status,omitempty"`
}

修改 types.go 后必须重新生成代码和清单:

make generate && make manifests

常用 Marker 注释

Marker说明
+kubebuilder:object:root=true标记为 CRD 根对象
+kubebuilder:subresource:status启用 Status 子资源(Status 更新走独立接口)
+kubebuilder:default=<val>字段默认值(写入 CRD validation)
+kubebuilder:validation:Minimum=<n>数值最小值校验
+kubebuilder:printcolumn:...kubectl get 时额外显示的列
+kubebuilder:rbac:groups=...,resources=...,verbs=...声明 RBAC 权限,make manifests 自动生成 ClusterRole

Reconciler 实现

标准三段式结构

// internal/controller/application_controller.go

const applicationFinalizer = "apps.example.com/finalizer"

func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)

// 第一段:查询对象,处理不存在的情况
app := &appsv1alpha1.Application{}
if err := r.Get(ctx, req.NamespacedName, app); err != nil {
// 对象已被删除,忽略即可(Finalizer 清理已在对象删除前完成)
return ctrl.Result{}, client.IgnoreNotFound(err)
}

// 第二段:检查 DeletionTimestamp,处理删除逻辑
if !app.DeletionTimestamp.IsZero() {
return r.handleDeletion(ctx, app)
}

// 确保 Finalizer 存在
if !controllerutil.ContainsFinalizer(app, applicationFinalizer) {
controllerutil.AddFinalizer(app, applicationFinalizer)
if err := r.Update(ctx, app); err != nil {
return ctrl.Result{}, err
}
// 更新 metadata 后返回,等待下次 reconcile 继续处理
return ctrl.Result{}, nil
}

// 第三段:正常对账逻辑
return r.reconcileNormal(ctx, app)
}

Finalizer 模式

Finalizer 确保在 K8s 对象真正删除前,Controller 能完成外部资源清理:

func (r *ApplicationReconciler) handleDeletion(ctx context.Context, app *appsv1alpha1.Application) (ctrl.Result, error) {
log := log.FromContext(ctx)

if controllerutil.ContainsFinalizer(app, applicationFinalizer) {
// 执行清理逻辑(删除外部资源、通知下游系统等)
if err := r.cleanupExternalResources(ctx, app); err != nil {
log.Error(err, "清理外部资源失败")
return ctrl.Result{}, err
}

// 清理完成,移除 Finalizer,K8s 才会真正删除对象
controllerutil.RemoveFinalizer(app, applicationFinalizer)
if err := r.Update(ctx, app); err != nil {
return ctrl.Result{}, err
}
}

return ctrl.Result{}, nil
}

func (r *ApplicationReconciler) cleanupExternalResources(ctx context.Context, app *appsv1alpha1.Application) error {
// 在这里删除外部资源(云 API、数据库记录等)
return nil
}

Finalizer 生命周期:

创建对象 → AddFinalizer → metadata.finalizers: ["apps.example.com/finalizer"]

kubectl delete → DeletionTimestamp 被设置(对象不会立即消失)

Reconciler 检测到 DeletionTimestamp → cleanupExternalResources()

RemoveFinalizer → metadata.finalizers: [] → K8s 真正删除对象

Status 更新

Status 必须通过 Status 子资源接口更新,不能混用 r.Update()

func (r *ApplicationReconciler) reconcileNormal(ctx context.Context, app *appsv1alpha1.Application) (ctrl.Result, error) {
// 对账业务逻辑(此处以同步 Deployment 为例)
deploy := &appsv1.Deployment{}
err := r.Get(ctx, types.NamespacedName{Name: app.Name, Namespace: app.Namespace}, deploy)

// 根据实际状态更新 Status
patch := client.MergeFrom(app.DeepCopy())
if err != nil || deploy.Status.ReadyReplicas == 0 {
app.Status.Ready = false
app.Status.ReadyReplicas = 0
} else {
app.Status.Ready = deploy.Status.ReadyReplicas >= app.Spec.Replicas
app.Status.ReadyReplicas = deploy.Status.ReadyReplicas
}

// 使用 Status().Patch() 只更新 Status 字段
if err := r.Status().Patch(ctx, app, patch); err != nil {
return ctrl.Result{}, err
}

// 定期重新对账,保证最终一致性
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
}

r.Update() vs r.Status().Update()

方法更新内容场景
r.Update(ctx, obj)更新 metadataspec(不含 status)添加/删除 Finalizer、更新 Annotation
r.Status().Update(ctx, obj)只更新 status 子资源汇报 Ready 状态、更新 Conditions
r.Status().Patch(ctx, obj, patch)只 patch status,减少冲突推荐替代 Status().Update()
警告

混用 r.Update() 更新 Status 字段在启用了 Status 子资源后不会生效,Status 变更会被静默丢弃。

声明 RBAC 权限

用 marker 注释声明权限,make manifests 自动生成 ClusterRole:

// +kubebuilder:rbac:groups=apps.example.com,resources=applications,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=apps.example.com,resources=applications/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=apps.example.com,resources=applications/finalizers,verbs=update
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete

func (r *ApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&appsv1alpha1.Application{}).
// 同时 Watch Deployment,Deployment 变化也触发 Application reconcile
Owns(&appsv1.Deployment{}).
Complete(r)
}

Webhook(可选)

Webhook 用于在资源写入前做校验(Validating)或自动补全(Defaulting/Mutating):

# 生成 Webhook 骨架
kubebuilder create webhook \
--group apps \
--version v1alpha1 \
--kind Application \
--defaulting \ # 生成 Defaulter 接口(Mutating)
--programmatic-validation # 生成 Validator 接口(Validating)

生成的 api/v1alpha1/application_webhook.go 中实现两个接口:

// Default 实现 Defaulter 接口(自动填充默认值)
func (r *Application) Default() {
if r.Spec.Replicas == 0 {
r.Spec.Replicas = 1
}
}

// ValidateCreate / ValidateUpdate / ValidateDelete 实现 Validator 接口
func (r *Application) ValidateCreate() (admission.Warnings, error) {
if r.Spec.Image == "" {
return nil, fmt.Errorf("spec.image 不能为空")
}
return nil, nil
}

Webhook 需要 TLS 证书,本地开发时可用 cert-manager 或跳过。

完整开发流程

# 1. 初始化项目
kubebuilder init --domain example.com --repo github.com/example/my-operator

# 2. 创建 API + Controller
kubebuilder create api --group apps --version v1alpha1 --kind Application \
--controller=true --resource=true

# 3. 编辑 api/v1alpha1/application_types.go 定义 Spec/Status
vim api/v1alpha1/application_types.go

# 4. 生成 deepcopy 代码和 CRD 清单
make generate && make manifests

# 5. 安装 CRD,本地运行调试
make install
make run

# 6. 在另一个终端创建测试资源
kubectl apply -f - <<EOF
apiVersion: apps.example.com/v1alpha1
kind: Application
metadata:
name: demo
spec:
image: nginx:latest
replicas: 2
EOF

# 7. 观察 Status 变化
kubectl get application demo -w

# 8. 构建镜像并部署到集群
make docker-buildx IMG=my-registry/my-operator:v0.1.0
make docker-push IMG=my-registry/my-operator:v0.1.0
make deploy IMG=my-registry/my-operator:v0.1.0

# 9. 生成可分发安装包
make build-installer IMG=my-registry/my-operator:v0.1.0

进一步阅读