# 🛡️ NetworkPolicy và CNI

NetworkPolicy là dạng bài gần như chắc chắn xuất hiện trong domain Services & Networking (20% điểm CKA). Tin tốt: nó chỉ có vài biến thể, và đề thi luôn hỏi theo cùng một kiểu. Tin xấu: rất nhiều bạn tạo policy xong, `kubectl get netpol` thấy object tồn tại, rồi tưởng đã xong, trong khi traffic vẫn chạy ầm ầm vì CNI đang cài không enforce policy. Bài này mình đi qua đúng thứ tự: hiểu cơ chế, dựng policy từng bước, rồi luyện tay.

## Mục tiêu bài học

- Giải thích được NetworkPolicy chỉ là object khai báo, còn việc chặn traffic là do CNI plugin thực hiện.
- Viết được policy giới hạn traffic theo `podSelector`, `namespaceSelector`, `ipBlock` và `ports`.
- Phân biệt được chiều Ingress và Egress, hiểu `policyTypes` và trạng thái "isolate" của pod.
- Kiểm tra CNI đang cài có hỗ trợ enforce hay không.
- Verify policy bằng `kubectl describe netpol` và bằng một pod test thật.

## Nhắc nhanh bài trước

Bài Ingress cho mình luồng traffic từ ngoài vào: client, Ingress controller, Service, rồi tới pod. NetworkPolicy là lớp hoàn toàn khác: nó kiểm soát traffic **pod tới pod** ngay bên trong cluster, không liên quan tới Ingress controller. Một policy chặn traffic giữa hai pod sẽ không làm hỏng rule Ingress của bạn, nhưng có thể làm chết app vì Ingress controller không gọi được backend nữa.

> [!NOTE]
> NetworkPolicy là object thuộc namespace. Nó không chặn được traffic từ bên ngoài cluster theo kiểu firewall, và không thay thế Security Group hay firewall của hạ tầng.

## Cơ chế hoạt động, hiểu trong 30 giây

Nguyên tắc cốt lõi: **mặc định mọi thứ được phép**. Chỉ khi một pod bị ít nhất một NetworkPolicy chọn ở một chiều (Ingress hoặc Egress) thì pod đó mới bị "isolate" ở chiều đó, và từ lúc đó chỉ những traffic khớp rule mới đi qua được.

```mermaid
flowchart TD
    A["📦 Pod nguồn gửi traffic"] --> B{"Pod đích có bị policy chọn ở chiều Ingress?"}
    B -- "Không" --> C["✅ Cho phép tất cả"]
    B -- "Có" --> D{"Khớp ít nhất một rule ingress?"}
    D -- "Có" --> E["✅ Cho phép"]
    D -- "Không" --> F["❌ Chặn"]
```

Hai chiều độc lập nhau. Chỉ chặn Ingress thì pod vẫn gọi ra ngoài bình thường. Chỉ chặn Egress thì pod vẫn nhận traffic vào bình thường.

Bảng CNI hay gặp, cái này bạn nên nhớ vì đề có thể hỏi gián tiếp:

| CNI plugin | Enforce NetworkPolicy |
| --- | --- |
| Calico | Có |
| Cilium | Có |
| Antrea | Có |
| Weave Net | Có |
| Flannel (chỉ Flannel) | Không |
| Canal (Flannel + Calico policy) | Có |

## Ví dụ mẫu: dựng policy theo từng bước

Bối cảnh: namespace `shop` có ba Deployment, label `app=web`, `app=api`, `app=db` (port 5432).

Yêu cầu kiểu đề thi: "Chặn toàn bộ ingress trong namespace `shop`, sau đó chỉ cho phép pod `app=web` gọi `app=api` trên TCP port 80."

```steps
1. Kiểm tra CNI: `kubectl get pods -n kube-system | grep -Ei 'calico|cilium|antrea|weave'` và trên node chạy `ls /etc/cni/net.d/`
2. Xem label thực tế của pod: `kubectl get pods -n shop --show-labels`
3. Tạo policy default deny ingress cho toàn namespace
4. Tạo policy allow từ web sang api
5. Đọc lại bằng `kubectl describe netpol -n shop` để chắc policy select đúng pod
6. Test bằng traffic thật: một pod `app=web` gọi được api, một pod khác thì không
```

Bước 3, policy nền tảng, không có nó thì policy thứ hai chỉ là "mở thêm" chứ không chặn được gì:

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: shop
spec:
  podSelector: {}
  policyTypes:
  - Ingress
```

`podSelector: {}` nghĩa là chọn **tất cả pod trong namespace `shop`**. Không có `ingress:` rule nào, cộng với `policyTypes: [Ingress]`, kết quả là deny toàn bộ ingress.

Bước 4, policy mở đúng luồng:

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-web-to-api
  namespace: shop
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: web
    ports:
    - protocol: TCP
      port: 80
```

Đọc policy này theo thứ tự: áp dụng cho pod `app=api`, ở chiều Ingress, cho phép nguồn là pod `app=web` **trong cùng namespace** (vì chỉ có `podSelector`, không có `namespaceSelector`), tới port 80.

Bước 5 và 6, verify:

```bash
kubectl describe netpol -n shop allow-web-to-api
# PodSelector: app=api
# Allowing ingress traffic:
#   To Port: 80/TCP
#   From:
#     PodSelector: app=web
# Policy Types: Ingress

# web gọi api: phải thành công
kubectl -n shop exec deploy/web -- curl -s -m 2 -o /dev/null -w '%{http_code}\n' http://api

# pod lạ gọi api: phải timeout
kubectl -n shop run tmpclient --rm -it --restart=Never --image=nicolaka/netshoot -- curl -s -m 2 http://api
```

> [!TIP]
> Trong phòng thi, `kubectl describe netpol` là cách verify nhanh nhất và không tốn thời gian chờ timeout. Dòng `PodSelector` và `Allowing ingress traffic` cho bạn biết ngay policy có select đúng pod hay không.

## Luyện tập có hướng dẫn

Làm theo đúng thứ tự, mỗi bài tự bấm giờ khoảng 5 phút. Đừng mở đáp án trước khi thử.

### Bài 1: Default deny ingress

Tạo namespace `team-a`, hai pod `client` (label `app=client`) và `server` (label `app=server`), image `nginx`. Tạo policy chặn toàn bộ ingress trong namespace. Chứng minh `client` không gọi được `server` nữa.

<details><summary>Gợi ý</summary>

Dùng `kubectl create deployment` hoặc `kubectl run` để có pod nhanh. Nhớ `kubectl expose` hoặc dùng trực tiếp pod IP từ `kubectl get pod -o wide`. Policy cần `podSelector: {}` và `policyTypes: [Ingress]`.

</details>

<details><summary>Đáp án</summary>

```bash
kubectl create ns team-a
kubectl -n team-a create deployment client --image=nginx
kubectl -n team-a create deployment server --image=nginx
kubectl -n team-a label deploy/client app=client --overwrite
kubectl -n team-a label deploy/server app=server --overwrite
```

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-ingress
  namespace: team-a
spec:
  podSelector: {}
  policyTypes:
  - Ingress
```

```bash
kubectl -n team-a get pod -l app=server -o wide
kubectl -n team-a run probe --rm -it --restart=Never --image=nicolaka/netshoot \
  -- curl -s -m 2 http://<server-pod-ip>
# phải timeout
```

</details>

### Bài 2: Mở đúng một luồng

Vẫn namespace `team-a`. Cho phép duy nhất pod `app=client` gọi `app=server` trên TCP port 80. Một pod thứ ba bất kỳ phải bị chặn.

<details><summary>Gợi ý</summary>

Policy thứ hai áp lên `app=server`, `from` dùng `podSelector` với `app: client`. Hai policy cộng lại theo kiểu union: cái deny chặn nền, cái allow mở một lỗ nhỏ.

</details>

<details><summary>Đáp án</summary>

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-client-to-server
  namespace: team-a
spec:
  podSelector:
    matchLabels:
      app: server
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: client
    ports:
    - protocol: TCP
      port: 80
```

```bash
kubectl -n team-a exec deploy/client -- curl -s -m 2 -o /dev/null -w '%{http_code}\n' http://server
kubectl -n team-a run intruder --rm -it --restart=Never --image=nicolaka/netshoot -- curl -s -m 2 http://server
```

</details>

### Bài 3: Egress isolation, cạm bẫy DNS

Namespace `team-b` có pod `worker` (label `app=worker`) và `api` (label `app=api`). Chỉ cho `worker` đi ra ngoài tới DNS và tới `api` port 80. Mọi egress khác bị chặn.

<details><summary>Gợi ý</summary>

Phải set `policyTypes: [Egress]`. Nếu chỉ khai báo `egress:` rule mà quên `policyTypes`, Kubernetes sẽ tự thêm Egress, nhưng nếu muốn deny sạch mà không có rule nào thì bắt buộc phải khai báo tường minh. Đừng quên port 53.

</details>

<details><summary>Đáp án</summary>

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: worker-egress
  namespace: team-b
spec:
  podSelector:
    matchLabels:
      app: worker
  policyTypes:
  - Egress
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: kube-system
      podSelector:
        matchLabels:
          k8s-app: kube-dns
    ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53
  - to:
    - podSelector:
        matchLabels:
          app: api
    ports:
    - protocol: TCP
      port: 80
```

```bash
kubectl -n team-b exec deploy/worker -- nslookup kubernetes.default   # phải resolve
kubectl -n team-b exec deploy/worker -- curl -s -m 2 http://api       # phải OK
kubectl -n team-b exec deploy/worker -- curl -s -m 2 http://1.1.1.1   # phải fail
```

Lưu ý kỹ thuật: `namespaceSelector` và `podSelector` nằm trong **cùng một** phần tử `to` nên kết hợp bằng AND. Mỗi namespace từ 1.21 có sẵn label `kubernetes.io/metadata.name`.

</details>

### Bài 4: Cross-namespace

Cho phép pod label `app=frontend` ở các namespace có label `env=prod` gọi vào `server` trong `team-a` trên port 80.

<details><summary>Gợi ý</summary>

Đây là dạng bài khó nhất và cũng hay gặp. Cần cả `namespaceSelector` và `podSelector` trong cùng một phần tử `from`. Nhớ gắn label cho namespace trước khi test.

</details>

<details><summary>Đáp án</summary>

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-prod
  namespace: team-a
spec:
  podSelector:
    matchLabels:
      app: server
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          env: prod
      podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 80
```

```bash
kubectl create ns frontend-prod
kubectl label ns frontend-prod env=prod
kubectl -n frontend-prod run fe --image=nicolaka/netshoot --labels app=frontend -- sleep 3600
kubectl -n frontend-prod exec fe -- curl -s -m 2 -o /dev/null -w '%{http_code}\n' http://server.team-a
```

</details>

### Tự kiểm tra nhanh

Điền từ còn thiếu, che phần trong ngoặc lại rồi tự trả lời:

- NetworkPolicy chỉ được enforce nếu {{c1::CNI plugin}} đang cài hỗ trợ, ví dụ Calico, Cilium, Antrea. Flannel thuần thì {{c2::không}} enforce được.
- Khi chưa có NetworkPolicy nào chọn pod, toàn bộ traffic ingress và egress đều được {{c3::cho phép}}.
- Một khi pod bị policy chọn ở chiều Ingress, pod đó bị {{c4::isolate}} ở chiều đó và chỉ nhận traffic khớp rule.
- `podSelector: {}` chọn {{c5::tất cả pod}} trong namespace của policy.
- `namespaceSelector` và `podSelector` trong cùng một phần tử `from` kết hợp theo phép {{c6::AND}}, còn nhiều phần tử trong danh sách `from` kết hợp theo phép OR.

## Lỗi thường gặp và troubleshoot

**1. CNI không hỗ trợ policy.** Policy tạo thành công, `describe` đẹp, nhưng traffic vẫn đi. Kiểm tra bằng `ls /etc/cni/net.d/` trên node hoặc `kubectl get ds -n kube-system`. Nếu chỉ thấy Flannel, câu trả lời đúng là policy không có hiệu lực.

**2. Quên `policyTypes` khi muốn deny egress.** Viết `egress: []` mà không khai báo `policyTypes: [Egress]` thì Kubernetes hiểu là policy chỉ áp cho Ingress và không chặn egress gì cả. Luôn khai báo tường minh, đừng để hệ thống tự suy luận.

**3. Egress isolation giết DNS.** Đây là lỗi kinh điển. App báo `no such host` hoặc treo khi resolve. Nguyên nhân: pod bị isolate egress, rule không có port 53. Sửa bằng cách thêm rule tới `kube-system` port 53 UDP và TCP.

**4. Nhầm AND với OR.** Một phần tử trong `from` chứa cả hai selector là AND. Hai phần tử riêng biệt trong `from` là OR. Đề thi rất hay khai thác chỗ này, đọc kỹ "from pods with label X in namespaces with label Y" (AND) so với "from pods with label X, or from namespaces with label Y" (OR).

**5. Quên scope namespace.** `podSelector` một mình nghĩa là pod trong **cùng namespace** với policy, không phải toàn cluster. Muốn cross-namespace phải có `namespaceSelector`.

**6. Label sai một chữ.** Policy select `app=web` nhưng pod đang là `app=Web`. `kubectl describe netpol` hiện `PodSelector: app=web`, đối chiếu với `kubectl get pod --show-labels` là ra ngay.

**7. Policy cũ còn sót lại.** Khi troubleshoot "tại sao pod không gọi được nhau", chạy `kubectl get netpol -A` trước tiên. Trong lab hoặc đề thi nhiều bước, một policy default deny từ task trước có thể đang chặn task hiện tại.

**8. `ipBlock` dùng sai chỗ.** `ipBlock` so khớp địa chỉ IP nguồn, nên nó dành cho CIDR bên ngoài cluster. Với pod trong cluster, hãy dùng `podSelector`, đừng cố viết CIDR của pod.

> [!WARNING]
> Đừng tạo default deny cho cả namespace nếu đề không yêu cầu. Trong đề thi nhiều task dùng chung một namespace, một policy deny-all bạn tự thêm vào có thể làm hỏng các task khác và bạn sẽ không biết vì sao mất điểm.

## Góc chuyên gia

- **Đọc đề trước, viết sau.** Đề CKA luôn có từ khóa quyết định: "allow only", "from namespace", "on port", "deny all ingress". Gạch chân 3 thông tin: nguồn, đích, port. Xong mới mở editor.
- **Không có generator imperative cho NetworkPolicy.** `kubectl create` không sinh được netpol. Cách nhanh nhất là `kubectl apply -f -` với heredoc, hoặc giữ sẵn một file template trong `~/netpol.yaml` rồi sửa. Đừng gõ YAML từ đầu mỗi lần.
- **Luôn set `policyTypes` tường minh.** Ngắn hơn thì không đáng kể, mà tránh được toàn bộ nhóm lỗi suy luận sai.
- **Verify bằng `describe`, không bằng niềm tin.** Ba dòng cần nhìn: `PodSelector`, `Allowing ingress/egress traffic`, `Policy Types`. Nếu `PodSelector` không phải pod bạn muốn, policy vô dụng.
- **Test traffic chỉ khi còn thời gian.** Nếu đồng hồ đang căng, `describe` là đủ để tự tin nộp. Test bằng pod `netshoot` tốn 30 đến 60 giây chờ pull image.
- **Mỗi task NetworkPolicy nên dưới 4 phút.** Nếu bạn đang mất hơn 6 phút, gần như chắc chắn đang nhầm AND/OR hoặc nhầm namespace. Flag lại, làm tiếp, quay lại sau.
- **Nhớ thứ tự ưu tiên trong đầu:** không policy nào chọn pod thì cho phép tất cả. Có policy chọn thì deny mặc định, chỉ mở đúng rule. Nhiều policy cùng chọn một pod thì hợp lại thành union các rule được phép.

## Tóm tắt và bước tiếp theo

Chốt lại 5 dòng để bạn ghi ra giấy nháp:

- NetworkPolicy là khai báo, CNI plugin mới là thứ chặn traffic thật.
- Không có policy chọn pod: mọi traffic được phép. Có policy: mặc định chặn, mở theo rule.
- `podSelector: {}` là tất cả pod trong namespace. `namespaceSelector: {}` là tất cả namespace.
- Cùng một phần tử `from` là AND, nhiều phần tử là OR.
- Egress isolation luôn kéo theo nhu cầu mở port 53 cho DNS.

### Flashcard ôn tập

```flashcard
[
  { "front": "Thành phần nào thực sự enforce NetworkPolicy?", "back": "CNI plugin đang cài (Calico, Cilium, Antrea, Weave), không phải API server." },
  { "front": "Flannel thuần có enforce NetworkPolicy không?", "back": "Không. Cần Canal (Flannel + Calico) hoặc CNI khác có hỗ trợ policy." },
  { "front": "Khi chưa có NetworkPolicy nào chọn pod, traffic được xử lý thế nào?", "back": "Cho phép toàn bộ cả ingress và egress." },
  { "front": "Một pod bị policy chọn ở chiều Ingress thì sao?", "back": "Pod bị isolate ở chiều Ingress, chỉ traffic khớp rule mới vào được." },
  { "front": "policyTypes mặc định khi không khai báo là gì?", "back": "Luôn có Ingress, thêm Egress nếu policy có egress rules." },
  { "front": "podSelector: {} chọn những pod nào?", "back": "Tất cả pod trong namespace chứa policy." },
  { "front": "namespaceSelector: {} chọn gì?", "back": "Tất cả namespace trong cluster." },
  { "front": "namespaceSelector và podSelector trong cùng một phần tử from kết hợp thế nào?", "back": "AND: pod phải nằm trong namespace khớp và có label khớp." },
  { "front": "Nhiều phần tử trong danh sách from kết hợp thế nào?", "back": "OR: khớp bất kỳ phần tử nào là được phép." },
  { "front": "Egress isolation làm DNS hỏng, sửa thế nào?", "back": "Thêm egress rule tới namespace kube-system, pod k8s-app=kube-dns, port 53 UDP và TCP." },
  { "front": "Kiểm tra CNI đang cài bằng cách nào?", "back": "kubectl get pods -n kube-system, và ls /etc/cni/net.d/ trên node." },
  { "front": "Có generator imperative cho NetworkPolicy không?", "back": "Không. Dùng kubectl apply -f với heredoc hoặc file template." },
  { "front": "ipBlock dùng cho trường hợp nào?", "back": "CIDR bên ngoài cluster; pod trong cluster nên dùng podSelector." },
  { "front": "Lệnh nào xem nhanh policy có select đúng pod không?", "back": "kubectl describe netpol, nhìn dòng PodSelector và Allowing ingress traffic." }
]
```

### Bước tiếp theo

Bạn đã có đủ bốn mảnh của module networking: Service, DNS, Ingress, NetworkPolicy. Bài tiếp theo là **Checkpoint: Services và networking**, một đề tổng hợp ngắn yêu cầu bạn expose workload, sửa DNS đang lỗi, dựng Ingress route và viết policy chặn đúng luồng, tất cả trong một lần bấm giờ. Trước khi sang đó, hãy tự làm lại Bài 3 và Bài 4 mà không mở đáp án. Nếu làm được dưới 5 phút mỗi bài, bạn đã sẵn sàng.
