Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là DevOps Engineer tại một công ty fintech ở TP.HCM. Đội của bạn có ba quy tắc "bất di bất dịch" khi deploy lên Kubernetes:
- Mọi container phải có resource limits (giới hạn CPU/RAM), nếu không một pod lỗi có thể ngốn hết tài nguyên node và kéo sập cả cụm.
- Không pod nào được chạy với quyền root, vì đó là cửa ngõ leo thang đặc quyền nếu container bị chiếm quyền.
- Mọi PVC (yêu cầu ổ đĩa) phải có dung lượng dưới 100Gi để tránh ai đó vô tình xin 10Ti và làm hóa đơn cloud tăng vọt.
Đây chính là bài toán mà Policy as Code giải quyết. Thay vì "viết doc và cầu nguyện", ta mã hóa (codify) các quy tắc thành code có thể chạy tự động, và để hệ thống từ chối những gì vi phạm — ngay tại thời điểm apply, trước khi nó kịp vào cluster. Bài này sẽ giúp bạn hiểu bản chất Policy as Code, hai công cụ trụ cột là OPA (Open Policy Agent) và Kyverno, và cách áp dụng thực tế trong Kubernetes.
Khái niệm cốt lõi
Policy as Code là gì
Policy as Code (PaC) là việc biểu diễn các quy tắc quản trị, bảo mật và tuân thủ dưới dạng code, được lưu trong Git, review qua pull request, kiểm thử tự động và thực thi bằng máy. Nó là một nhánh của tư tưởng "everything as code" — giống như Infrastructure as Code mô tả hạ tầng bằng code, Policy as Code mô tả luật lệ bằng code.
Điểm mấu chốt: policy không còn là văn bản để con người diễn giải, mà là logic máy tính có thể đánh giá một cách nhất quán, không cảm tính, không bỏ sót.
Vòng đời thực thi: từ đâu policy được kiểm tra
Trong Kubernetes, cơ chế thực thi chính là Admission Controller — một chốt chặn nằm giữa API Server và etcd. Khi bạn kubectl apply, request đi qua các bước:
- Authentication — bạn là ai?
- Authorization (RBAC) — bạn có quyền không?
- Admission Control — object bạn gửi có hợp lệ theo policy không?
- ValidatingAdmissionWebhook: chỉ nói "chấp nhận" hay "từ chối". Ví dụ: từ chối pod chạy root.
- MutatingAdmissionWebhook: sửa object trước khi lưu. Ví dụ: tự động thêm resource limits mặc định nếu thiếu.
OPA và Rego
OPA (Open Policy Agent) là một policy engine đa mục đích (general-purpose), không chỉ dành cho Kubernetes. Nó nhận vào một JSON input bất kỳ và trả về quyết định dựa trên policy bạn viết bằng ngôn ngữ Rego. Bạn có thể dùng OPA cho Kubernetes, cho API microservice (kiểm tra quyền truy cập), cho Terraform plan, cho CI/CD...
Trong Kubernetes, OPA thường đi kèm Gatekeeper — một dự án tích hợp OPA vào admission control với các khái niệm ConstraintTemplate (định nghĩa loại policy) và Constraint (áp dụng cụ thể).
Rego là ngôn ngữ khai báo (declarative). Một ví dụ đơn giản — từ chối pod chạy root:
package kubernetes.security
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
container.securityContext.runAsNonRoot != true
msg := sprintf("Container %v phải chạy với runAsNonRoot=true", [container.name])
}
Điểm mạnh của OPA: cực kỳ linh hoạt và mạnh mẽ, dùng được ngoài Kubernetes. Điểm yếu: Rego có đường cong học tập dốc — cú pháp lạ với người quen viết YAML/imperative.
Kyverno
Kyverno là policy engine sinh ra riêng cho Kubernetes. Điểm khác biệt lớn nhất: policy được viết bằng chính YAML, không cần học ngôn ngữ mới. Với người đã quen viết manifest Kubernetes, Kyverno gần như "đọc là hiểu".
Cùng quy tắc "phải có runAsNonRoot", policy Kyverno trông như sau:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-run-as-non-root
spec:
validationFailureAction: Enforce
rules:
- name: check-run-as-non-root
match:
any:
- resources:
kinds: ["Pod"]
validate:
message: "Pod phải chạy với runAsNonRoot=true"
pattern:
spec:
containers:
- securityContext:
runAsNonRoot: true
Kyverno hỗ trợ ba nhóm hành động: validate (kiểm tra), mutate (tự sửa/thêm giá trị mặc định), và generate (tự sinh resource, ví dụ tự tạo NetworkPolicy mặc định cho mọi namespace mới).
OPA/Gatekeeper vs Kyverno — chọn cái nào
| Tiêu chí | OPA/Gatekeeper | Kyverno |
|---|---|---|
| Ngôn ngữ | Rego (phải học riêng) | YAML thuần |
| Phạm vi | Đa mục đích, ngoài K8s | Chuyên Kubernetes |
| Đường cong học | Dốc | Thoải |
| Sức mạnh logic | Rất cao | Đủ cho hầu hết K8s use case |
| Mutate/Generate | Hạn chế (mạnh về validate) | Hỗ trợ tốt cả ba |
Tình huống thực tế
Ví dụ 1 — Fintech Việt Nam siết resource limits sau sự cố "node chết"
Một công ty ví điện tử tại Hà Nội (giả định, khoảng 60 kỹ sư) gặp sự cố nghiêm trọng: một service xử lý đối soát bị memory leak, không có memory limit, ăn dần hết 32GB RAM của node, khiến kubelet OOM-kill hàng loạt pod khác cùng node — trong đó có service thanh toán. Downtime 22 phút vào giờ cao điểm trưa, ước tính thiệt hại giao dịch lỡ hàng trăm triệu đồng.
Sau postmortem, họ triển khai Kyverno với một ClusterPolicy require-resource-limits, đặt validationFailureAction: Enforce. Kể từ đó, bất kỳ Deployment nào thiếu resources.limits.memory đều bị từ chối ngay khi apply, kèm thông báo rõ ràng cho developer. Họ còn thêm một policy mutate để tự gán limit mặc định (512Mi) cho các job phụ trợ ít quan trọng, tránh chặn cứng gây khó chịu.
Bài học: Quy tắc "phải có resource limits" tồn tại trên wiki suốt một năm mà không ai tuân thủ. Chỉ khi biến nó thành code có khả năng từ chối, tỷ lệ workload có limits mới nhảy từ ~55% lên 100% trong hai tuần.
Ví dụ 2 — Công ty SaaS Đông Nam Á và "hard gate" trong CI
Một startup SaaS ở Singapore phục vụ khách hàng khu vực ASEAN muốn đạt chứng nhận bảo mật để bán cho ngân hàng. Yêu cầu: không image nào được kéo từ registry công cộng tùy tiện, chỉ được dùng registry nội bộ registry.company.io. Họ dùng OPA với Conftest để quét manifest ngay trong GitHub Actions, trước cả khi vào cluster.
Mỗi pull request đụng tới file YAML deployment sẽ chạy conftest test với một policy Rego kiểm tra prefix của image. PR vi phạm bị fail check, không merge được. Nhờ chạy ở CI (shift left), developer nhận phản hồi trong 30 giây thay vì bị từ chối lúc deploy production. Họ tái dùng đúng file Rego đó ở tầng Gatekeeper trong cluster như hàng rào cuối.
Bài học: Cùng một policy nên được thực thi ở nhiều tầng. Chạy ở CI để phản hồi nhanh và thân thiện; chạy ở admission để đảm bảo không có đường vòng nào lọt qua.
Ví dụ 3 — Từ "Audit" sang "Enforce" một cách an toàn
Một đội platform tại một công ty logistics ở TP.HCM muốn cấm PVC lớn hơn 100Gi. Nhưng họ lo: bật Enforce ngay có thể chặn cả những workload hợp lệ đang chạy, gây gián đoạn. Cách họ làm rất khôn ngoan:
- Tuần 1–2: triển khai policy ở chế độ Audit (Kyverno) /
dryrun— chỉ ghi log vi phạm, không chặn. - Họ thu thập report, phát hiện 3 team đang dùng PVC 200–500Gi cho batch data, liên hệ và cấp exception qua namespace annotation.
- Tuần 3: chuyển sang Enforce sau khi dọn sạch vi phạm.
Hướng dẫn từng bước
Dưới đây là quy trình triển khai Kyverno để giải quyết đúng ba yêu cầu ở đầu bài.
Bước 1 — Cài đặt policy engine. Với Kyverno, dùng Helm:
helm repo add kyverno https://kyverno.github.io/kyverno/
helm install kyverno kyverno/kyverno -n kyverno --create-namespace
Bước 2 — Viết policy ở chế độ Audit trước. Bắt đầu với validationFailureAction: Audit để không chặn gì cả, chỉ quan sát.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-resource-limits
spec:
validationFailureAction: Audit
rules:
- name: validate-limits
match:
any:
- resources:
kinds: ["Pod"]
validate:
message: "Container phải có CPU và memory limits"
pattern:
spec:
containers:
- resources:
limits:
memory: "?*"
cpu: "?*"
Bước 3 — Kiểm tra report vi phạm. Xem các PolicyReport để biết workload nào đang vi phạm:
kubectl get policyreport -A
Bước 4 — Dọn vi phạm và liên hệ team liên quan. Cập nhật các Deployment thiếu limits, hoặc cấp exception có kiểm soát.
Bước 5 — Chuyển sang Enforce. Khi report đã sạch, đổi Audit thành Enforce. Từ giờ, vi phạm sẽ bị từ chối:
kubectl patch clusterpolicy require-resource-limits --type merge \
-p '{"spec":{"validationFailureAction":"Enforce"}}'
Bước 6 — Đưa policy vào Git và GitOps. Lưu tất cả policy YAML vào một repo, review qua PR, và để Argo CD/Flux đồng bộ vào cluster. Policy giờ đây có version, có lịch sử, có review — đúng tinh thần "as code".
Bước 7 — Shift left vào CI. Thêm bước quét policy trong pipeline. Với Kyverno có thể dùng kyverno apply CLI để test manifest offline; với OPA dùng conftest. Cách này bắt lỗi trước khi PR được merge.
Lỗi thường gặp & mẹo
Bật Enforce ngay từ đầu trên cluster production. Đây là lỗi phổ biến nhất và nguy hiểm nhất. Bạn có thể vô tình chặn deployment hợp lệ, kể cả pod của chính hệ thống. Luôn đi qua Audit trước.
Quên loại trừ system namespace. Nếu policy áp lên cả kube-system hay namespace của chính policy engine, bạn có thể tự khóa cụm hoặc gây crash loop cho component nền tảng. Dùng exclude để bỏ qua kube-system, kyverno, các namespace hạ tầng.
Coi webhook admission là điểm lỗi đơn (single point of failure). Nếu policy engine chết và webhook đặt failurePolicy: Fail, mọi apply có thể bị chặn — cả việc sửa lỗi. Cân nhắc cấu hình timeout hợp lý và hiểu rõ đánh đổi giữa Fail (an toàn hơn, rủi ro chặn) và Ignore (linh hoạt hơn, rủi ro lọt policy).
Viết policy quá cứng, không có cơ chế exception. Thực tế luôn có ngoại lệ hợp pháp (batch job cần PVC lớn). Thiết kế sẵn cơ chế exception qua label/annotation hoặc PolicyException, kèm quy trình phê duyệt — thay vì để developer "lách" bằng cách tắt policy.
Chỉ chạy ở admission mà bỏ qua CI. Bị từ chối lúc deploy tạo trải nghiệm tệ và vòng phản hồi dài. Luôn shift left: chạy cùng bộ policy trong CI để developer biết sớm.
Mẹo: Bắt đầu với một bộ policy có sẵn thay vì viết từ đầu. Kyverno có thư viện Policy Library hàng trăm policy mẫu (Pod Security Standards, best practices), OPA Gatekeeper cũng có library tương tự. Đừng phát minh lại bánh xe.
Bài tập thực hành
- Cài đặt & quan sát: Dựng một cluster local (kind hoặc minikube), cài Kyverno, và apply policy
require-resource-limitsở chế độ Audit. Deploy một pod cố tình thiếu limits rồi kiểm traPolicyReportđể thấy vi phạm được ghi nhận.
- Chuyển Audit → Enforce: Đổi policy sang
Enforce, thử apply lại pod thiếu limits và quan sát thông báo từ chối. Ghi lại nội dung message trả về.
- Viết policy "no root": Tự viết một
ClusterPolicyKyverno từ chối mọi container không đặtrunAsNonRoot: true. Test với một pod chạy root và một pod hợp lệ.
- Ràng buộc dung lượng PVC: Viết policy chặn PVC yêu cầu storage lớn hơn 100Gi. Gợi ý: dùng phép so sánh trong khối
validatevớideny/pattern.
- So sánh hai engine (nâng cao): Viết lại policy "no root" ở bài 3 bằng Rego cho OPA/Gatekeeper. So sánh trải nghiệm và độ dài code giữa hai cách. Bạn thấy cái nào dễ đọc hơn cho đội của mình?
- Shift left: Thêm một bước trong GitHub Actions (hoặc GitLab CI) dùng
kyverno applyđể test manifest trước khi merge. Tạo một PR vi phạm và xác nhận check bị fail.
Tóm tắt
- Policy as Code biến các quy tắc quản trị, bảo mật, tuân thủ từ văn bản "viết doc rồi hy vọng" thành code chạy được, tự động thực thi, lưu trong Git và review qua PR.
- Trong Kubernetes, cơ chế thực thi chính là Admission Webhook (validating để chặn, mutating để sửa), nằm giữa API Server và etcd.
- OPA dùng ngôn ngữ Rego, đa mục đích, mạnh nhưng khó học; hợp khi policy cần trải rộng ngoài Kubernetes. Kyverno viết bằng YAML thuần, chuyên Kubernetes, dễ tiếp cận, hỗ trợ cả validate/mutate/generate.
- Luôn rollout theo trình tự Audit → dọn vi phạm → Enforce, loại trừ system namespace, thiết kế cơ chế exception, và shift left vào CI để phản hồi sớm.
- Cùng một bộ policy nên chạy ở nhiều tầng (IDE, CI, admission) để vừa thân thiện với developer vừa đảm bảo không có đường vòng nào lọt qua.