CustomResourceDefinition: Thêm Kiểu Của Riêng Bạn

K
Kai··5 min read

Mười một part đầu dùng các resource có sẵn của Kubernetes — Pod, Service, Deployment. Part XII đi theo hướng khác: mở rộng chính Kubernetes. Điểm bắt đầu tự nhiên là CustomResourceDefinition (CRD), cách thêm một kiểu object mới vào API server mà không phải sửa hay biên dịch lại nó. Khai một CRD xong, API server phục vụ kiểu mới y như kiểu gốc: kubectl get được, validate, phân version, lưu etcd.

Một CRD gồm gì

CRD khai metadata về kiểu mới: nó thuộc API group nào, có những version nào, phạm vi namespaced hay cluster, tên gọi ra sao, và schema để validate. Dựng một kiểu Widget:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: widgets.kkloud.io          # bắt buộc: <plural>.<group>
spec:
  group: kkloud.io
  scope: Namespaced
  names: {plural: widgets, singular: widget, kind: Widget, shortNames: [wg]}
  versions:
  - name: v1
    served: true                   # version này được phục vụ
    storage: true                  # đúng một version làm storage
    subresources: {status: {}}     # tách /status thành subresource riêng
    additionalPrinterColumns:
    - {name: Size,  type: integer, jsonPath: .spec.size}
    - {name: Color, type: string,  jsonPath: .spec.color}
    - {name: Phase, type: string,  jsonPath: .status.phase}
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            required: [size, color]
            properties:
              size:  {type: integer, minimum: 1, maximum: 5}
              color: {type: string, enum: [red, green, blue]}
          status:
            type: object
            properties:
              phase: {type: string}

Áp vào, API server đăng ký kiểu mới ngay và kubectl thấy nó trong danh sách resource:

kubectl apply -f widget-crd.yaml
kubectl api-resources --api-group=kkloud.io
NAME      SHORTNAMES   APIVERSION     NAMESPACED   KIND
widgets   wg           kkloud.io/v1   true         Widget

widgets giờ là một resource thật, có short name wg, namespaced, kind Widget. Không cài controller, không khởi động lại API server.

Phục vụ động: không restart apiserver

Câu "không khởi động lại API server" đáng dừng lại, vì nó là điểm khác biệt cốt lõi của CRD. Bên trong API server có một controller theo dõi chính các object CRD; khi một CRD xuất hiện, nó dựng động bộ xử lý REST cho group/version mới (/apis/kkloud.io/v1/...) và làm mới bảng discovery + lược đồ OpenAPI — tất cả trong tiến trình đang chạy. Kiểm chứng bằng cách so thời điểm khởi động của apiserver trước và sau khi tạo CRD:

ssh controller-0 'sudo systemctl show kube-apiserver -p ActiveEnterTimestamp --value'
kubectl get --raw /apis/kkloud.io   # trước khi tạo CRD
# ...apply CRD...
kubectl get --raw /apis/kkloud.io/v1
ssh controller-0 'sudo systemctl show kube-apiserver -p ActiveEnterTimestamp --value'
Sat 2026-05-23 17:22:23 UTC                                  # thời điểm khởi động (trước)
Error from server (NotFound): the server could not find...   # /apis/kkloud.io chưa tồn tại
{"kind":"APIResourceList", ... "name":"widgets","namespaced":true,"kind":"Widget"}   # sau: phục vụ ngay
Sat 2026-05-23 17:22:23 UTC                                  # thời điểm khởi động (sau) — Y HỆT

Endpoint /apis/kkloud.io/v1 từ chỗ NotFound chuyển sang phục vụ một APIResourceList đầy đủ, trong khi ActiveEnterTimestamp của apiserver không đổi — chứng minh nó không hề restart, chỉ lắp thêm handler lúc đang chạy. Đây là lý do CRD nhẹ và an toàn để thêm: khác với sửa code apiserver hay aggregation (Bài 60, gắn cả một server riêng), CRD chỉ là dữ liệu mà apiserver tự lắp đường phục vụ. Validation theo openAPIV3Schema cũng chạy ngay trong apiserver, tại chặng admission (Bài 51) — phần sau cho thấy nó chặn dữ liệu sai.

Schema validate custom resource

Phần openAPIV3Schema không chỉ để mô tả — API server dùng nó để validate mọi custom resource. Tạo một Widget hợp lệ:

kubectl -n crd-demo apply -f - <<'EOF'
apiVersion: kkloud.io/v1
kind: Widget
metadata: {name: w1}
spec: {size: 3, color: blue}
EOF
widget.kkloud.io/w1 created

Rồi thử hai cái sai schema — size vượt maximum, và color ngoài enum:

# size: 9 (> maximum 5)
# color: purple (ngoài enum red/green/blue)
The Widget "bad1" is invalid: spec.size: Invalid value: 9: spec.size in body should be less than or equal to 5
The Widget "bad2" is invalid: spec.color: Unsupported value: "purple": supported values: "red", "green", "blue"

API server từ chối ngay, với thông báo chỉ rõ trường nào sai và luật nào bị vi phạm. Validate xảy ra ở chính API server, không cần code ngoài — required, minimum/maximum, enum, kiểu dữ liệu đều do schema lo.

Printer columns và status subresource

additionalPrinterColumns cho kubectl get hiển thị đúng trường ta cần, thay vì chỉ tên và tuổi:

kubectl -n crd-demo get wg
NAME   SIZE   COLOR   PHASE
w1     3      blue    

PHASE rỗng vì chưa ai đặt status. Khai subresources: {status: {}} tách status thành một endpoint riêng /status — cập nhật nó qua đường khác với cập nhật spec:

kubectl -n crd-demo patch widget w1 --subresource=status --type=merge -p '{"status":{"phase":"Ready"}}'
kubectl -n crd-demo get wg w1
NAME   SIZE   COLOR   PHASE
w1     3      blue    Ready

Tách specstatus không phải hình thức: nó cho phép phân quyền RBAC riêng (controller được ghi status, user chỉ ghi spec) — đúng mô hình spec là mong muốn của người dùng, status là thực tế do controller báo cáo, sẽ dùng ở Bài 59. Custom resource lưu trong etcd dưới group của nó, cạnh resource gốc:

sudo etcdctl ... get /registry/kkloud.io/widgets/crd-demo/w1 --keys-only
/registry/kkloud.io/widgets/crd-demo/w1

🧹 Dọn dẹp

kubectl delete crd widgets.kkloud.io      # xóa CRD cuốn theo mọi Widget
kubectl delete namespace crd-demo

Xóa CRD gỡ luôn endpoint API và mọi custom resource thuộc kiểu đó khỏi etcd. Manifest ở github.com/nghiadaulau/kubernetes-from-scratch, thư mục 57-crd.

Tổng kết

CustomResourceDefinition thêm một kiểu object mới vào API server mà không sửa hay khởi động lại nó: khai group, version (served/storage), scope, names, và openAPIV3Schema. Sau khi áp, kubectl phục vụ kiểu đó như resource gốc — ta tạo Widget hợp lệ, và hai cái sai schema (size vượt max, color ngoài enum) bị API server từ chối ngay, vì validate chạy tại API server theo schema. additionalPrinterColumns tùy biến cột kubectl get; subresources: {status: {}} tách status thành endpoint riêng để phân quyền spec/status — nền cho mô hình controller ở Bài 59. Custom resource lưu trong etcd dưới /registry/<group> cạnh resource gốc.

CRD mới chỉ là cấu trúc dữ liệu — tạo một Widget chưa làm gì xảy ra cả, vì chưa có ai phản ứng với nó. Bài 58 thêm một mắt xích vào đường ghi: admission webhook, một dịch vụ ngoài mà API server gọi để chấp nhận, từ chối, hoặc sửa object trước khi lưu.

Related Posts