随着云原生技术的成熟,Kubernetes (K8s) 已成为事实上的基础设施操作系统。然而,原生的 K8s 资源(如 Deployment、Service)主要关注通用型负载的编排。在处理复杂的业务逻辑(如数据库高可用切换、分布式任务调度、自定义中间件管理)时,直接使用 YAML 配置往往力不从心。此时,Kubernetes Operator 模式 应运而生。学习地址:pan.baidu.com/s/1WwerIZ_elz_FyPKqXAiZCA?pwd=waug
本文将带你深入 Operator 开发的核心战场,从定义自定义资源(CRD)到编写控制循环,手把手构建一个企业级的 Operator。
一、 架构设计理念:为何需要 Operator?
Operator 基于 Kubernetes 控制器模式。它通过"监控实际状态 -> 调整偏差 -> 使实际状态趋向期望状态"的无限循环来管理工作负载。
- CRD (Custom Resource Definition) :扩展 K8s API,声明一种新的资源类型(如
MySQLCluster)。 - Controller:业务逻辑的大脑,监听 CRD 的变化并执行操作。
二、 实战第一步:定义 CRD
假设我们要开发一个简单的 WebApp 资源,它能自动管理 Deployment 并根据副本数配置 HPA。首先,我们需要定义 CRD。
crd/webapp.yaml
yaml
复制
yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: webapps.example.com # 资源名称,复数形式
spec:
group: example.com # API 组名
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
replicas:
type: integer
image:
type: string
port:
type: integer
status:
type: object
properties:
availableReplicas:
type: integer
scope: Namespaced
names:
plural: webapps
singular: webapp
kind: WebApp
shortNames:
- wa
应用该 YAML 后,K8s 集群便识别了 WebApp 这种新资源。
三、 实战核心:使用 Kubebuilder 开发 Controller
虽然手写 client-go 也是可行的,但在企业级开发中,我们强烈推荐使用 Kubebuilder 或 Operator SDK,它们提供了脚手架和代码生成工具,极大地提升了效率。
1. 初始化项目
bash
复制
css
# 安装 kubebuilder 后
kubebuilder init --domain example.com --repo myapp/operator
kubebuilder create api --group webapp --version v1 --kind WebApp
2. 编写 Reconcile 逻辑
这是 Operator 的灵魂。我们需要在 controllers/webapp_controller.go 中实现 Reconcile 方法。该方法的任务是:获取 WebApp 实例,查看是否有对应的 Deployment,没有则创建,有则更新。
go
复制
go
package controllers
import (
"context"
"fmt"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
myappv1 "myapp/operator/api/v1"
)
// 定义注解,用于 OwnerReference
const finalizerName = "webapp.finalizer"
// WebAppReconciler 结构体
type WebAppReconciler struct {
client.Client
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=webapp.example.com,resources=webapps,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=webapp.example.com,resources=webapps/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
// Reconcile 核心调协逻辑
func (r *WebAppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
// 1. 获取 WebApp 实例
var webApp myappv1.WebApp
if err := r.Get(ctx, req.NamespacedName, &webApp); err != nil {
if errors.IsNotFound(err) {
log.Info("WebApp resource not found. Ignoring since object must be deleted")
return ctrl.Result{}, nil
}
return ctrl.Result{}, err
}
// 2. 查找关联的 Deployment
foundDeploy := &appsv1.Deployment{}
err := r.Get(ctx, client.ObjectKey{Name: webApp.Name, Namespace: webApp.Namespace}, foundDeploy)
// 3. 如果 Deployment 不存在,则创建它
if err != nil && errors.IsNotFound(err) {
deploy := r.newDeployment(&webApp)
log.Info("Creating a new Deployment", "Deployment.Namespace", deploy.Namespace, "Deployment.Name", deploy.Name)
if err := r.Create(ctx, deploy); err != nil {
log.Error(err, "Failed to create new Deployment", "Deployment.Namespace", deploy.Namespace, "Deployment.Name", deploy.Name)
return ctrl.Result{}, err
}
// 创建成功,重新入队等待状态更新
return ctrl.Result{Requeue: true}, nil
} else if err != nil {
log.Error(err, "Failed to get Deployment")
return ctrl.Result{}, err
}
// 4. 更新状态 (示例:简单地将副本数同步到 Status)
webApp.Status.AvailableReplicas = foundDeploy.Status.AvailableReplicas
if err := r.Status().Update(ctx, &webApp); err != nil {
log.Error(err, "Failed to update WebApp status")
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
// newDeployment 生成 Deployment 对象的辅助函数
func (r *WebAppReconciler) newDeployment(cr *myappv1.WebApp) *appsv1.Deployment {
labels := map[string]string{"app": cr.Name}
return &appsv1.Deployment{
ObjectMeta: ctrl.ObjectMeta{
Name: cr.Name,
Namespace: cr.Namespace,
},
Spec: appsv1.DeploymentSpec{
Replicas: &cr.Spec.Replicas,
Selector: &metav1.LabelSelector{
MatchLabels: labels,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: labels},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "nginx",
Image: cr.Spec.Image,
Ports: []corev1.ContainerPort{
{ContainerPort: cr.Spec.Port},
},
},
},
},
},
},
}
}
// SetupWithManager 注册控制器到 Manager
func (r *WebAppReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&myappv1.WebApp{}).
Owns(&appsv1.Deployment{}). // 监控 Owned 资源
Complete(r)
}
四、 本地调试与部署
开发完成后,不要急着部署到集群,利用 make run 可以在本地直接运行控制器,它会连接到你的 ~/.kube/config 指定的集群。
bash
复制
bash
make install # 安装 CRD
make run # 运行控制器
此时,在另一个终端创建一个 WebApp 实例:
yaml
复制
yaml
apiVersion: webapp.example.com/v1
kind: WebApp
metadata:
name: my-demo-app
spec:
replicas: 3
image: nginx:1.19
port: 80
应用 YAML 后,观察控制器日志,你会发现它自动创建了对应的 Deployment,并维持了 3 个副本。
五、 总结
Kubernetes Operator 开发不仅仅是为了自动化部署,更是为了将领域知识(Domain Knowledge)编码进 K8s。通过 CRD 定义业务模型,通过 Controller 实现运维逻辑,我们成功地将 K8s 变成了一个可编程的操作系统。掌握 Kubebuilder 和控制循环模式,是迈向云原生高阶开发者的必经之路。