RBAC quyết định ai được tạo pod. Pod Security Standards quyết định pod đó được phép làm gì với node. Hai lớp khác nhau, và lớp thứ hai bật bằng một dòng nhãn.
Bật bằng nhãn namespace
kubectl create ns pss-restricted
kubectl label ns pss-restricted \
pod-security.kubernetes.io/enforce=restricted
Không cài gì thêm. Từ Kubernetes 1.25, bộ kiểm này nằm sẵn trong API server, thay cho PodSecurityPolicy đã bị gỡ.
Ba mức: privileged (không chặn gì), baseline (chặn leo thang rõ ràng), restricted (siết chặt).
Đo thật: cùng một pod, ba namespace
Tôi tạo ba namespace gắn ba mức, rồi thả cùng một bộ pod vào.
| pod | privileged | baseline | restricted |
|---|---|---|---|
| nginx bình thường | tạo được | tạo được | từ chối |
privileged: true |
tạo được | từ chối | từ chối |
hostNetwork: true |
tạo được | từ chối | từ chối |
Ba dòng sau đúng như tên gọi. Dòng đầu mới là điều đáng dừng lại.
Pod nginx bình thường bị từ chối
Đây là toàn văn lời từ chối:
violates PodSecurity "restricted:latest":
allowPrivilegeEscalation != false
(container "c" must set securityContext.allowPrivilegeEscalation=false)
unrestricted capabilities
(container "c" must set securityContext.capabilities.drop=["ALL"])
runAsNonRoot != true
(pod or container "c" must set securityContext.runAsNonRoot=true)
seccompProfile
(pod or container "c" must set securityContext.seccompProfile.type
to "RuntimeDefault" or "Localhost")
Pod này không làm gì đặc biệt cả. Nó chỉ chạy nginx.
Điểm cần hiểu: restricted không hỏi "pod này có nguy hiểm không" mà hỏi "pod này có khai báo là nó không nguy hiểm không". Không khai là từ chối.
Cách đó nghe khó chịu nhưng đúng. Kubernetes không biết container bên trong làm gì; nó chỉ đọc được phần khai báo. Mặc định của securityContext là "chạy root, giữ đủ capability, cho leo thang quyền" — nên im lặng nghĩa là mở, và restricted không chấp nhận im lặng.
Pod hợp lệ trông thế nào
apiVersion: v1
kind: Pod
metadata: {name: pok}
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile: {type: RuntimeDefault}
containers:
- name: c
image: nginx:alpine
securityContext:
allowPrivilegeEscalation: false
capabilities: {drop: ["ALL"]}
Kết quả: pod/pok created trong pss-restricted.
Năm dòng. Không dòng nào khó, nhưng dòng nào cũng phải có — và runAsUser: 1000 không thừa: runAsNonRoot: true chỉ kiểm tra image không chạy root, còn nếu image không khai USER thì pod sẽ dừng ở CreateContainerConfigError sau khi đã tạo xong. Bộ kiểm cho qua, kubelet mới chặn — hai chỗ khác nhau.
Với nginx thật, image chính thức chạy root nên phải dùng bản nginxinc/nginx-unprivileged hoặc tự dựng. Đó mới là chi phí thật của restricted: không phải năm dòng YAML, mà là việc phải kiểm lại từng image trong hệ thống.
Ba nhãn, ba hành vi
| Nhãn | Làm gì |
|---|---|
enforce |
Từ chối tạo pod |
warn |
Cảnh báo cho người gõ lệnh, pod vẫn được tạo |
audit |
Ghi vào audit log |
Dùng chung được, và đó là cách siết chặt an toàn:
kubectl label ns san-xuat \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restricted
Namespace này đang chặn ở mức baseline, nhưng mọi pod chưa đạt restricted sẽ hiện cảnh báo và vào audit log. Bạn đo được siết chặt sẽ gây vỡ bao nhiêu, mà chưa vỡ gì cả.
Đặt enforce=restricted thẳng vào một namespace đang chạy là cách nhanh nhất để dừng mọi lần triển khai tiếp theo — và nó không dừng pod đang chạy, nên bạn chỉ biết khi lần deploy sau thất bại.
enforce chỉ xét lúc tạo pod
Gắn nhãn enforce không đụng tới pod đang chạy. Pod vi phạm vẫn sống cho tới khi có ai xoá nó.
Hệ quả thực tế: một Deployment vi phạm vẫn phục vụ bình thường, nhưng nếu node chết hoặc bạn scale lên, ReplicaSet không tạo được pod thay thế. Sự cố nằm im chờ đúng lúc tệ nhất mới nổ.
Nên sau khi gắn nhãn, đừng dừng ở "không có lỗi". Đá thử một pod:
kubectl delete pod -n san-xuat <mot-pod> && \
kubectl get pods -n san-xuat -w
Thử ba mươi giây
Xem namespace nào đang không có lớp bảo vệ nào:
kubectl get ns -o json | python3 -c '
import sys,json
for n in json.load(sys.stdin)["items"]:
l=n["metadata"].get("labels",{})
m=l.get("pod-security.kubernetes.io/enforce","(khong co)")
print(f"{m:>12} " + n["metadata"]["name"])
'
Rồi thử warn trước khi thử enforce — không mất gì mà biết ngay có bao nhiêu thứ sẽ vỡ:
kubectl label --overwrite ns <ten> \
pod-security.kubernetes.io/warn=restricted
# tao lai chinh nhung pod dang chay, nhung chi chay thu
kubectl get pods -n <ten> -o yaml | kubectl create -f - --dry-run=server
Mỗi dòng Warning: would violate PodSecurity là một pod sẽ không tạo lại được sau khi siết. Lỗi AlreadyExists ở cuối là bình thường — pod đang chạy thật, cảnh báo đã in ra trước đó rồi.
Lệnh đầu tôi viết là kubectl delete pod --all --dry-run=server, và nó không in cảnh báo nào. Suýt kết luận là namespace sạch. Lý do rõ ràng khi nghĩ lại: bộ kiểm chỉ chạy lúc tạo pod, mà xoá thì không tạo gì. Muốn thấy cảnh báo thì phải làm động tác tạo.
Phần sau đo kubectl từ phía người dùng: cache ở đâu, và vì sao lệnh đầu tiên sau khi đổi cụm luôn chậm hơn hẳn.