Kubernetes自定义资源定义(CRD)深度解析与实践
什么是Kubernetes自定义资源定义(CRD)?
Kubernetes自定义资源定义(Custom Resource Definition,简称CRD)是Kubernetes提供的一种扩展机制,允许用户定义自己的资源类型,就像使用内置资源(如Pod、Service、Deployment)一样使用这些自定义资源。
通过CRD,我们可以:
- 扩展Kubernetes API,添加自定义资源类型
- 创建领域特定的资源,如数据库、消息队列等
- 实现自定义控制器,自动管理这些资源
CRD的核心概念
1. CustomResourceDefinition
CRD是一个Kubernetes资源对象,用于定义自定义资源的结构和行为:
apiVersion: crd.k8s.io/v1 kind: CustomResourceDefinition metadata: name: databases.example.com spec: group: example.com names: kind: Database listKind: DatabaseList plural: databases singular: database shortNames: - db scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: version: type: string replicas: type: integer storage: type: string2. CustomResource
一旦CRD被创建,我们就可以创建该类型的自定义资源实例:
apiVersion: example.com/v1 kind: Database metadata: name: my-postgres spec: version: "14" replicas: 3 storage: "10Gi"3. Custom Controller
自定义控制器是一个独立运行的进程,用于监控自定义资源的状态并执行相应的操作:
func (r *DatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { var db examplev1.Database if err := r.Get(ctx, req.NamespacedName, &db); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } // 检查StatefulSet是否存在 var sts appsv1.StatefulSet sts.Name = db.Name sts.Namespace = db.Namespace if err := r.Get(ctx, types.NamespacedName{Name: db.Name, Namespace: db.Namespace}, &sts); err != nil { if apierrors.IsNotFound(err) { // 创建StatefulSet sts = r.createStatefulSet(&db) if err := r.Create(ctx, &sts); err != nil { return ctrl.Result{}, err } return ctrl.Result{Requeue: true}, nil } return ctrl.Result{}, err } // 同步副本数 if sts.Spec.Replicas != &db.Spec.Replicas { sts.Spec.Replicas = &db.Spec.Replicas if err := r.Update(ctx, &sts); err != nil { return ctrl.Result{}, err } } return ctrl.Result{}, nil }创建CRD的步骤
第一步:定义CRD
apiVersion: crd.k8s.io/v1 kind: CustomResourceDefinition metadata: name: webapps.example.com spec: group: example.com names: kind: WebApp plural: webapps singular: webapp shortNames: - app scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: image: type: string replicas: type: integer minimum: 1 maximum: 10 ports: type: array items: type: object properties: name: type: string port: type: integer status: type: object properties: readyReplicas: type: integer conditions: type: array items: type: object properties: type: type: string status: type: string第二步:部署CRD
kubectl apply -f webapp-crd.yaml第三步:创建自定义资源实例
apiVersion: example.com/v1 kind: WebApp metadata: name: my-webapp spec: image: nginx:latest replicas: 3 ports: - name: http port: 80第四步:创建自定义控制器
package controllers import ( "context" "fmt" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" examplev1 "github.com/example/webapp-operator/api/v1" ) func SetupWithManager(mgr manager.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&examplev1.WebApp{}). Owns(&appsv1.Deployment{}). Complete(&WebAppReconciler{Client: mgr.GetClient(), Scheme: mgr.GetScheme()}) } type WebAppReconciler struct { client.Client Scheme *runtime.Scheme } func (r *WebAppReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { log := log.FromContext(ctx) var webapp examplev1.WebApp if err := r.Get(ctx, req.NamespacedName, &webapp); err != nil { if errors.IsNotFound(err) { return reconcile.Result{}, nil } log.Error(err, "Unable to fetch WebApp") return reconcile.Result{}, err } var deployment appsv1.Deployment deployment.Name = webapp.Name deployment.Namespace = webapp.Namespace if err := r.Get(ctx, types.NamespacedName{Name: webapp.Name, Namespace: webapp.Namespace}, &deployment); err != nil { if errors.IsNotFound(err) { deployment = *r.createDeployment(&webapp) if err := r.Create(ctx, &deployment); err != nil { log.Error(err, "Failed to create Deployment") return reconcile.Result{}, err } log.Info("Deployment created successfully", "name", deployment.Name) return reconcile.Result{Requeue: true}, nil } log.Error(err, "Unable to fetch Deployment") return reconcile.Result{}, err } if *deployment.Spec.Replicas != webapp.Spec.Replicas { deployment.Spec.Replicas = &webapp.Spec.Replicas if err := r.Update(ctx, &deployment); err != nil { log.Error(err, "Failed to update Deployment") return reconcile.Result{}, err } log.Info("Deployment updated successfully", "name", deployment.Name) } return reconcile.Result{}, nil } func (r *WebAppReconciler) createDeployment(webapp *examplev1.WebApp) *appsv1.Deployment { replicas := webapp.Spec.Replicas return &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: webapp.Name, Namespace: webapp.Namespace, OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef(webapp, examplev1.GroupVersion.WithKind("WebApp")), }, }, Spec: appsv1.DeploymentSpec{ Replicas: &replicas, Selector: &metav1.LabelSelector{ MatchLabels: map[string]string{ "app": webapp.Name, }, }, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{ "app": webapp.Name, }, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{ { Name: "webapp", Image: webapp.Spec.Image, Ports: []corev1.ContainerPort{ { Name: "http", ContainerPort: 80, }, }, }, }, }, }, }, } }CRD的高级特性
1. 版本管理
CRD支持多个版本,可以平滑升级:
apiVersion: crd.k8s.io/v1 kind: CustomResourceDefinition metadata: name: databases.example.com spec: group: example.com names: kind: Database plural: databases scope: Namespaced versions: - name: v1alpha1 served: true storage: false schema: openAPIV3Schema: type: object properties: spec: type: object properties: version: type: string - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: version: type: string replicas: type: integer2. 默认值和验证
可以定义字段的默认值和验证规则:
apiVersion: crd.k8s.io/v1 kind: CustomResourceDefinition metadata: name: webapps.example.com spec: group: example.com names: kind: WebApp plural: webapps scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: replicas: type: integer minimum: 1 maximum: 10 default: 1 image: type: string pattern: "^[a-z0-9]+/[a-z0-9]+:[a-z0-9]+$"3. 状态子资源
可以定义状态字段,用于存储资源的运行状态:
apiVersion: crd.k8s.io/v1 kind: CustomResourceDefinition metadata: name: webapps.example.com spec: group: example.com names: kind: WebApp plural: webapps scope: Namespaced versions: - name: v1 served: true storage: true subresources: status: {} schema: openAPIV3Schema: type: object properties: spec: type: object properties: image: type: string replicas: type: integer status: type: object properties: readyReplicas: type: integerCRD的最佳实践
1. 使用Operator模式
将CRD与自定义控制器结合使用,实现自动化管理:
# 安装Operator SDK brew install operator-sdk # 创建新的Operator项目 operator-sdk init --domain example.com --repo github.com/example/my-operator # 创建新的API operator-sdk create api --group example --version v1 --kind Database --resource --controller2. 遵循Kubernetes API规范
- 使用标准的API版本控制(v1alpha1, v1beta1, v1)
- 遵循Kubernetes的命名约定
- 使用标准的状态字段(readyReplicas, conditions等)
3. 实现优雅的错误处理
func (r *DatabaseReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { log := log.FromContext(ctx) var db examplev1.Database if err := r.Get(ctx, req.NamespacedName, &db); err != nil { if errors.IsNotFound(err) { return reconcile.Result{}, nil } log.Error(err, "Unable to fetch Database") return reconcile.Result{}, err } if db.Status.Conditions == nil { db.Status.Conditions = make([]metav1.Condition, 0) } // 更新状态 db.Status.ReadyReplicas = 0 for _, condition := range db.Status.Conditions { if condition.Type == "Ready" && condition.Status == metav1.ConditionTrue { db.Status.ReadyReplicas++ } } if err := r.Update(ctx, &db); err != nil { log.Error(err, "Failed to update Database status") return reconcile.Result{}, err } return reconcile.Result{}, nil }4. 使用finalizers实现优雅删除
apiVersion: example.com/v1 kind: Database metadata: name: my-database finalizers: - database.finalizers.example.com spec: version: "14" replicas: 3func (r *DatabaseReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { var db examplev1.Database if err := r.Get(ctx, req.NamespacedName, &db); err != nil { return reconcile.Result{}, client.IgnoreNotFound(err) } // 检查是否正在删除 if db.GetDeletionTimestamp() != nil { // 执行清理操作 if err := r.cleanupResources(ctx, &db); err != nil { return reconcile.Result{}, err } // 移除finalizer db.SetFinalizers(nil) if err := r.Update(ctx, &db); err != nil { return reconcile.Result{}, err } return reconcile.Result{}, nil } // 添加finalizer if !containsString(db.GetFinalizers(), "database.finalizers.example.com") { db.SetFinalizers(append(db.GetFinalizers(), "database.finalizers.example.com")) if err := r.Update(ctx, &db); err != nil { return reconcile.Result{}, err } } // 正常的reconcile逻辑 return reconcile.Result{}, nil }CRD实战案例
案例1:数据库管理CRD
apiVersion: example.com/v1 kind: Database metadata: name: production-postgres spec: engine: postgres version: "14" replicas: 3 storage: "100Gi" backup: schedule: "0 2 * * *" retention: 7案例2:消息队列CRD
apiVersion: example.com/v1 kind: MessageQueue metadata: name: event-bus spec: engine: kafka version: "3.3" replicas: 3 partitions: 12 replicationFactor: 3案例3:缓存CRD
apiVersion: example.com/v1 kind: Cache metadata: name: redis-cache spec: engine: redis version: "7.0" replicas: 3 mode: cluster memory: "10Gi"总结
Kubernetes自定义资源定义(CRD)是扩展Kubernetes功能的强大工具。通过CRD,我们可以创建领域特定的资源类型,并通过自定义控制器实现自动化管理。
在实际应用中,CRD广泛用于:
- 数据库管理(如PostgreSQL、MySQL)
- 消息队列(如Kafka、RabbitMQ)
- 缓存服务(如Redis、Memcached)
- 自定义应用部署和管理
掌握CRD的开发和使用,对于构建复杂的云原生应用至关重要。希望本文能帮助你深入理解CRD,并在实际项目中应用这一强大功能。