Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 54 — Policy as Code — OPA, Kyverno

Đã đọc 0/15 mục · còn khoảng 11 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

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.
Vậy bạn làm gì? Cách phổ biến nhất — và cũng tệ nhất — là viết một trang Confluence liệt kê các quy tắc này, rồi... hy vọng (hope) mọi người đọc và tuân thủ. Thực tế thì developer mới vào không đọc, người đang gấp deadline thì bỏ qua, và reviewer trong pull request thì mỏi mắt không soi hết mọi manifest YAML. Kết quả: ba tháng sau bạn phát hiện 40% workload trong production không có resource limits, vài service chạy root, và một PVC 2Ti bị bỏ quên đang đốt tiền mỗi ngày.

Đâ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?
Chính ở bước 3, policy engine chen vào qua cơ chế Dynamic Admission Webhook. Có hai loại:

  • 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.
Policy as Code không chỉ chạy lúc admission. Nó nên chạy ở nhiều tầng (shift left): trong IDE của developer, trong CI pipeline (khi review PR), và cuối cùng ở admission như hàng rào cuối. Bắt lỗi càng sớm, sửa càng rẻ.

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/GatekeeperKyverno
Ngôn ngữRego (phải học riêng)YAML thuần
Phạm viĐa mục đích, ngoài K8sChuyên Kubernetes
Đường cong họcDốcThoải
Sức mạnh logicRất caoĐủ cho hầu hết K8s use case
Mutate/GenerateHạn chế (mạnh về validate)Hỗ trợ tốt cả ba
Nguyên tắc thực dụng: nếu bạn chỉ làm Kubernetes và muốn đội ngũ tiếp cận nhanh, chọn Kyverno. Nếu bạn cần policy trải rộng nhiều hệ thống (microservice authz, Terraform, CI), OPA cho bạn một ngôn ngữ thống nhất.

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.
Bài học: Đừng bao giờ bật Enforce trên cluster có sẵn workload mà không qua giai đoạn Audit. Policy as Code cần được rollout dần dần y như một feature.

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 tra PolicyReport để 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 ClusterPolicy Kyverno từ chối mọi container không đặt runAsNonRoot: 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 validate với deny/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.
Khi bạn đã có Policy as Code, ba quy tắc ở đầu bài không còn là lời cầu nguyện trên wiki — chúng trở thành hàng rào tự động, nhất quán, không cảm tính, bảo vệ cluster của bạn mỗi ngày.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi