Nhiều người dựng namespace và nghĩ đã có ranh giới. Bài này đo xem ranh giới đó có tồn tại không, và cách tạo ra nó.
Mặc định: mọi pod gọi được mọi pod
Pod pr ở namespace default:
gọi a.default -> 200
gọi web.kh -> 200 (namespace KHÁC)
Không có ranh giới mạng nào giữa namespace.
Namespace chia không gian tên, không chia không gian mạng. Một pod bị chiếm quyền trong namespace dev gọi thẳng được cơ sở dữ liệu trong namespace prod, chỉ cần biết tên dịch vụ — và tên đó đoán được.
Đây là mặc định có chủ ý (mọi thứ chạy được ngay), nhưng nó khiến NetworkPolicy là thứ nên thêm sớm chứ không phải để sau.
Luật 1: chặn hết
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: chan-het, namespace: kh}
spec:
podSelector: {}
policyTypes: [Ingress]
gọi a.default -> 200 namespace khác không ảnh hưởng
gọi web.kh -> 000 chặn sạch
podSelector: {} chọn mọi pod trong namespace. Không có mục ingress nào nghĩa là không cho phép gì cả.
Đây là chính sách nền đáng có ở mọi namespace: chặn hết trước, rồi mở dần. Nó áp cho namespace kh mà không đụng gì tới default.
Luật 2: mở cho đúng một nhãn
spec:
podSelector: {matchLabels: {app: web}}
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: default}}
podSelector: {matchLabels: {duoc: "co"}}
ports: [{protocol: TCP, port: 80}]
pod CHƯA có nhãn duoc=co -> 000
sau khi gán nhãn duoc=co -> 200
Gán một nhãn là đủ để mở đường — và gỡ nhãn là đủ để đóng lại. Không cần khởi động lại gì.
Một chi tiết cú pháp quan trọng: namespaceSelector và podSelector nằm trong cùng một mục của danh sách from nghĩa là VÀ — namespace phải khớp và pod phải khớp. Tách thành hai mục riêng thì thành HOẶC, và ý nghĩa khác hẳn:
# VÀ — pod có nhãn duoc=co, trong namespace default
- from:
- namespaceSelector: {...}
podSelector: {...}
# HOẶC — mọi pod trong namespace default, HOẶC mọi pod có nhãn duoc=co ở bất kỳ đâu
- from:
- namespaceSelector: {...}
- podSelector: {...}
Một dấu gạch đầu dòng. Đây là lỗi hay gặp nhất khi viết NetworkPolicy, và nó mở rộng quyền chứ không thu hẹp — nên nó không bao giờ làm gì hỏng và bạn không phát hiện ra.
Luật 3: giới hạn cổng
ports: [{protocol: TCP, port: 80}]
gọi thẳng podIP:80 -> 200
gọi thẳng podIP:8080 -> 000
Chú ý: tôi gọi thẳng podIP, không qua Service. Chính sách vẫn áp.
Đó là vì NetworkPolicy được thực thi ở tầng mạng của node (bởi CNI), không ở tầng Service. Nó chặn gói tin thật, nên không có đường vòng nào.
Hệ quả ngược lại cũng đúng: NetworkPolicy không biết gì về Service. Bạn khai cổng của pod (targetPort), không phải cổng của Service.
Ba điều dễ hiểu sai
Chính sách cộng dồn, không ghi đè. Hai chính sách cùng chọn một pod thì pod đó được phép làm hợp của hai tập luật. Không có thứ tự ưu tiên, không có "từ chối" — NetworkPolicy chỉ có cho phép.
Nghĩa là bạn không thể viết một chính sách để chặn thứ mà chính sách khác đã cho phép. Muốn chặn thì phải sửa hoặc xoá chính sách kia.
Có ít nhất một chính sách chọn tới pod → mọi thứ không được nêu bị chặn. Không chính sách nào chọn tới → mở hoàn toàn.
Đây là chỗ hay gây bất ngờ: thêm một chính sách rất hẹp cho một pod sẽ chặn mọi thứ khác của pod đó, kể cả những luồng đang chạy.
policyTypes quyết định chiều. Chặn Ingress không chặn Egress — pod vẫn gọi ra ngoài bình thường. Muốn chặn cả hai thì phải khai cả hai.
Và chặn Egress khó hơn nhiều vì phải nhớ mở cho DNS:
egress:
- to: [{namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: kube-system}}}]
ports: [{protocol: UDP, port: 53}, {protocol: TCP, port: 53}]
Quên dòng này là pod không phân giải được tên nào, và triệu chứng trông giống hệt sự cố DNS chứ không giống sự cố chính sách.
CNI phải hỗ trợ
NetworkPolicy là một object của Kubernetes, nhưng việc thực thi là của CNI. Với plugin không hỗ trợ, bạn tạo chính sách và nó không có tác dụng gì — không lỗi, không cảnh báo.
Đây là kiểu hỏng im lặng nguy hiểm: bạn nghĩ đã chặn, thực ra chưa.
Kiểm bằng cách thử thật: tạo chính sách chặn hết rồi gọi thử. Nếu vẫn thông, CNI của bạn không thực thi nó.
Toàn bộ phép đo trong bài này chạy trên kindnet — CNI mặc định của kind — và nó có thực thi. Tôi kiểm lại ba lần để chắc chắn:
có chính sách, pod chưa có nhãn -> 000
gán nhãn duoc=co -> 200
xoá hết chính sách, gỡ nhãn -> 200
Điều này đáng nói vì kindnet từng không hỗ trợ NetworkPolicy, và rất nhiều tài liệu vẫn viết như vậy. Bản trong kindest/node:v1.34.3 thì có. Đừng tin tài liệu — hãy chạy phép thử ở cuối bài trên chính cụm của bạn.
Bộ chính sách nền đáng có
Cho mỗi namespace:
# 1. chặn hết vào
kind: NetworkPolicy
metadata: {name: 00-chan-vao}
spec: {podSelector: {}, policyTypes: [Ingress]}
---
# 2. cho phép trong cùng namespace gọi nhau
kind: NetworkPolicy
metadata: {name: 01-cung-namespace}
spec:
podSelector: {}
policyTypes: [Ingress]
ingress: [{from: [{podSelector: {}}]}]
Hai chính sách này cho bạn ranh giới namespace thật, với chi phí vận hành gần như bằng không. Mọi luồng đi qua namespace sau đó phải được khai rõ — và đó chính là điều bạn muốn.
Thử ba mươi giây
Kiểm CNI của bạn có thực thi NetworkPolicy không:
kubectl create ns thu
kubectl -n thu create deployment web --image=nginx:alpine
kubectl -n thu expose deployment web --port=80
kubectl -n thu wait --for=condition=available deployment/web
kubectl run t --image=curlimages/curl --restart=Never --rm -it -- \
curl -s -m 4 -o /dev/null -w "truoc: %{http_code}\n" http://web.thu/
kubectl -n thu apply -f - <<'Y'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: {name: chan}
spec: {podSelector: {}, policyTypes: [Ingress]}
Y
kubectl run t2 --image=curlimages/curl --restart=Never --rm -it -- \
curl -s -m 4 -o /dev/null -w "sau: %{http_code}\n" http://web.thu/
Nếu cả hai đều 200, CNI của bạn đang bỏ qua NetworkPolicy — và mọi chính sách bạn đã viết chỉ là văn bản.
Phần sau đo CNI: cùng phép thử, kindnet và Calico chênh nhau gần hai lần thông lượng giữa node.