# 🚦 NetworkPolicy và luồng traffic giữa các Pod

## Mục tiêu học tập

- Giải thích được mô hình mạng phẳng: mỗi Pod có IP riêng và mặc định gọi được tới mọi Pod khác.
- Đọc hiểu manifest NetworkPolicy: `podSelector`, `policyTypes`, `ingress`/`egress`, `from`/`to`, `ports`.
- Phân biệt đúng AND (cùng một phần tử trong list) và OR (nhiều phần tử trong list).
- Viết được bộ policy default-deny + allow theo label mà không tự khóa mình ra ngoài.
- Nhận ra cạm bẫy lớn nhất: CNI không hỗ trợ NetworkPolicy thì policy tạo ra mà không có tác dụng gì.

## Ôn tập nhanh

Ở bài Service và Ingress, bạn đã dùng `selector` trên label để Service tìm ra endpoint. NetworkPolicy dùng đúng cơ chế label đó, nhưng mục đích ngược lại: không phải để mở đường mà để giới hạn đường. Nếu bạn còn nhớ `kubectl get pods --show-labels`, bài này sẽ rất nhanh.

## Lý thuyết

### 1. Mạng phẳng: mặc định ai cũng nói chuyện được với ai

Trong cluster, mỗi Pod có một IP riêng và mọi Pod định tuyến được tới Pod khác mà không qua NAT. Không có tường lửa mặc định ở tầng Pod. Cluster vừa dựng xong thì Pod ở namespace `frontend` gọi thẳng IP của Pod trong namespace `db` được, và ngược lại.

NetworkPolicy là resource **namespaced** dùng để thay đổi mặc định đó. Nó chọn Pod mục tiêu bằng label, rồi khai báo danh sách traffic được phép vào (`ingress`) và ra (`egress`).

### 2. Ba nguyên tắc không được quên

- Không có policy nào chọn Pod: mọi chiều đều **allow all**.
- Có ít nhất một policy chọn Pod và `policyTypes` gồm `Ingress`: Pod bị **isolate** chiều đó, chỉ traffic khớp rule mới vào được.
- Nhiều policy cùng chọn một Pod: **cộng dồn (union)**, không có thứ tự ưu tiên, không có rule deny.

Nói cách khác, bạn không viết "chặn A". Bạn viết "mở B" và phần còn lại tự bị chặn một khi đã có policy. Đây là lý do NetworkPolicy được gọi là **allow-list**.

```mermaid
flowchart TD
    A["📦 Pod nhận traffic"] --> B{"Có policy nào chọn Pod này<br/>với policyTypes Ingress?"}
    B -->|Không| C["✅ Allow all"]
    B -->|Có| D["🔒 Isolated"]
    D --> E["Chỉ hợp các rule được liệt kê<br/>không có deny, không có thứ tự"]
```

`policyTypes` nếu bỏ trống sẽ được suy ra: có `ingress:` thì thêm `Ingress`, có `egress:` thì thêm `Egress`. Cẩn thận: muốn chặn sạch egress thì phải khai `policyTypes: [Egress]` với `egress: []`. Nếu chỉ khai `[Ingress]`, chiều ra vẫn mở toang.

### 3. AND và OR trong `from` / `to`

Đây là chỗ người mới sai nhiều nhất. Quy tắc: **cùng một phần tử list = AND, khác phần tử = OR**.

```yaml
  ingress:
  - from:
    - namespaceSelector:            # AND: phải ở namespace monitoring
        matchLabels:
          kubernetes.io/metadata.name: monitoring
      podSelector:                  # VÀ có label app=prometheus
        matchLabels:
          app: prometheus
```

```yaml
  ingress:
  - from:
    - podSelector: {matchLabels: {app: frontend}}   # OR: frontend cùng namespace
    - namespaceSelector:                            # HOẶC bất kỳ Pod nào
        matchLabels:                                # trong namespace monitoring
          kubernetes.io/metadata.name: monitoring
```

Label `kubernetes.io/metadata.name` được gắn tự động bằng tên namespace (từ Kubernetes 1.21), nên không cần tự label namespace nữa.

### 4. Cạm bẫy số một: CNI

NetworkPolicy chỉ là ý định khai báo. Việc thực thi nằm ở CNI plugin. Calico, Cilium, Antrea làm được. Flannel bản mặc định **không** làm được: `kubectl apply` vẫn báo thành công, `kubectl get netpol` vẫn liệt kê policy, nhưng traffic không hề bị chặn. Luôn kiểm tra CNI trước khi tin vào policy của bạn.

> [!WARNING]
> Trước khi kết luận "policy sai", hãy xác nhận CNI của cluster có hỗ trợ NetworkPolicy. Nếu không, mọi thứ bạn viết chỉ là tài liệu trang trí.

## Ví dụ có hướng dẫn

### Ví dụ 1: default-deny ingress cho cả namespace

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

`podSelector: {}` chọn **tất cả** Pod trong namespace `shop`. Không có rule `ingress` nào nghĩa là không ai vào được. Đây là policy đầu tiên bạn nên apply ở mọi namespace mới.

### Ví dụ 2: mở đúng một đường

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

Đọc theo thứ tự: Pod `app=api` nhận traffic từ Pod `app=frontend` cùng namespace, trên port 8080 của container. Vì chỉ có một rule, mọi port khác của `api` cũng bị chặn. Lưu ý `port` ở đây là **container port**, không phải port của Service.

### Ví dụ 3: egress và cái bẫy DNS

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

Rule thứ hai là DNS. Bỏ nó đi thì `api` vẫn "nói chuyện" được với IP của `db`, nhưng không resolve được `db.shop.svc.cluster.local`, và mọi lời gọi bằng hostname sẽ chết với lỗi trông rất giống lỗi policy.

## Thực hành có hướng dẫn

### Bài 1: Đọc policy

Với policy `allow-frontend-to-api` ở trên, Pod `app=api` còn nhận được traffic từ đâu nữa không? Trả lời trước khi mở đáp án.

<details><summary>Xem đáp án</summary>

Không còn nguồn nào khác. Pod `api` đã bị isolate chiều ingress, và chỉ một rule duy nhất được khai. Traffic từ Ingress controller, từ Pod test, hay từ chính bạn gọi thẳng IP Pod đều bị chặn, trừ khi nguồn đó có label `app=frontend` trong namespace `shop`.

</details>

### Bài 2: Viết policy cho namespace `shop`

Yêu cầu: `frontend` chỉ nhận traffic từ Ingress controller ở namespace `ingress-nginx` (label `app.kubernetes.io/name: ingress-nginx`), port 80. `db` chỉ nhận từ `api`, port 5432.

```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-ingress
  namespace: shop
spec:
  podSelector:
    matchLabels:
      app: frontend
  policyTypes: [Ingress]
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: ingress-nginx
      podSelector:
        matchLabels:
          app.kubernetes.io/name: ingress-nginx
    ports:
    - protocol: TCP
      port: 80
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-ingress
  namespace: shop
spec:
  podSelector:
    matchLabels:
      app: db
  policyTypes: [Ingress]
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: api
    ports:
    - protocol: TCP
      port: 5432
```

Hai policy độc lập, cộng dồn vào nhau. Không cần policy nào "chặn" `frontend` gọi `db`.

### Bài 3: Debug

Bạn apply default-deny egress cho namespace `shop`, sau đó thêm rule cho phép `api` gọi `db:5432`. `kubectl exec` vào `api` chạy `nc -zv db 5432` thì báo `bad address`. Nguyên nhân?

<details><summary>Xem hướng dẫn</summary>

`bad address` là lỗi resolve DNS, không phải lỗi kết nối. Egress đã bị isolate và bạn chưa allow port 53. Thêm rule DNS như Ví dụ 3, hoặc test nhanh bằng IP của Pod `db` (`kubectl get pod -l app=db -o wide`) để xác nhận đường 5432 đã thông. Cách kiểm tra tổng quát: chạy một Pod tạm trong namespace để test từ cả hai phía.

```bash
kubectl run nettest --rm -it -n shop --image=nicolaka/netshoot -- bash
# trong Pod: nc -zv api 8080 ; nslookup db
```

</details>

## Góc chuyên gia

- **Thứ tự apply không quan trọng, nhưng thời điểm thì có.** Các policy cộng dồn nên apply cùng lúc là an toàn. Cái không an toàn là apply mỗi default-deny rồi đi uống cà phê. Luôn chuẩn bị sẵn danh sách allow trước.
- **Đừng quên health check và metrics.** Probe từ kubelet và Pod dùng `hostNetwork` không bị NetworkPolicy chi phối, nhưng Prometheus scrape thì bị. Rất nhiều dashboard "mất trắng" sau khi bật default-deny.
- **Service không phải lá chắn.** Policy kiểm tra ở tầng Pod sau khi DNAT, nên gọi qua ClusterIP hay gọi thẳng Pod IP đều chịu cùng một luật.
- **Luôn bật default-deny cho cả hai chiều ở namespace mới**, rồi mở dần. Bật ngược lại (mở hết rồi siết sau) gần như chắc chắn bạn sẽ bỏ sót.
- ==Policy không có tác dụng thì kiểm tra CNI trước, kiểm tra YAML sau.==

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

Bốn điều mang về: mạng Pod mặc định phẳng và mở; NetworkPolicy là allow-list namespaced, cộng dồn, không có deny; isolate chỉ xảy ra khi có policy chọn Pod với `policyTypes` tương ứng; và egress default-deny luôn kéo theo bài toán DNS port 53.

Tự kiểm tra nhanh:

```cloze
Mặc định khi chưa có NetworkPolicy nào, traffic giữa hai Pod trong cluster là {{c1::allow all (mạng phẳng)}}.
NetworkPolicy chọn Pod mục tiêu bằng trường {{c2::podSelector}} trong spec.
Nhiều NetworkPolicy cùng chọn một Pod thì các rule được {{c3::cộng dồn (union)}}, không có thứ tự ưu tiên.
Trong một phần tử của `from`, `namespaceSelector` và `podSelector` kết hợp với nhau theo phép {{c4::AND}}.
Khi bật default-deny egress, phải allow port {{c5::53}} UDP/TCP để Pod còn resolve được DNS.
```

```flashcard
[
  { "front": "Mặc định, khi chưa có NetworkPolicy nào, traffic giữa hai Pod là gì?", "back": "Allow all, vì mạng Pod phẳng và không có tường lửa mặc định." },
  { "front": "NetworkPolicy chọn Pod mục tiêu bằng trường nào?", "back": "spec.podSelector, một label selector trong cùng namespace." },
  { "front": "Điều kiện tiên quyết để NetworkPolicy thực sự có hiệu lực?", "back": "CNI plugin hỗ trợ NetworkPolicy, ví dụ Calico, Cilium, Antrea. Flannel mặc định thì không." },
  { "front": "podSelector: {} nghĩa là gì?", "back": "Chọn tất cả Pod trong namespace đó." },
  { "front": "Khi một Pod bị policy chọn với policyTypes Ingress thì mô hình là gì?", "back": "Allow-list: chỉ traffic khớp rule được vào, phần còn lại bị chặn." },
  { "front": "Nhiều NetworkPolicy cùng chọn một Pod thì áp dụng thế nào?", "back": "Cộng dồn (union), không có thứ tự ưu tiên và không có rule deny." },
  { "front": "Trong from, namespaceSelector và podSelector nằm trong CÙNG một phần tử list nghĩa là gì?", "back": "AND: nguồn phải ở namespace khớp VÀ có label khớp." },
  { "front": "Nhiều phần tử khác nhau trong list from nghĩa là gì?", "back": "OR: khớp bất kỳ phần tử nào là đủ." },
  { "front": "Cạm bẫy lớn nhất khi bật egress default-deny?", "back": "Mất DNS: phải allow port 53 UDP/TCP tới `kube-dns`, nếu không mọi lệnh resolve tên sẽ fail và bạn tưởng policy chặn sai." },
  { "front": "Nếu bỏ trống policyTypes thì Kubernetes hiểu thế nào?", "back": "Mặc định là Ingress; nếu có egress trong spec thì tự thêm Egress." },
  { "front": "ingress trong NetworkPolicy có phải Ingress resource không?", "back": "Không. Đây là hướng traffic đi vào Pod, khác hoàn toàn Ingress resource dùng cho HTTP routing." },
  { "front": "NetworkPolicy có hiệu lực xuyên namespace không?", "back": "Không. Nó là object theo namespace và chỉ chọn Pod trong namespace của nó, nhưng được phép cho phép nguồn từ namespace khác." },
  { "front": "Vì sao nên bắt đầu bằng default-deny rồi mở dần?", "back": "Vì policy là allow-list, mở dần giúp bạn biết chính xác luồng nào thật sự cần và không bỏ sót traffic lạ." },
  { "front": "Lệnh nào giúp xem nhanh các NetworkPolicy đang có trong namespace?", "back": "kubectl get networkpolicy -n <namespace>, sau đó kubectl describe để đọc rule chi tiết." }
]
```

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

Bốn điều cần mang theo sau bài này:

- Mạng Pod mặc định là phẳng và **allow all**. NetworkPolicy là lớp allow-list duy nhất bạn có, và nó chỉ hoạt động khi CNI plugin hỗ trợ (Calico, Cilium, Antrea). Kiểm tra điều này trước khi viết policy, đừng viết xong mới phát hiện policy không có tác dụng.
- Policy chọn Pod bằng `podSelector` trong namespace của chính nó. Một Pod bị chọn với `policyTypes: [Ingress]` thì mọi traffic vào không khớp rule đều bị chặn. Không có rule deny, không có thứ tự ưu tiên, nhiều policy thì cộng dồn.
- Trong `from`/`to`: các phần tử trong list là OR, còn `namespaceSelector` với `podSelector` nằm trong cùng một phần tử là AND. Nhầm chỗ này là nguyên nhân số một khiến policy "chặn nhầm" hoặc "mở quá rộng".
- Khi bật default-deny egress, luôn allow DNS (port 53) trước, rồi mới siết các luồng còn lại. Và luôn kiểm tra từ một Pod test thật, đừng chỉ đọc YAML.

**Bước tiếp theo:** NetworkPolicy trả lời câu hỏi "ai được nói chuyện với ai". Bài sau, RBAC, ServiceAccount và Pod Security trả lời câu hỏi "ai được làm gì trên API server, và Pod được phép chạy với quyền gì". Hai lớp này bổ trợ nhau: NetworkPolicy giới hạn traffic, còn RBAC và Pod Security giới hạn quyền của chính workload khi nó đã kết nối được. Bạn sẽ viết ServiceAccount riêng cho từng workload, gán Role/ClusterRole tối thiểu, và bật Pod Security Admission để chặn container chạy privileged.
