
Viết một Kubernetes Operator từ đầu bằng client-go thô đòi hỏi hàng nghìn dòng mã boilerplate: informers, listers, workqueues, event recorders và reflection types.
Operator SDK (được xây dựng trên Kubebuilder và controller-runtime) xử lý phần khung này cho bạn. Nó cho phép bạn tập trung vào logic nghiệp vụ: định nghĩa schema Custom Resource Definition (CRD) của bạn và triển khai vòng lặp đối chiếu.
Hướng dẫn này sẽ hướng dẫn bạn xây dựng một RedisCluster Operator bằng Go sẵn sàng cho sản xuất, hoàn chỉnh với finalizers để dọn dẹp an toàn, quản lý tài nguyên phụ bất biến và kiểm thử tích hợp tự động với envtest.
Cài đặt Operator SDK và tạo khung một dự án Go mới:
# Initialize operator repository
operator-sdk init \
--domain db.example.com \
--repo github.com/my-org/redis-operator \
--plugins=go/v4
# Generate API (CRD) and Controller
operator-sdk create api \
--group cache \
--version v1alpha1 \
--kind RedisCluster \
--resource \
--controller
Điều này tạo ra cấu trúc dự án:
api/v1alpha1/rediscluster_types.go: Định nghĩa struct Go cho Custom Resource của bạn.internal/controller/rediscluster_controller.go: Vòng lặp Reconcile.config/: Kustomize manifests cho CRD, vai trò RBAC và triển khai manager.Chỉnh sửa api/v1alpha1/rediscluster_types.go để định nghĩa trạng thái mong muốn (Spec) và trạng thái quan sát được (Status):
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// RedisClusterSpec defines the desired state of RedisCluster
type RedisClusterSpec struct {
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=9
// +kubebuilder:default=3
Replicas int32 `json:"replicas"`
// +kubebuilder:validation:Required
Version string `json:"version"`
// +kubebuilder:default="256Mi"
MemoryLimit string `json:"memoryLimit,omitempty"`
}
// RedisClusterStatus defines the observed state of RedisCluster
type RedisClusterStatus struct {
Nodes []string `json:"nodes,omitempty"`
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Replicas",type="integer",JSONPath=".spec.replicas"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
// RedisCluster is the Schema for the redisclusters API
type RedisCluster struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec RedisClusterSpec `json:"spec,omitempty"`
Status RedisClusterStatus `json:"status,omitempty"`
}
Tạo các tệp YAML CRD của Kubernetes từ các struct Go:
make manifests
Hợp đồng cốt lõi của một bộ điều khiển Kubernetes là đối chiếu kích hoạt theo cấp độ: hàm Reconcile của bạn phải có khả năng chạy lặp lại mà không gây ra tác dụng phụ không mong muốn.
Đây là internal/controller/rediscluster_controller.go:
package controller
import (
"context"
"time"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"
cachev1alpha1 "github.com/my-org/redis-operator/api/v1alpha1"
)
const redisFinalizer = "cache.db.example.com/finalizer"
type RedisClusterReconciler struct {
client.Client
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=cache.db.example.com,resources=redisclusters,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=cache.db.example.com,resources=redisclusters/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete
func (r *RedisClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
// 1. Fetch the RedisCluster instance
var cluster cachev1alpha1.RedisCluster
if err := r.Get(ctx, req.NamespacedName, &cluster); err != nil {
if errors.IsNotFound(err) {
return ctrl.Result{}, nil // Object deleted, ignore
}
return ctrl.Result{}, err
}
// 2. Handle Finalizers (Graceful Deletion)
if cluster.ObjectMeta.DeletionTimestamp.IsZero() {
// Register finalizer if not present
if !controllerutil.ContainsFinalizer(&cluster, redisFinalizer) {
controllerutil.AddFinalizer(&cluster, redisFinalizer)
if err := r.Update(ctx, &cluster); err != nil {
return ctrl.Result{}, err
}
}
} else {
// Object is being deleted
if controllerutil.ContainsFinalizer(&cluster, redisFinalizer) {
logger.Info("Cleaning up external Redis state...")
// (Perform external cleanup like draining nodes or releasing cloud disks here)
controllerutil.RemoveFinalizer(&cluster, redisFinalizer)
if err := r.Update(ctx, &cluster); err != nil {
return ctrl.Result{}, err
}
}
return ctrl.Result{}, nil
}
// 3. Reconcile Underlying StatefulSet using CreateOrUpdate
sts := &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: cluster.Name + "-nodes",
Namespace: cluster.Namespace,
},
}
opResult, err := controllerutil.CreateOrUpdate(ctx, r.Client, sts, func() error {
// Set owner reference for automatic garbage collection
if err := ctrl.SetControllerReference(&cluster, sts, r.Scheme); err != nil {
return err
}
labels := map[string]string{"app": "redis", "cluster": cluster.Name}
sts.Spec.Replicas = &cluster.Spec.Replicas
sts.Spec.Selector = &metav1.LabelSelector{MatchLabels: labels}
sts.Spec.Template = corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: labels},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "redis",
Image: "redis:" + cluster.Spec.Version,
Resources: corev1.ResourceRequirements{
Limits: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse(cluster.Spec.MemoryLimit),
},
},
},
},
},
}
return nil
})
if err != nil {
logger.Error(err, "Failed to reconcile StatefulSet")
return ctrl.Result{}, err
}
if opResult != controllerutil.OperationResultNone {
logger.Info("StatefulSet reconciled", "operation", opResult)
}
// 4. Update Status
cluster.Status.Nodes = []string{cluster.Name + "-0", cluster.Name + "-1"}
if err := r.Status().Update(ctx, &cluster); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{RequeueAfter: 5 * time.Minute}, nil
}
func (r *RedisClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&cachev1alpha1.RedisCluster{}).
Owns(&appsv1.StatefulSet{}). // Re-trigger reconcile when child StatefulSet changes
Complete(r)
}
envtestĐừng triển khai lên một cụm Kubernetes từ xa cho các bài kiểm thử đơn vị/tích hợp cơ bản. envtest khởi động một etcd và kube-apiserver thực sự cục bộ dưới dạng nhị phân trong vòng chưa đầy 2 giây:
var _ = Describe("RedisCluster Controller", func() {
Context("When creating a RedisCluster", func() {
It("Should create a child StatefulSet successfully", func() {
ctx := context.Background()
cluster := &cachev1alpha1.RedisCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "test-redis",
Namespace: "default",
},
Spec: cachev1alpha1.RedisClusterSpec{
Replicas: 3,
Version: "7.2-alpine",
},
}
Expect(k8sClient.Create(ctx, cluster)).Should(Succeed())
// Verify StatefulSet is created by the controller
stsLookupKey := types.NamespacedName{Name: "test-redis-nodes", Namespace: "default"}
createdSts := &appsv1.StatefulSet{}
Eventually(func() bool {
err := k8sClient.Get(ctx, stsLookupKey, createdSts)
return err == nil
}, time.Second*10, time.Millisecond*250).Should(BeTrue())
Expect(*createdSts.Spec.Replicas).To(Equal(int32(3)))
})
})
})
Chạy bộ kiểm thử:
make test
controllerutil.CreateOrUpdate: Ngăn chặn các điều kiện tranh chấp và ghi đè các chú thích bên ngoài.Owns() trong SetupWithManager: Đảm bảo các thay đổi tài nguyên con kích hoạt đối chiếu tài nguyên cha ngay lập tức.envtest để phản hồi CI nhanh: Các bài kiểm thử chạy cục bộ mà không có Docker hoặc Minikube overhead.Một developer định nghĩa một `RedisCluster` CRD với `spec.replicas` có `Minimum=1` và `Maximum=9`. Nếu người dùng tạo một đối tượng `RedisCluster` với `spec.replicas: 10`, hành vi mong đợi khi áp dụng manifest này vào một Kubernetes cluster là gì?
Làm sạch log AI CLI, tạo biểu thức cron, giải mã JWT và tính quyền chmod offline.

Mở rộng mặt phẳng điều khiển Kubernetes với Operators và Custom Resource Definitions (CRD) để tự động hóa quản lý vòng đời cho các ứng dụng có trạng thái phức tạp, tìm hiểu về vòng lặp đối chiếu, RBAC, kiểm thử và các mẫu sản xuất.
Đọc tiếp
Hướng dẫn từng bước về xây dựng Kubernetes Operator trong môi trường sản xuất bằng Go sử dụng Kubebuilder để quản lý các workload có trạng thái và tài nguyên tùy chỉnh.
Đọc tiếp
Hướng dẫn toàn diện về linux cgroups v2 trong kubernetes: psi pressure stall information, memory throttling & oom shields với kiến trúc và ví dụ code cấp độ sản xuất.
Đọc tiếp