Thứ Hai, 23/02/2026, 17:00 (GMT+0)

Hướng dẫn xây dựng Memcached Operator

Quay lại Trang chủ Blog
Trên trang này

Mục tiêu của bài hướng dẫn là xây dựng Operator chịu trách nhiệm duy trì trạng thái mong muốn cho các tài nguyên tên là “memcached” thông qua các nhiệm vụ cụ thể:

  • Điều hòa (Reconcile) Custom Resource (CR): Theo dõi và quản lý các instance Memcached được định nghĩa trong Cluster.
  • Quản lý Deployment: Tự động khởi tạo và cấu hình Deployment sử dụng image Memcached chính thức.
  • Đảm bảo tính sẵn sàng (Scaling): Duy trì số lượng Pod chính xác dựa trên tham số size được thiết lập trong cấu hình CR.
  • Cập nhật trạng thái (Status Reporting): Phản hồi thông tin thực tế từ hệ thống (như danh sách các Pod đang chạy) ngược lại cho Memcached CR để người dùng tiện theo dõi.

Link source code tham khảo: source code  https://github.com/kubernetes-sigs/kubebuilder/tree/master/docs/book/src/getting-started/testdata/project

Bước 1.Cài đặt công cụ (Installation)

Trước khi bắt đầu xây dựng Kubernetes Operator, bạn cần chuẩn bị đầy đủ các công cụ hỗ trợ. Dưới đây là danh sách các yêu cầu phần mềm và các bước cài đặt cụ thể

Điều kiện tiên quyết (Prerequisites)

Trước khi bắt đầu, bạn cần chuẩn bị môi trường:

  • Go: Phiên bản mới nhất (thường là 1.19+).
  • Docker: Để build và chạy container.
  • kubectl: Để tương tác với cluster.
  • Một Kubernetes Cluster: Bạn có thể dùng kind hoặc minikube để thử nghiệm local.

Cài đặt Kubebuilder

Kubebuilder là một framework giúp chúng ta xây dựng Kubernetes APIs một cách nhanh chóng. Để cài đặt bản binary trên Linux hoặc macOS, bạn thực hiện các lệnh sau:

Lưu ý: Lệnh dưới đây sẽ tự động nhận diện hệ điều hành và kiến trúc chip của bạn

curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)

chmod +x kubebuilder && mv kubebuilder /usr/local/bin/

Kiểm tra cài đặt

Cuối cùng, hãy xác nhận xem mọi thứ đã sẵn sàng chưa bằng cách kiểm tra phiên bản:

kubebuilder version

Nếu thông tin phiên bản hiện ra, bạn đã cài đặt thành công và sẵn sàng để bắt đầu khởi tạo dự án.

Bước 2. Khởi tạo dự án (Create a Project)

Sau khi đã chuẩn bị đầy đủ công cụ, chúng ta sẽ tiến hành khởi tạo cấu trúc thư mục cho dự án Operator bằng Kubebuilder.

Init project

Đầu tiên, hãy tạo một thư mục mới cho dự án và thực hiện lệnh init:

mkdir memcached-operator

cd memcached-operator

kubebuilder init --domain example.com --repo github.com/example/memcached-operator

Ý nghĩa:

  • --domain → dùng để build API group (vd: cache.example.com)
  • --repo → module path Go

Cấu trúc thư mục dự án

Sau khi lệnh hoàn tất, Kubebuilder sẽ tự động tạo bộ khung cho dự án. Các thành phần then chốt cần lưu ý bao gồm:

Những thành phần chính:

  • api/: Nơi chứa các định nghĩa cho Custom Resource Definition (CRD) bao gồm cấu trúc Spec và Status.
  • internal/controller/: Thư mục chứa logic điều khiển (reconciliation logic) của Operator.
  • config/: Chứa các file manifest YAML để triển khai (CRD, RBAC, Manager, Webhooks...).
  • cmd/main.go: Điểm khởi đầu (entrypoint) của ứng dụng, nơi quản lý vòng đời của Operator.

Bước 3. Khởi tạo, định nghĩa API (Create an API)

Sau khi đã có bộ khung dự án, bước tiếp theo là xác định thực thể mà Operator sẽ quản lý. Chúng ta sẽ tạo một API mới với định danh Group/Version là cache/v1alpha1 và loại tài nguyên (Kind) là Memcached.

Khởi tạo API

Chạy lệnh sau trong thư mục gốc của dự án để tạo một API mới (Group/Version) là cache/v1alpha1 và một loại tài nguyên mới (Kind/CRD) tên là Memcached:

kubebuilder create api --group cache --version v1alpha1 --kind Memcached

Trong quá trình thực hiện, Kubebuilder sẽ đưa ra hai câu hỏi xác nhận tạo tài template:

  • Create Resource [y/n]: Chọn y để tạo file định nghĩa API (api/v1alpha1/ memcached_types.go).
  • Create Controller [y/n]: Chọn y để tạo logic điều khiển (internal/controller/memcached_controller.go).

Các thành phần được khởi tạo

Sau khi lệnh hoàn tất, bạn sẽ thấy các thành phần quan trọng sau được tự động sinh ra trong dự án:

Nhóm file định nghĩa API

  • api/v1alpha1/memcached_types.go: Đây là nơi bạn sẽ định nghĩa cấu trúc Spec (trạng thái mong muốn) và Status (trạng thái thực tế) cho tài nguyên Memcached.
  • api/v1alpha1/groupversion_info.go: Chứa các thông tin siêu dữ liệu (metadata) về Group và Version của API.

Logic điều khiển (Controller)

  • internal/controller/memcached_controller.go: File này chứa hàm Reconcile, phần quan trọng nhất của operator, sẽ thực hiện để quản lý vòng đời của resource.

Cấu hình tài nguyên (CRD YAML)

  • config/crd/bases/cache.example.com_memcacheds.yaml: File manifest dùng để đăng ký Custom Resource Definition với Kubernetes Cluster.

Bước 4: Định nghĩa API (API Definition)

Sau khi tạo file, chúng ta cần tùy chỉnh cấu trúc dữ liệu trong file api/v1alpha1/memcached_types.go. Đây là nơi bạn xác định "ngôn ngữ" mà người dùng sẽ sử dụng để giao tiếp với Operator của bạn.

Cấu trúc mong muốn (Spec Definition)

Trường Spec đại diện cho trạng thái mong muốn (Desired State) mà người dùng khai báo. Chúng ta sẽ thêm trường Size để xác định số lượng bản sao (replicas) của Memcache

type MemcachedSpec struct {
  // +kubebuilder:validation:Minimum=1
  Size int32 `json:"size,omitempty"`
}
  • Size: Đại diện cho số lượng Pod sẽ được triển khai.
  • Marker +kubebuilder:validation:Minimum=1: Đây là một annotation cực kỳ mạnh mẽ. Nó yêu cầu Kubebuilder tự động sinh ra mã OpenAPI validation trong file CRD YAML, đảm bảo người dùng không thể cấu hình số lượng máy chủ nhỏ hơn 1.

Trạng thái thực tế (Status Definition)

Trường Status dùng để phản ánh trạng thái quan sát được (Observed State). Trong môi trường Cloud hoặc môi trường thực tế của đối tượng, việc theo dõi các điều kiện (Conditions) là tiêu chuẩn để biết tài nguyên đang chạy tốt, đang khởi tạo hay gặp lỗi.

// MemcachedStatus định nghĩa trạng thái hiện tại của tài nguyên Memcached

type MemcachedStatus struct {

    // Conditions đại diện cho các quan sát về trạng thái hiện tại của Memcached

    // +listType=map

    // +listMapKey=type

    // +optional

    Conditions []metav1.Condition `json:"conditions,omitempty"`

}

Tại sao nên sử dụng Conditions?

  • Tính minh bạch: Giúp người dùng dùng lệnh kubectl describe để biết chính xác lý do tại sao Operator không thể tạo Pod (ví dụ: thiếu tài nguyên, lỗi Image).
  • Chuẩn hóa: Sử dụng metav1.Condition giúp Operator của bạn tuân thủ đúng chuẩn thiết kế của Kubernetes API (như các trạng thái Ready, Progressing, hoặc Error).
  • Marker +listType=map: Đảm bảo rằng khi cập nhật danh sách các trạng thái, Kubernetes sẽ xử lý chúng như một tập hợp các bản ghi có khóa (type), tránh việc ghi đè dữ liệu sai lệch.

Lưu ý :

Sau khi chỉnh sửa file _types.go, đừng quên chạy lệnh sau để cập nhật lại mã nguồn tự động và các file manifest, chi tiết sẽ được trình bình trong bước sau:

make generate

make manifests

Bước 5. Generate Code and manifest

Một trong những ưu điểm lớn nhất của Kubebuilder là khả năng tự động sinh mã (Code Generation). Sau khi bạn thay đổi cấu trúc Spec hoặc Status trong file _types.go, chúng ta cần thực thi các lệnh sau để cập nhật toàn bộ dự án.

Thực thi lệnh

Để thực hiện, chạy câu lệnh sau trên terminal:

make generate
make manifests

Ý nghĩa các câu lệnh make

Việc hiểu rõ cơ chế phía sau sẽ giúp bạn xử lý lỗi (debug) tốt hơn khi dự án trở nên phức tạp:

  • make generate:
    • Sinh DeepCopy: Tạo ra các phương thức DeepCopy(), giúp Kubernetes có thể sao chép các đối tượng tùy chỉnh của bạn một cách an toàn trong bộ nhớ.
    • Boilerplate Code: Tự động điền các đoạn mã lặp lại cần thiết để Go có thể nhận diện Resource của bạn là một đối tượng Kubernetes hợp lệ.
  • make manifests:
    • Cập nhật CRD YAML: Chuyển đổi các định nghĩa Go và các Marker (như +kubebuilder:validation) thành lược đồ OpenAPI trong file YAML tại thư mục config/crd/bases/.
    • Cấu hình RBAC: Nếu bạn thêm các Marker về quyền hạn (ví dụ: quyền đọc Pod), lệnh này sẽ tự động cập nhật các file Role và ClusterRole tương ứng.

Kết quả đạt được

Sau khi thực hiện thành công, tài nguyên Custom Resource Definition (CRD) của bạn sẽ sở hữu các tính năng cao cấp:

  • Schema Validation: Cluster sẽ tự động từ chối nếu người dùng nhập Size < 1 (nhờ OpenAPI schema).
  • Subresources (Status): Kích hoạt endpoint /status, cho phép cập nhật trạng thái mà không làm thay đổi phần Spec.
  • Printer Columns: (Nếu có cấu hình) Giúp lệnh kubectl get memcached hiển thị thêm các cột tùy chỉnh như STATUS hay AGE ngay trên terminal.

Bước 6: Triển khai logic reconcile Loop

Sau khi đã có bộ khung và API, chúng ta sẽ bước vào phần "linh hồn" của Operator: Reconcile Loop. Đây là vòng lặp vô tận nơi Operator liên tục so sánh trạng thái người dùng mong muốn (Desired State) và trạng thái thực tế trên Cluster (Actual State) để thực hiện các hành động điều chỉnh.

Vai trò của Controller-Runtime Manager

Trước khi đi vào chi tiết mã nguồn, chúng ta cần hiểu về Manager – thành phần cốt lõi điều khiển toàn bộ Operator. Kubebuilder sử dụng thư viện controller-runtime để khởi tạo một Manager với 3 nhiệm vụ chính:

  • Khởi tạo Scheme: Đăng ký các loại tài nguyên (GVK - Group Version Kind) để Operator có thể hiểu và làm việc với cả tài nguyên chuẩn của Kubernetes lẫn Custom Resource (Memcached) mà chúng ta vừa tạo.
  • Đăng ký Controller: Kết nối logic điều khiển (Reconciler) với các tài nguyên tương ứng trên Cluster.
  • Vận hành Vòng lặp sự kiện (Event Loop): Đây là "trái tim" của Operator. Manager sẽ liên tục lắng nghe các sự kiện (Create, Update, Delete) từ API Server và đẩy chúng vào hàng đợi để hàm Reconcile xử lý.

Cơ chế vận hành (How it works)

Khi người dùng tạo một Custom Resource (CR) Memcached, một chuỗi sự kiện sẽ diễn ra trong hệ thống:

  1. Ghi nhận: API Server lưu trữ object Memcached vào etcd.
  2. Thông báo: Một thành phần gọi là Informer phát hiện sự thay đổi và gửi event tới Controller.
  3. Điều hòa (Reconcile): Hàm Reconcile được kích hoạt để xử lý event.
  4. Hành động: * Nếu chưa có Deployment: Controller sẽ khởi tạo một Deployment mới.
    • Nếu đã có Deployment: Controller kiểm tra xem số lượng bản sao (size) có khớp với Spec không. Nếu lệch, nó sẽ cập nhật Deployment để đồng bộ.
  5. Phản hồi: Cập nhật lại trạng thái (Status) của Memcached CR để người dùng theo dõi.

Triển khai Reconcile

Triển khai code cho Reconcile tại file:

internal/controller/memcached_controller.go

Kiểm tra và Khởi tạo tài nguyên

Đầu tiên, Operator cần kiểm tra xem Deployment tương ứng đã tồn tại trên Cluster hay chưa.

   // Check if the deployment already exists, if not create a new one

    found := &appsv1.Deployment{}

    err = r.Get(ctx, types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found)

    if err != nil && apierrors.IsNotFound(err) {

        // Define a new deployment

        dep := r.deploymentForMemcached()

        // Create the Deployment on the cluster

        if err = r.Create(ctx, dep); err != nil {

            log.Error(err, "Failed to create new Deployment",

            "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name)

            return ctrl.Result{}, err

        }

        ...

    }

 

Đồng bộ hóa trạng thái (Sync State)

Nếu Deployment đã tồn tại, nhiệm vụ của Operator là đảm bảo các thông số kỹ thuật (như size) phải luôn khớp với những gì người dùng khai báo trong Spec.

...

size := memcached.Spec.Size

if *found.Spec.Replicas != size {

     found.Spec.Replicas = &size

     if err = r.Update(ctx, found); err != nil {

         log.Error(err, "Failed to update Deployment",

                "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name)

         return ctrl.Result{}, err

     }

...

Lưu ý:

Khi xây dựng hàm reconcile cần đảm bảo và lưu ý:

  • Tính Lũy đẳng (Idempotency): Hãy nhấn mạnh rằng hàm Reconcile có thể chạy hàng nghìn lần. Vì vậy, logic luôn phải là: "Kiểm tra trạng thái -> Nếu sai thì sửa -> Nếu đúng thì thôi".
  • OwnerReference: Đây là tính năng cực kỳ quan trọng giúp Kubernetes tự động dọn dẹp tài nguyên (Garbage Collection), tránh để lại "rác" trên hệ thống Cloud của bạn.

Xây dựng Deployment Template

Để hàm Reconcile trên hoạt động, chúng ta cần một hàm bổ trợ để định nghĩa cấu hình Deployment cho Memcached. Đây là nơi bạn thiết lập Image, Port và các cấu hình hệ thống:

func (r *MemcachedReconciler) deploymentForMemcached(m *cachev1alpha1.Memcached) *appsv1.Deployment {

    labels := labelsForMemcached(m.Name)

    replicas := m.Spec.Size



    dep := &appsv1.Deployment{

        ObjectMeta: metav1.ObjectMeta{

            Name:      m.Name,

            Namespace: m.Namespace,

        },

        Spec: appsv1.DeploymentSpec{

            Replicas: &replicas,

            Selector: &metav1.LabelSelector{

                MatchLabels: labels,

            },

            Template: corev1.PodTemplateSpec{

                ObjectMeta: metav1.ObjectMeta{

                    Labels: labels,

                },

                Spec: corev1.PodSpec{

                    Containers: []corev1.Container{{

                        Image: "memcached:1.4.36-alpine",

                        Name:  "memcached",

                        Ports: []corev1.ContainerPort{{

                            ContainerPort: 11211,

                            Name:          "memcached",

                        }},

                    }},

                },

            },

        },

    }

    // Thiết lập quyền sở hữu (OwnerReference) để khi xóa Memcached CR, Deployment cũng bị xóa theo

    ctrl.SetControllerReference(m, dep, r.Scheme)

    return dep

}

Bước 7: Cấu hình RBAC

Để Operator có thể thay mặt người dùng thao tác với các tài nguyên trên Cluster (như tạo Deployment hay theo dõi Pod), chúng ta cần cấp quyền thông qua cơ chế RBAC (Role-Based Access Control).

Cấu hình quyền hạn cụ thể

Thay vì viết các file YAML phức tạp một cách thủ công, Kubebuilder cho phép chúng ta định nghĩa quyền hạn ngay trong mã nguồn Go bằng các Markers (chú thích đặc biệt). Các markers này nằm ngay phía trên hàm Reconcile trong file internal/controller/memcached_controller.go.

//+kubebuilder:rbac:groups=cache.example.com,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete

Vì Operator của chúng ta cần quản lý cả Custom Resource lẫn các tài nguyên chuẩn của Kubernetes, bạn cần bổ sung các dòng sau:

// +kubebuilder:rbac:groups=cache.example.com,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete

// +kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/status,verbs=get;update;patch

// +kubebuilder:rbac:groups=cache.example.com,resources=memcacheds/finalizers,verbs=update

// +kubebuilder:rbac:groups=events.k8s.io,resources=events,verbs=create;patch

// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete

// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch

Tự động hóa tạo Manifest

Sau khi thêm các markers, hãy chạy lại lệnh sau để Kubebuilder tự động cập nhật cấu hình:

make manifests

Kết quả: Toàn bộ các quyền hạn trên sẽ được tổng hợp vào file config/rbac/role.yaml. File này đóng vai trò là "chứng minh thư" về quyền hạn của Operator khi được triển khai lên cụm Kubernetes.

Tại sao bước này lại quan trọng?

  • Tính bảo mật: Việc định nghĩa chính xác các verbs (get, list, watch,...) giúp đảm bảo Operator chỉ có đủ quyền để làm việc của mình, tránh rủi ro bảo mật cho toàn bộ Cluster.
  • Tự động hóa: Mọi thay đổi về logic code đi kèm với thay đổi quyền hạn sẽ luôn được đồng bộ nhờ lệnh make manifests, giúp giảm thiểu lỗi "Permission Denied" khi vận hành thực tế.

Bước 8. Triển khai, thử nghiệm operator

Sau khi đã hoàn tất logic và cấu hình RBAC, đây là lúc chúng ta đưa Operator vào vận hành thực tế trên Kubernetes Cluster.

Cài đặt Custom Resource Definition (CRD)

Trước khi Operator có thể hiểu được tài nguyên Memcached, bạn cần đăng ký định nghĩa của nó với Cluster.

Lưu ý: Đảm bảo kubectl của bạn đang trỏ đúng vào context của Cluster (Kind, Minikube hoặc Cloud Cluster).

make install

 

Lệnh này sẽ lấy file manifest từ config/crd/bases và áp dụng lên Cluster. Bây giờ, Cluster đã sẵn sàng để tiếp nhận các đối tượng loại Memcached.

Vận hành Controller

Bạn có hai lựa chọn để chạy Operator tùy theo mục đích kiểm thử:

Cách A: Chạy trực tiếp tại máy cục bộ (Dành cho Debug)

Nếu bạn đang trong quá trình phát triển và muốn xem log nhanh, hãy dùng lệnh:

make run

Lệnh này sẽ chạy Operator như một tiến trình Go bình thường trên máy bạn nhưng sử dụng quyền hạn từ kubeconfig để tương tác với Cluster từ xa.

Cách B: Triển khai lên Cluster (Dành cho Production/Staging)

Để chạy Operator như một Deployment thực thụ bên trong Kubernetes, bạn cần build image và push lên registry (Docker Hub, GCR, v.v.):

# Build và Push Image

make docker-build docker-push IMG=<your-registry>/memcached-operator:v1

# Triển khai Operator lên Cluster

make deploy IMG=<your-registry>/memcached-operator:v1

Lệnh deploy sẽ tự động khởi tạo:

  • Deployment: Chạy controller manager.
  • ServiceAccount & RBAC: Cấp quyền cho Operator hoạt động.
  • Namespace: Mặc định dự án sẽ được triển khai vào namespace hệ thống của nó.

Tạo Custom Resource và Kiểm tra thành quả

Chúng ta sẽ tạo một file manifest mẫu tại config/samples/cache_v1alpha1_memcached.yaml:

config/samples/cache_v1alpha1_memcached.yaml

Thực thi lệnh apply:

kubectl apply -f config/samples/cache_v1alpha1_memcached.yaml

Kiểm tra trạng thái:

Hãy quan sát cách Operator tự động tạo ra Deployment và các Pod tương ứng:

kubectl get memcached,deployment

Deployment sẽ được tạo tương ứng.

Sau khi hoàn thành các bước hướng dẫn, bạn đã nắm vững quy trình cơ bản để xây dựng một Kubernetes Operator bằng Kubebuilder, bao gồm:

  • Định nghĩa CRD: Khởi tạo cấu trúc Custom Resource để mở rộng Kubernetes API.
  • Triển khai Controller: Viết logic điều hòa (Reconcile Loop) để quản lý trạng thái tài nguyên.
  • Vận hành Operator: Đóng gói, triển khai và kiểm thử khả năng tự động hóa trên Cluster thực tế.
  • Quản lý Resource: Điều phối các tài nguyên Custom theo đúng thông số kỹ thuật (Spec) đã khai báo.

Các chủ đề nâng cao cần tìm hiểu

Để đưa Operator vào môi trường Production với độ tin cậy cao, bạn có thể tiếp tục nghiên cứu các tính năng nâng cao trong tài liệu The Kubebuilder Book (https://book.kubebuilder.io/) 

  1. Admission Webhooks: Mutating Webhooks: Tự động điền giá trị mặc định cho tài nguyên. Validating Webhooks: Kiểm tra các ràng buộc logic phức tạp trước khi dữ liệu được ghi vào Cluster.
  2. Finalizers: Đảm bảo quy trình dọn dẹp tài nguyên (Cleanup) diễn ra an toàn và đầy đủ trước khi xóa hoàn toàn CRD.
  3. Status Management nâng cao: Sử dụng Conditions theo chuẩn Kubernetes để phản hồi chi tiết trạng thái hệ thống cho người vận hành.
  4. Metrics & Monitoring: Tích hợp Prometheus để giám sát hiệu năng và sức khỏe của Operator.
#CloudWave Radar
#CloudWave Radar
Chúng tôi có 4 môi trường staging, 2 môi trường production, hàng chục microservice và rất nhiều phiên bản thử nghiệm. Lúc đầu dùng VPS tưởng là đủ, nhưng rồi mỗi lần cập nhật code là một lần lo… không biết lần này ‘tháo’ có làm hỏng cái gì không?
Tại sao doanh nghiệp hiện đại cần Kubernetes?
Tiếp tục đọc