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ỹ
- 2. Custom Resource vs Custom Resource Definition
- 3. OpenAPI v3 Schema Validation
- 4. Viết CRD bằng tay (Hands-on)
- 5. Thêm Enum và Short Names
- 6. Status, Scale Sub-resource và Printer Columns
- 7. Cách xem và thao tác với Custom Resource
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:
- Tạo endpoint cho form đó
- Validate các object được tạo dựa trên schema
- 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 webappshoạt độngkubectl explain webapp.spechoạt động- RBAC rules có thể target
webapps.webapp.codecloud.com
Các trường quan trọng của CRD
| Trường | Mô tả |
|---|---|
group | Identifier cho API group của bạn |
names.plural | Tên dùng trong kubectl (vd: webapps) |
names.singular | Tên số ít (vd: webapp) |
names.shortNames | Danh sách tên viết tắt cho kubectl |
scope | Namespaced hoặc Cluster |
versions | Danh 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,maxLengthpattern(regex)enum(tập giá trị đóng — ví dụ: dev, staging, prod)
Numbers:
minimum,maximumexclusiveMinimum
Arrays:
minItems,maxItemsuniqueItems
Required fields:
requiredtạ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.
apiVersionluôn làapiextensions.k8s.io/v1metadata.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.namephả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: truenghĩa là version đó reachable qua APIstorage: truechọ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
- Sai quy tắc đặt tên
metadata.name: phải là<plural>.<group> - 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 NAME và AGE. 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