Skip to content

Organization Policies for GKE & Admission Debugging

Hai lớp guardrail khác nhau: GCP API vs Kubernetes admission

Toàn bộ chương tới giờ bàn về admission control bên trong Kubernetes — chặn cấu hình Pod/workload tại API server của cluster. Nhưng có một lớp guardrail hoàn toàn khác, nằm bên trên cluster: Organization Policy của GCP, chặn cấu hình chính cluster tại tầng GCP API (Resource Manager). Hiểu sự phân biệt này là điều quan trọng nhất của nửa đầu file.

Xét hai câu hỏi:

  • "Pod này có được chạy privileged không?" → câu hỏi về cấu hình Pod → Kubernetes admission (PSA, Gatekeeper, webhook, CEL — file 1-8).
  • "Cluster này có được phép tạo với public endpoint / không bật private nodes không?" → câu hỏi về cấu hình clusterGCP Organization Policy, chặn tại container.googleapis.com API trước khi cluster tồn tại.

Đây là hai lớp hoàn toàn tách biệt, ở hai tầng kiểm soát khác nhau:

[Người dùng tạo/sửa CLUSTER]

   ▼  GCP Resource Manager API
   ▼  ┌─────────────────────────────────────┐
      │ ORGANIZATION POLICY (tầng GCP)        │  ← chặn cấu hình CLUSTER
      │ "cluster phải private", "phải Autopilot"│     (file này, nửa đầu)
      └─────────────────────────────────────┘
   ▼  cluster được tạo

[Người dùng tạo POD trong cluster]

   ▼  Kubernetes API server (admission)
   ▼  ┌─────────────────────────────────────┐
      │ PSA / Gatekeeper / CEL / webhook      │  ← chặn cấu hình POD
      │ "Pod không được privileged"           │     (file 1-8)
      └─────────────────────────────────────┘

Điểm mạnh quyết định của Organization Policy: nó không thể bị bỏ qua từ bên trong cluster. Một admin cluster (kể cả cluster-admin RBAC) không thể tạo một cluster public nếu Org Policy của tổ chức cấm — vì việc chặn xảy ra ở tầng GCP IAM/Resource Manager, trên cả quyền Kubernetes. Đây là guardrail cấp tổ chức thực sự, trong khi Kubernetes admission có thể bị admin cluster tắt (xóa WebhookConfiguration, đổi PSA label). Hai lớp bổ trợ, không thay thế nhau.

Organization Policy cho GKE: managed và custom constraints

Organization Policy (xem nền tảng ở Chương 1 — Org Policies) áp ràng buộc lên tài nguyên GCP theo phân cấp Organization → Folder → Project. Có hai loại constraint áp dụng cho GKE:

Managed constraints

Là các constraint dựng sẵn do Google cung cấp, thường dạng list/boolean. Ví dụ liên quan GKE và workload trên đó: constraints/compute.requireShieldedVm, các constraint hạn chế vị trí tài nguyên (constraints/gcp.resourceLocations) ảnh hưởng region cluster được tạo, hay constraint về Binary Authorization. Bạn bật và cấu hình, không viết logic.

Custom constraints với CEL

Đây là phần mạnh và linh hoạt nhất: bạn viết custom constraint bằng CEL áp lên tài nguyên GKE cụ thể (Restrict actions on GKE resources). Cấu trúc một custom constraint cho GKE:

  • resourceTypes — loại tài nguyên GKE: container.googleapis.com/Cluster hoặc container.googleapis.com/NodePool.
  • methodTypes — RESTful method áp dụng: CREATE, hoặc CREATEUPDATE.
  • condition — biểu thức CEL trên resource.<field> (field tên theo camelCase của GKE API).
  • actionTypeALLOW (chỉ cho phép nếu condition đúng) hoặc DENY (chặn nếu condition đúng).

Ví dụ cụ thể (đối chiếu tài liệu GKE):

1. Bắt buộc cluster phải bật Autopilot — chặn tạo cluster Standard:

yaml
name: organizations/ORG_ID/customConstraints/custom.requireAutopilot
resourceTypes:
- container.googleapis.com/Cluster
methodTypes:
- CREATE
condition: "resource.autopilot.enabled == false"
actionType: DENY
displayName: "Bắt buộc dùng Autopilot"

2. Bắt buộc private nodes — chặn cluster có node với IP công khai:

yaml
resourceTypes:
- container.googleapis.com/Cluster
methodTypes:
- CREATE
condition: "resource.privateClusterConfig.enablePrivateNodes == false"
actionType: DENY

3. Ép node pool bật auto-upgrade — chỉ cho phép node pool có auto-upgrade:

yaml
resourceTypes:
- container.googleapis.com/NodePool
methodTypes:
- CREATE
condition: "resource.management.autoUpgrade == true"
actionType: ALLOW

4. Ràng node service account — chặn node pool dùng SA không được duyệt:

yaml
resourceTypes:
- container.googleapis.com/NodePool
methodTypes:
- CREATE
- UPDATE
condition: "resource.config.serviceAccount != 'restricted-sa@project.iam.gserviceaccount.com'"
actionType: DENY

Sau khi tạo custom constraint ở cấp organization, bạn enforce nó qua một Organization Policy gắn vào org/folder/project. Từ đó, mọi nỗ lực tạo/sửa cluster vi phạm sẽ bị gcloud/Terraform/Console từ chối ngay ở tầng API — không tài khoản nào trong phạm vi đó vượt qua được.

Vì sao điều này quan trọng cho governance

Org Policy cho GKE là cách enforce các quyết định kiến trúc không thể thương lượng ở quy mô tổ chức: "mọi cluster production phải private", "mọi cluster phải ở region được phép (data residency)", "không cluster nào được tạo ngoài Autopilot". Đây là tầng mà platform team đặt ra luật chơi, còn từng team không thể phá — khác hẳn Kubernetes admission vốn áp trong từng cluster và admin cluster có thể can thiệp. Kết hợp hai lớp: Org Policy khóa cấu hình cluster, Kubernetes admission khóa cấu hình workload.

Admission debugging: bộ công cụ

Nửa sau file: khi admission control chặn nhầm (hoặc không chặn như mong đợi), debug thế nào? Bốn công cụ chính.

1. Audit log — "ai chặn, vì sao"

Đây là nguồn sự thật đầu tiên. Trên GKE, audit log đi vào Cloud Logging. Một số truy vấn then chốt:

Tìm request bị từ chối bởi admission (mọi nguồn):

resource.type="k8s_cluster"
protoPayload.response.status="Failure"
protoPayload.response.reason="Forbidden"

Tìm vi phạm bị một webhook cụ thể từ chối — message thường chứa tên webhook:

resource.type="k8s_cluster"
protoPayload.response.message=~"admission webhook .* denied"

Tìm vi phạm PSA (mode audit ghi annotation):

resource.type="k8s_cluster"
protoPayload.responseObject.metadata.annotations."pod-security.kubernetes.io/audit-violations":*

Để debug "object lúc validating khác object tôi apply" (file 1), cần audit ở mức RequestResponse — nó ghi cả object sau mutation. GKE cho cấu hình audit policy mức chi tiết này (đối chiếu Chương 14 observability cho log routing). Lưu ý audit mức cao tốn tiền và sinh nhiều log — bật có chọn lọc.

2. Dry-run server-side — "nếu tôi apply thì sao"

kubectl --dry-run=server chạy request qua toàn bộ admission pipeline (kể cả webhook, PSA, VAP) nhưng không ghi etcd (file 2). Đây là công cụ debug an toàn nhất:

bash
# Object này có bị policy nào chặn không?
kubectl apply -f pod.yaml --dry-run=server
# Cảnh báo PSA warn và lỗi enforce hiện ngay tại đây

Khác với --dry-run=client (chỉ kiểm cú pháp phía client, không chạy admission), --dry-run=server là cách duy nhất biết policy thực sự phản ứng thế nào. Dùng nó trong CI để bắt vi phạm trước merge.

3. Log của chính webhook / Gatekeeper

Khi audit log nói "webhook X từ chối" nhưng không rõ vì sao, phải đọc log bên trong webhook server:

bash
# Log webhook server của bạn
kubectl logs -n webhook-system deploy/webhook-server --tail=100

# Với Gatekeeper/Policy Controller: vi phạm nằm trong status constraint
kubectl get <constraint-kind> <name> -o jsonpath='{.status.violations}'

# Log controller Gatekeeper
kubectl logs -n gatekeeper-system deploy/gatekeeper-controller-manager

Với Gatekeeper, status.violations (do audit loop ghi, file 6) thường đủ để biết object nào vi phạm gì mà không cần đào log.

4. Metric admission của API server

Để phát hiện webhook chậm/chối nhiều trước khi thành outage, theo dõi các metric API server (qua control plane metrics của GKE, bật trong Cloud Monitoring):

MetricÝ nghĩa
apiserver_admission_webhook_admission_duration_secondsĐộ trễ mỗi webhook — phát hiện webhook chậm
apiserver_admission_webhook_rejection_countSố request bị webhook từ chối
apiserver_admission_controller_admission_duration_secondsĐộ trễ built-in plugin
pod_security_evaluations_totalSố lần PSA đánh giá (theo mode/level)

Dựng alert trên admission_duration_seconds p99 để được báo khi một webhook bắt đầu chậm — đây là tín hiệu sớm của failure mode file 3, trước khi nó leo thang thành nghẽn control plane.

Quy trình debug điển hình

Khi nhận báo "không apply được X", đi theo thứ tự:

  1. Đọc message lỗi kubectl — thường nói thẳng nguồn (admission webhook "..." denied, exceeded quota, violates PodSecurity "restricted"). Đây là 80% trường hợp.
  2. Nếu là quota/PSA/VAP — message đã đủ; sửa object hoặc quota.
  3. Nếu là webhook và message mơ hồkubectl get validatingwebhookconfigurations tìm webhook khớp, đọc log webhook server.
  4. Nếu "apply thành công nhưng Pod không chạy" — gần như chắc là PSA enforce mà thiếu warn (file 5); kiểm event của ReplicaSet/Job, không phải Deployment.
  5. Nếu "mọi apply treo/timeout" — webhook Fail + service chết (file 3); dùng break-glass xóa WebhookConfiguration.
  6. Nếu là cấu hình cluster bị chặn (gcloud/Terraform) — đó là Org Policy tầng GCP, không phải Kubernetes admission; kiểm gcloud org-policies và Policy Troubleshooter.

Production architecture patterns

Policy Troubleshooter cho Org Policy

Khi gcloud container clusters create bị chặn bởi Org Policy mà không rõ constraint nào, dùng Policy Troubleshootergcloud org-policies để tìm constraint đang enforce và phạm vi (org/folder/project) nó đến từ đâu. Vì Org Policy kế thừa theo phân cấp, constraint có thể đến từ một folder cha — không hiển nhiên khi nhìn ở mức project.

Dry-run cho cả hai lớp

Cả hai lớp guardrail đều có chế độ "thử trước khi enforce": Kubernetes có --dry-run=server và mode audit/dryrun (PSA, Gatekeeper, VAP); Org Policy có dry-run mode cho phép áp constraint ở chế độ chỉ-ghi-log trước khi enforce thật. Luôn dùng dry-run ở cả hai lớp trước khi bật enforcement trên môi trường thật.

Tập trung quan sát admission vào một dashboard

Gom các tín hiệu admission vào một dashboard: tỷ lệ webhook rejection, p99 admission duration, số vi phạm PSA/Gatekeeper theo namespace, và các Forbidden trong audit log. Dashboard này biến admission từ "hộp đen chỉ lộ ra khi có người than" thành thứ quan sát được liên tục.

Common mistakes / anti-patterns

Nhầm lẫn hai lớp guardrail

Cố dùng Kubernetes admission để chặn "cluster phải private" — không làm được, vì cluster config không phải Kubernetes object. Và cố dùng Org Policy để chặn "Pod không privileged" — cũng không, vì Pod không phải GCP resource. Đặt đúng policy ở đúng tầng.

Dùng --dry-run=client để kiểm policy

Client dry-run không chạy admission — nó chỉ kiểm cú pháp local. Phải --dry-run=server mới thấy phản ứng của webhook/PSA/VAP.

Không bật control plane metrics

Không có metric admission, webhook chậm chỉ lộ ra khi đã thành outage. Bật control plane metrics trong Cloud Monitoring và alert trên admission duration.

Quên Org Policy kế thừa từ folder cha

Debug ở mức project mà constraint đến từ folder/org cha — không thấy. Luôn kiểm cả phân cấp bằng Policy Troubleshooter.

GCP-native implementation guidance

Tạo và enforce một custom org policy constraint cho GKE:

bash
# Tạo custom constraint (từ file YAML ở trên)
gcloud org-policies set-custom-constraint constraint.yaml

# Enforce qua một policy gắn vào project/folder/org
gcloud org-policies set-policy policy.yaml

# Liệt kê policy đang áp lên một resource
gcloud org-policies list --project=PROJECT_ID

Bật control plane metrics để quan sát admission (nếu chưa bật):

bash
gcloud container clusters update CLUSTER_NAME \
  --monitoring=SYSTEM,API_SERVER,SCHEDULER,CONTROLLER_MANAGER \
  --location=LOCATION

Truy vấn nhanh các từ chối admission gần đây:

bash
gcloud logging read \
  'resource.type="k8s_cluster" AND protoPayload.response.reason="Forbidden"' \
  --limit=20 --format=json

Official references