ValidatingAdmissionPolicy (CEL) — Policy In-Process Không Cần Webhook
Vì sao CEL policy là hướng đi của tương lai
Webhook (file 2, 3, 4) mạnh nhưng mang theo cả một va-li gánh nặng vận hành: một HTTPS server phải HA, một cert phải gia hạn, một round-trip HTTP phải nằm trong latency budget, và một loạt failure mode có thể đánh sập cluster. Với phần lớn policy thực tế — vốn chỉ là "field này phải thỏa điều kiện kia" — toàn bộ gánh nặng đó là lãng phí. Bạn không cần một server riêng để kiểm tra "Pod có runAsNonRoot không".
Kubernetes giải bài toán này với ValidatingAdmissionPolicy (VAP) — cơ chế chạy logic policy bằng CEL (Common Expression Language) ngay bên trong API server, không qua webhook. VAP đạt GA (stable) ở Kubernetes 1.30 (ValidatingAdmissionPolicy), nghĩa là trên GKE phiên bản hiện đại nó luôn sẵn sàng. Đây là một thay đổi kiến trúc lớn: policy chuyển từ "code chạy ngoài process" sang "biểu thức khai báo chạy in-process".
Lợi ích cốt lõi, và là luận điểm trung tâm file này: VAP loại bỏ gần như toàn bộ failure mode của webhook:
- Không có service riêng → không có gì để chết (file 3).
- Không có HTTPS → không có cert để hết hạn (file 4).
- Không có round-trip → latency gần như bằng 0, không timeout (file 3).
- Đánh giá in-process → không bị deadlock kiểu webhook tự-validate.
Đổi lại, CEL kém biểu cảm hơn Rego (Gatekeeper, file 6): không gọi được hệ thống ngoài, hỗ trợ cross-object hạn chế. Đây là đánh đổi trung tâm khi chọn engine (cuối file).
Ba API object: Policy + Binding + Param
VAP tách định nghĩa policy khỏi việc áp dụng — tương tự mô hình ConstraintTemplate/Constraint của Gatekeeper (file 6), nhưng native trong Kubernetes:
1. ValidatingAdmissionPolicy — định nghĩa logic
Khai báo logic trừu tượng: kiểm gì, trên tài nguyên nào.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "require-non-root.example.com"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: "object.spec.securityContext.runAsNonRoot == true"
reason: Forbidden
message: "Pod phải đặt securityContext.runAsNonRoot=true"2. ValidatingAdmissionPolicyBinding — áp dụng + phạm vi + action
Một policy là trừu tượng; Binding kích hoạt nó, quyết định phạm vi (namespace nào) và hành động (Deny/Warn/Audit):
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "require-non-root-binding"
spec:
policyName: "require-non-root.example.com"
validationActions: [Deny]
matchResources:
namespaceSelector:
matchLabels:
environment: production # chỉ áp cho namespace productionTách Policy khỏi Binding cho phép một policy, nhiều cách áp: cùng policy require-non-root có thể Deny ở production nhưng chỉ Warn ở staging — bằng hai Binding khác nhau, không sửa policy.
3. paramRef — tham số hóa policy
Như Gatekeeper, VAP cho phép tham số hóa để tái dùng logic với giá trị khác nhau. Policy khai paramKind (một CRD hoặc native type như ConfigMap), Binding trỏ tới instance cụ thể qua paramRef:
# Trong Policy:
spec:
paramKind:
apiVersion: rules.example.com/v1
kind: ReplicaLimit
validations:
- expression: "object.spec.replicas <= params.maxReplicas"
---
# Trong Binding:
spec:
paramRef:
name: "replica-limit-prod"
parameterNotFoundAction: Denyparams trong CEL trỏ tới object tham số. parameterNotFoundAction quyết định hành vi khi không tìm thấy param (Deny an toàn hơn — không có param thì chặn).
Biến CEL khả dụng
CEL trong VAP truy cập một tập biến cố định (VAP CEL variables):
| Biến | Ý nghĩa |
|---|---|
object | Object đang được kiểm (đã qua mutation — file 1). null với DELETE |
oldObject | Trạng thái cũ (chỉ UPDATE). null với CREATE |
request | Thông tin request: request.userInfo, request.operation, request.dryRun... |
params | Object tham số từ paramRef (nếu policy có paramKind) |
namespaceObject | Object Namespace của tài nguyên (cho phép kiểm theo label/annotation namespace) |
authorizer | Cho phép kiểm tra quyền RBAC trong CEL (nâng cao) |
Biểu thức CEL trả về boolean: true = hợp lệ, false = vi phạm. Ví dụ thực tế:
validations:
# Image phải từ registry được duyệt
- expression: "object.spec.containers.all(c, c.image.startsWith('asia-docker.pkg.dev/'))"
message: "image phải từ asia-docker.pkg.dev"
# Không cho đổi serviceAccountName khi update
- expression: "oldObject == null || object.spec.serviceAccountName == oldObject.spec.serviceAccountName"
message: "không được đổi serviceAccountName"
# Replica tối thiểu cho namespace production
- expression: "namespaceObject.metadata.labels['environment'] != 'production' || object.spec.replicas >= 3"
message: "workload production phải có ≥3 replica"Sức mạnh của CEL nằm ở các macro như all, exists, filter, map trên list — đủ biểu đạt phần lớn policy thực tế gọn gàng.
matchConstraints, matchConditions, variables
Ba cơ chế thu hẹp và tổ chức:
matchConstraints.resourceRules— nhưrulescủa webhook: quyết định loại tài nguyên/operation nào kích hoạt policy. CóexcludeResourceRules,namespaceSelector,objectSelectorđể loại trừ.matchConditions— bộ lọc CEL trước khi chạyvalidations: chỉ chạy policy nếu điều kiện thỏa. Ví dụ chỉ kiểm Deployment lớn:
matchConditions:
- name: "only-large-deployments"
expression: "object.spec.replicas > 10"variables— định nghĩa biểu thức trung gian tái dùng, tránh lặp và tăng dễ đọc:
spec:
variables:
- name: containers
expression: "object.spec.containers"
validations:
- expression: "variables.containers.all(c, has(c.resources.limits))"
message: "mọi container phải có resources.limits"validationActions: Deny / Warn / Audit
Đặt ở Binding (không phải Policy) — cho phép cùng policy áp khác nhau theo môi trường (validation actions):
Deny— vi phạm → từ chối request.Warn— vi phạm → cảnh báo cho người dùng (không chặn).Audit— vi phạm → ghi vào audit annotation (không chặn, không cảnh báo).
Có thể kết hợp ([Warn, Audit]) nhưng Deny và Warn không dùng chung (thừa: Deny đã chặn). Đây lại là mô hình rollout có kỷ luật của cả chương: bắt đầu [Audit] hoặc [Warn, Audit], quan sát, rồi chuyển [Deny].
failurePolicy của VAP
VAP cũng có failurePolicy, nhưng ngữ nghĩa khác webhook: nó xử lý lỗi đánh giá biểu thức CEL (ví dụ biểu thức truy cập field không tồn tại gây lỗi runtime), không phải lỗi mạng/timeout (VAP không có mạng).
Fail(mặc định) — lỗi đánh giá → xử lý theovalidationActions(Deny → chặn).Ignore— lỗi đánh giá → bỏ qua.
Vì không có round-trip, failurePolicy: Fail của VAP an toàn hơn nhiều so với webhook: không có "service chết" để gây outage hàng loạt — chỉ có biểu thức lỗi trên object cụ thể. Đây là một lý do nữa VAP ít rủi ro hơn webhook. Dù vậy, vẫn nên viết CEL phòng thủ (dùng has() kiểm field tồn tại trước khi truy cập) để tránh lỗi đánh giá ngay từ đầu.
MutatingAdmissionPolicy — mutation bằng CEL
VAP chỉ validate. Cho mutation bằng CEL (thay mutating webhook), Kubernetes phát triển MutatingAdmissionPolicy — cơ chế tương tự nhưng dùng CEL để sửa object (qua applyConfiguration hoặc JSON Patch biểu đạt bằng CEL). Tại thời điểm viết, MutatingAdmissionPolicy mới hơn VAP và đang ở giai đoạn beta/tiến tới GA ở các bản Kubernetes gần đây — kiểm tra phiên bản cluster GKE của bạn để biết nó đã khả dụng chưa. Khi GA, nó sẽ loại bỏ phần lớn mutating webhook đơn giản (thêm label, điền default) khỏi nhu cầu vận hành server riêng.
Ma trận chọn engine: webhook / Gatekeeper / CEL policy
Đây là quyết định kiến trúc trung tâm của cả chương. Tổng hợp ba engine:
| Tiêu chí | Webhook tự viết | Gatekeeper / Policy Controller | ValidatingAdmissionPolicy (CEL) |
|---|---|---|---|
| Chạy ở đâu | Server ngoài (của bạn) | Pod trong cluster (OPA) | In-process API server |
| Cert | Cần (file 4) | Cần (webhook Gatekeeper) | Không |
| Failure mode | Nhiều (file 3) | Như webhook | Rất ít |
| Biểu cảm | Tùy ý (mọi ngôn ngữ) | Cao (Rego) | Trung bình (CEL) |
| Gọi hệ thống ngoài | Có | Không | Không |
| Cross-object | Tự làm | Có (referential) | Hạn chế |
| Thư viện chuẩn | Không | CIS/PCI/NIST | Không |
| Mutation | Có | Có | MutatingAdmissionPolicy |
Cây quyết định khuyến nghị:
- Policy là "field thỏa điều kiện" trên một object, biểu đạt được bằng CEL? → VAP. Ít rủi ro nhất, nên là lựa chọn mặc định cho validating policy.
- Cần cross-object, compliance bundle chuẩn, hay logic Rego phức tạp? → Gatekeeper/Policy Controller.
- Cần gọi hệ thống ngoài (scanner, CMDB) hoặc mutation rất phức tạp? → Webhook tự viết (chấp nhận gánh nặng cert + HA).
- Hardening Pod theo profile chuẩn? → PSA (file 5), không cần engine nào ở trên.
Xu hướng rõ ràng: dịch chuyển từ webhook sang CEL policy cho mọi thứ biểu đạt được, dành webhook cho các trường hợp thực sự cần ngoài-process.
Production architecture patterns
Thay webhook validating đơn giản bằng VAP
Nếu bạn đang vận hành các validating webhook chỉ kiểm field (image registry, label bắt buộc, runAsNonRoot), cân nhắc migrate sang VAP. Lợi ích vận hành tức thì: bỏ được cert-manager cho webhook đó, bỏ HA server, bỏ lo timeout. Quy trình: viết VAP tương đương, Binding ở [Audit] trước để xác nhận hành vi khớp webhook cũ, rồi chuyển [Deny] và gỡ webhook.
Một policy, nhiều môi trường qua Binding
Tận dụng tách Policy/Binding: định nghĩa policy một lần, tạo Binding [Warn] cho staging và [Deny] cho production. Cùng quy tắc, độ nghiêm khác nhau, không trùng lặp logic.
VAP làm guardrail bổ sung cho ResourceQuota
Tài liệu Kubernetes gợi ý dùng VAP để bảo vệ chính ResourceQuota — ngăn người dùng (kể cả có RBAC) sửa/xóa ResourceQuota object trừ một số danh tính được duyệt. Ví dụ một VAP chặn DELETE trên ResourceQuota nếu request.userInfo không thuộc nhóm admin. Đây là dạng policy "meta" mà CEL làm gọn.
Common mistakes / anti-patterns
Dùng webhook cho policy CEL biểu đạt được
Như nhấn mạnh xuyên file: nếu CEL đủ, webhook là gánh nặng vô ích (cert, HA, failure mode). Mặc định nên là VAP.
CEL không phòng thủ với field thiếu
object.spec.securityContext.runAsNonRoot gây lỗi đánh giá nếu securityContext không tồn tại. Dùng has() và toán tử an toàn: has(object.spec.securityContext) && object.spec.securityContext.runAsNonRoot == true. Lỗi đánh giá + failurePolicy: Fail = chặn nhầm object hợp lệ.
Quên rằng object là object đã mutate
Như mọi validating phase (file 1), VAP thấy object sau mutating webhook. Nếu một mutating webhook thêm container, CEL all(c, ...) sẽ kiểm cả container đó.
Bật [Deny] thẳng không qua Audit
Giống mọi enforcement: object cũ/hợp lệ-theo-cách-khác có thể bị chặn bất ngờ. Luôn [Audit]/[Warn] trước.
GCP-native implementation guidance
Kiểm tra VAP có khả dụng và liệt kê policy hiện có:
kubectl api-resources | grep validatingadmissionpolic
kubectl get validatingadmissionpolicies
kubectl get validatingadmissionpolicybindingsThử policy ở chế độ Audit trước (Binding validationActions: [Audit]), rồi tìm vi phạm trong audit log GKE (file 9):
resource.type="k8s_cluster"
protoPayload.responseObject.metadata.annotations."validation.policy.admission.k8s.io/validation_failure":*Trên GKE, VAP là cơ chế native (không cần cài gì) cho cluster đủ phiên bản — ưu tiên nó cho policy validating mới thay vì dựng webhook. Tham khảo GKE — about CEL in admission control và tài liệu Kubernetes cho cú pháp CEL đầy đủ.