开发流程与模式
本文以一个完整的 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) | 更新 metadata、spec(不含 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