Skip to main content

Part 2.Custom Resource Definitions Deep Dive

Cập nhật: 2026-09-16

Mục lục


1. CRD là gì và tại sao cần thiết kế kỹ

Bài toán thực tế

Bạn có một custom resource đang chạy tốt trên dev cluster. Mọi thứ trông ổn. Nhưng khi apply lên staging, API server từ chối với validation error không mong đợi. Bạn đào sâu vào và nhận ra: schema ban đầu chưa bao giờ được thiết kế đúng.

Nó quá permissive — chấp nhận mọi giá trị rác mà không phàn nàn gì. Giờ phải version-bump CRD, migrate objects cũ, rồi giải thích với team rằng operator đã hoàn thiện lại cần breaking change ngay ngày thứ hai.

Root cause

Tất cả xuất phát từ một lỗi phổ biến: đối xử với CRD như một bước đăng ký nhanh, thay vì như những gì nó thực sự là — một API server.

Hãy nghĩ về Kubernetes API server như một nhân viên tiếp nhận biểu mẫu cực kỳ nghiêm ngặt

Trước khi ai đó nộp đơn, bạn phải thiết kế sẵn mẫu đơn: tên gì, có những trường nào, giá trị cho phép là gì.

  • CRD (Custom Resource Definition) — là mẫu đơn trắng chưa điền
  • CR (Custom Resource) — là đơn đã điền đầy đủ thông tin mà user nộp lên

Tại sao thiết kế cẩn thận quan trọng

Kubernetes đối xử với CRD của bạn giống hệt như với Pod hay Deployment. Nếu thiết kế CRD cẩu thả, mọi controller built on top sẽ thừa hưởng sự cẩu thả đó.

Những gì sẽ học trong phần này

  • Custom Resource ở cấp độ API server là gì
  • Viết CRD bằng tay (không qua Kubebuilder) để thấy rõ metadata, versions block, OpenAPI v3 schema
  • Thiết kế schema để form từ chối input xấu trước khi nó đến controller
  • Dùng structural schemas, required fields, defaults, enums
  • Validation rules với CEL (Common Expression Language)
  • Status sub-resource, scale sub-resource, printer columns, short names

2. Custom Resource vs Custom Resource Definition

Ví dụ thực tế

Team của bạn vừa xây xong một nền tảng web application. Bạn muốn deploy và quản lý web app instances trên Kubernetes, mỗi instance có replica count, ingress rules, và resource limits riêng.

Bạn thử mô tả web app như một Kubernetes object — nhưng API server từ chối. Kubernetes chưa từng nghe về "web app". Nó chỉ biết những gì nó mang sẵn: pod, service, deployment, job.

CRD giải quyết vấn đề đó: dạy Kubernetes một "từ mới".

Hai khái niệm cần phân biệt

CRD (Custom Resource Definition) là mẫu đơn trắng bạn đăng ký với Kubernetes. Còn CR (Custom Resource) là một bản đã điền đầy — ví dụ một web app tên MyFrontend với image và replica count thực sự.

Khi bạn apply CRD, bạn đang đưa Kubernetes mẫu đơn trắng. API server đăng ký tên form đó (webapp), và từ đó kubectl có thể tạo và đọc các web app forms đã điền. Schema trên mẫu đơn là thứ giúp API server validate mỗi submission. CRD định nghĩa hình dạng (shape); còn behavior đến từ controller.

CR sống ở đâu

CR sống trong etcd, tạo bằng kubectl, và hoạt động y hệt pod hay deployment. Điểm khác biệt: Kubernetes không có built-in handling cho nó. kind, API group, và schema đều do bạn quyết định.

CRD template cũng là một Kubernetes object

CRD template chính nó là một Kubernetes object thuộc API group apiextensions.k8s.io/v1. Khi apply, API server:

  1. Tạo endpoint cho form đó
  2. Validate các object được tạo dựa trên schema
  3. Lưu các object được chấp nhận vào etcd dưới group/version của bạn

Ví dụ: CRD template định nghĩa WebApp kind trong group webapp.codecloud.com. Sau khi đăng ký:

  • kubectl get webapps hoạt động
  • kubectl explain webapp.spec hoạt động
  • RBAC rules có thể target webapps.webapp.codecloud.com

Các trường quan trọng của CRD

TrườngMô tả
groupIdentifier cho API group của bạn
names.pluralTên dùng trong kubectl (vd: webapps)
names.singularTên số ít (vd: webapp)
names.shortNamesDanh sách tên viết tắt cho kubectl
scopeNamespaced hoặc Cluster
versionsDanh sách các version, mỗi version có served flag và storage flag

CRD không làm gì nếu không có Controller

CRD tự nó không làm gì cả. Apply chỉ CRD, tạo một web app, và object đó cứ nằm đó mãi mãi. Không deployment, không service, không pods được tạo. API server đã lưu object của bạn, nhưng không có controller nào đang watch nó.

💡 Hình dung: CRD giống như việc bạn thiết kế mẫu đơn xin việc và đặt in 1000 bản. Nhưng đơn đó không tự động được ai điền và xử lý — phải có người (controller) nhặt đơn lên và thực hiện công việc. CRD = mẫu đơn, Controller = người điền và xử lý đơn.


3. OpenAPI v3 Schema Validation

Vấn đề không có schema

CRD không có schema sẽ chấp nhận mọi YAML bạn ném vào. Typos trong field names, string ở chỗ cần integer, replicas là số âm (-7). Đó là cách controller kết thúc với data rác.

OpenAPI v3 schema là tuyến phòng thủ đầu tiên. API server enforce nó trước khi controller bao giờ nhìn thấy object.

Form model

Schema in trên CRD template là tập hợp các quy tắc in trên mẫu đơn — định nghĩa box nào bắt buộc, giá trị nào được phép, entry nào phải từ chối trước khi lưu.

Schema location

Trên CRD template, schema nằm dưới spec.versions.schema.openAPIV3Schema, theo chuẩn OpenAPI v3. Đây là nơi mô tả schema dưới dạng cây: type, properties, và required.

Các ràng buộc phổ biến

Strings:

  • minLength, maxLength
  • pattern (regex)
  • enum (tập giá trị đóng — ví dụ: dev, staging, prod)

Numbers:

  • minimum, maximum
  • exclusiveMinimum

Arrays:

  • minItems, maxItems
  • uniqueItems

Required fields:

  • required tại mỗi object level liệt kê child properties bắt buộc

Default values

Đặt default trên một field. Khi user bỏ qua field đó, API server tự động điền giá trị mặc định khi ghi. Controller không cần xử lý trường hợp empty.

Defaults chỉ apply cho fields mà schema thực sự mô tả.

Pruning — Structural Schemas

Trong Kubernetes, structural schema là mẫu form được đánh máy đầy đủ. Bất kỳ thứ gì không được khai báo trên template sẽ bị strip trước khi lưu — nghĩa là không có chuyện user giấu data tùy ý bên trong CR.

💡 Hình dung: Giống như mẫu đơn chỉ có ô "Họ tên", "Địa chỉ", "Số điện thoại" — bạn không thể viết thêm "Sở thích" hay "Ghi chú" vào mẫu vì mẫu đơn không có ô đó.

Kubernetes Extension Fields

x-kubernetes-preserve-unknown-fields: opt-out khỏi pruning nếu bạn thực sự cần free-form blob trong subtree.

x-kubernetes-int-or-string: field chấp nhận integer HOẶC string — theo cách Kubernetes làm với ports và quantities.

x-kubernetes-validations: nơi validation rules sống. Ở đây cần refer đến CEL (Common Expression Language).

CEL — Common Expression Language

CEL cho phép viết expression-based validations chạy trong API server không cần webhook. Ví dụ:

x-kubernetes-validations:
- rule: "self.replicas <= 100"
message: "replicas must be at most 100"
- rule: "self.minReplicas <= self.maxReplicas"
message: "minReplicas must be less than or equal to maxReplicas"

CEL cover cross-field checks mà plain OpenAPI không thể express, và chúng rẻ vì chạy in-process.

Lưu ý quan trọng khi thay đổi schema

Stored objects không được revalidated khi bạn thay đổi schema trên CRD hiện có. Chỉ new writes được validate. Vì vậy:

  • Tighten constraints cẩn thận
  • Version your API khi shape thay đổi theo cách có ý nghĩa

4. Viết CRD bằng tay (Hands-on)

Chuẩn bị

CRD là một Kubernetes object, nên có cùng top-level fields đã biết: apiVersion, kind, metadata, spec.

  • apiVersion luôn là apiextensions.k8s.io/v1
  • metadata.name đặc biệt: phải là plural name + dấu chấm + API group

Cấu trúc CRD cơ bản

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: webapps.apps.gocodecloud.com # <-- QUAN TRỌNG: plural.group
spec:
group: apps.gocodecloud.com
scope: Namespaced
names:
plural: webapps
singular: webapp
kind: WebApp
shortNames:
- wa
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required:
- image
- replicas
properties:
image:
type: string
replicas:
type: integer
minimum: 1
maximum: 10
port:
type: integer
default: 8080

Quy tắc đặt tên

  • metadata.name phải theo format: <plural>.<group> (vd: webapps.apps.gocodecloud.com)

Versions

CRD có thể serve nhiều versions, nhưng chính xác một version phải có storage: true — đó là version được ghi vào etcd.

  • served: true nghĩa là version đó reachable qua API
  • storage: true chọn version được persist vào etcd

Apply và kiểm tra

kubectl apply -f webapp-crd.yaml
kubectl wait --for=condition=established --timeout=60s -f webapp-crd.yaml

CRD không sử dụng được cho đến khi API server set established condition — status flag báo hiệu type mới đã được đăng ký đầy đủ và sẵn sàng accept objects.

kubectl explain tự động nhận diện

Discovery doc cập nhật tự động. Không restart, không plugin cần thiết:

kubectl explain webapp.spec
# Output:
# KIND: WebApp
# VERSION: apps.gocodecloud.com/v1alpha1
# FIELD: spec <Object>
#
# DESCRIPTION:
# <miêu tả nếu có>
#
# FIELDS:
# image <string> -- required
# port <integer>
# replicas <integer> -- required

Test validation — Schema từ chối input xấu

Tạo CR với giá trị invalid:

apiVersion: apps.gocodecloud.com/v1alpha1
kind: WebApp
metadata:
name: bad-sample
spec:
image: nginx
replicas: 99 # > maximum 10
kubectl apply -f bad-sample.yaml
# Error:
# ✗ The WebApp "bad-sample" is invalid:
# spec.replicas: Invalid value: 99:
# spec.replicas.body should be less than or equal to 10

API server từ chối trước khi object đến etcd.

Default hoạt động thế nào

kubectl apply -f fixed-sample.yaml # replicas: 3, không có port
kubectl get webapp fixed-sample -o json | jq '.spec'

Output:

{
"image": "nginx",
"replicas": 3,
"port": 8080 # <-- API server điền default!
}

Dù không set port, default 8080 được apply phía server-side.

Hai lỗi phổ biến nhất

  1. Sai quy tắc đặt tên metadata.name: phải là <plural>.<group>
  2. Quên rằng chính xác một version phải có storage: true

5. Thêm Enum và Short Names

Thêm Short Names

spec:
names:
shortNames:
- wa

Cho phép kubectl dùng wa thay cho webapps.

kubectl get wa # thay vì kubectl get webapps

Thêm Enum validation

spec:
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
size:
type: string
enum:
- small
- medium
- large

Demo: Enum hoạt động

# Tạo widget với size hợp lệ
kubectl apply -f widget.yaml # size: medium → ✓ accepted

# Tạo widget với size không hợp lệ
kubectl apply -f bad-widget.yaml # size: huge → ✗ rejected
# Error: spec.size: Invalid value: "huge": spec.size must be one of: small, medium, large

Schema enforce rule tại admission time.


6. Status, Scale Sub-resource và Printer Columns

Baseline — CRD cơ bản có vấn đề gì

CRD cơ bản hoạt động, nhưng chưa feel như built-in resource:

kubectl get wa
# NAME AGE
# myapp 10s
# Chỉ có name và age, không có thông tin hữu ích

kubectl scale wa myapp --replicas=5
# ✗ Error: does not implement the scale subresource

# kubectl apply ghi đè status không được bảo vệ

Bốn trường CRD biến CR thành native resource

Không cần thay đổi controller, chỉ cần CRD edits.

1. Status Sub-resource

Vấn đề: Nếu status không được khai báo trong schema, API server sẽ prune nó.

Giải pháp: Thêm empty status block trong schema, rồi enable sub-resource:

spec:
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
type: object
properties:
spec: ...
status: # <-- khai báo status trong schema
type: object
x-kubernetes-preserve-unknown-fields: true
subresources:
status: {} # <-- enable /status endpoint

Lợi ích: Regular writes không thể touch .status. Chỉ có /status endpoint được phép update.

# Ghi qua subresource → được chấp nhận
kubectl patch wa myapp --subresource=status -p '{"status":{"readyReplicas":3}}'

# Ghi trực tiếp (không qua subresource) → bị drop SILENTLY
kubectl patch wa myapp -p '{"status":{"readyReplicas":5}}'
# Không có error, nhưng giá trị không thay đổi

2. Scale Sub-resource

Cho phép kubectl scale và HPA hoạt động:

subresources:
status: {}
scale:
specReplicasPath: .spec.replicas # JSON path đến desired count
statusReplicasPath: .status.replicas # JSON path đến observed count
labelSelectorPath: .status.selector # JSON path đến selector
# Bây giờ hoạt động!
kubectl scale wa myapp --replicas=5

# HPA cũng target được
kubectl autoscale wa myapp --min=2 --max=10 --cpu-percent=80

3. Printer Columns

Mặc định kubectl get chỉ hiện NAMEAGE. Thêm columns hữu ích:

additionalPrinterColumns:
- name: Replicas
type: integer
jsonPath: .spec.replicas
priority: 0 # luôn hiện
- name: Ready
type: string
jsonPath: .status.readyReplicas
priority: 1 # chỉ hiện khi dùng -o wide
- name: Image
type: string
jsonPath: .spec.image
priority: 0
kubectl get wa
# NAME REPLICAS IMAGE AGE
# myapp 3 nginx:latest 10s

4. "all" Category

categories:
- all
# Tools respect categories
kubectl api-resources --categories=all
# NAME APIVERSION CATEGORIES
# pods v1 all
# deployments apps/v1 all
# webapps apps.gocodecloud.com/v1alpha1 all <-- xuất hiện cùng built-ins

Lưu ý: kubectl get all là hard-coded list của built-ins, không expand vào CR của bạn.

Tổng kết

Bốn CRD fields này biến basic custom resource thành thứ feel như native Kubernetes type:

  • Status sub-resource bảo vệ status field
  • Scale sub-resource cho phép kubectl scale và HPA
  • Printer columns cho kubectl get output hữu ích
  • "all" category hiện trong kubectl api-resources

7. Cách xem và thao tác với Custom Resource

Các cách xem CR

1. kubectl get (với short name và printer columns)

kubectl -n widget-demo get widgets
kubectl -n widget-demo get wg # short name

Với printer columns: output hiện thêm size, color — không cần grep YAML.

2. kubectl get với JSON output đầy đủ

kubectl -n widget-demo get widget my-widget -o yaml

Output:

apiVersion: apps.gocodecloud.com/v1alpha1
kind: Widget
metadata:
creationTimestamp: "2026-09-16T00:00:00Z"
generateName: widget-
resourceVersion: "12345"
uid: a1b2c3d4-e5f6-7890-abcd-ef1234567890
generation: 1
spec:
color: "#4287f5"
replicas: 3
size: medium
status: {} # empty vì không có controller watch

Metadata được API server populate: creationTimestamp, UUID, resourceVersion, generation — không cần type tay. Đây là cách API server đối xử với CR giống hệt Pod hay Deployment.

3. JSONPath cho shell scripts

kubectl -n widget-demo get widget my-widget -o jsonpath='{.spec.color}'
# Output: #4287f5

Một field, một dòng, không parse. Dùng trong shell scripts.

4. kubectl describe cho human-readable

kubectl -n widget-demo describe widget my-widget

Output có spec broken out by field và events block ở dưới. Events block empty khi không có controller — khi operator bắt đầu reconcile, block đó sẽ đầy và là nơi đầu tiên bạn check khi có vấn đề.

5. kubectl explain — đọc từ schema

kubectl explain widget.spec
kubectl explain widget.spec.color

Output đọc trực tiếp từ OpenAPI v3 schema trong CRD. Description text bạn đặt trên field sẽ hiện trong explain.

Đây là lý do bạn nên invest thời gian vào schema descriptions khi viết CRD — nó là documentation miễn phí cho mọi user.

Schema validation khi apply

kubectl apply -f my-widget.yaml
# Nếu color không match pattern → API server reject với pattern violation error

Nguồn tham khảo

Nguồn gốc: Khóa "Kubernetes Operators" — bài "Custom Resource Definitions Deep Dive", nền tảng KodeKloud