昨天我們看了 Service,他解決了 Pod IP 不固定的問題。透過 NodePort
對外曝光、ClusterIP
提供 Cluster 內穩定入口,再加上 Endpoints 的動態更新,讓服務彼此能可靠通訊,也自帶簡單的負載平衡。
但是現在叢集內所有 Pod 都能互相連線,預設是「完全開放」。在生產環境裡,這樣代表前端 Pod 可以直連資料庫,甚至不同團隊的服務都能互訪,這是一個很大的安全風險。
所以今天要來看看 Network Policy,它能幫我們設定哪些 Pod 可以互通、哪些該被擋下,讓 Cluster 網路變得更安全、可控。
在 Kubernetes 裡,預設所有 Pod 都可以互相通訊,任何 Pod 都能對任何 Pod 發送流量。不管是前端、後端還是資料庫,全部都是開放的。這雖然在小型開發環境很方便,但在生產環境卻是一個大大的隱患。
流量 (Traffic) 分成兩種類型:
我們來看一個簡單的應用架構,如上圖:
Ingress
(Port 80)Egress
→ API Pod Ingress
(Port 5000)Egress
→ Database Pod Ingress
(Port 3306)從流量 (Traffic) 角度來看:
所以在預設情況下,是沒有設定上述的 Ingress 和 Egress 限制的話,其實在 Web 這邊可以直接發送請求到 Database 這邊,顯然這不是我們所樂見的情況。為了防止這種情況的發生,這就是我們設定 Network Policy 的時候。
所以我們這邊以 Database 的角度來看,我們只能允許 API Pod 可以向 Database 發送請求,其他的一律都 Block 掉。如上圖,我們制定了一個 Policy 是「只允許來自 API Pod 並且是 Port 3306 的入口流量」。啟用這個 Policy 之後,我們限制只有 API Pod 能訪問 DB,其餘到 Database Pod 的流量都會被阻擋掉。
對於 Network Policy 要套用在哪個 Pod 上面,或者是允許哪個 Pod 的流量,跟 Deployment、Service 一樣,靠 labels 與 selectors 來綁定。
我們拿上面提到的例子設定的 Policy 來看看,只有帶有 name=api-pod
標籤的 Pod,可以存取 role=db
的 Pod,並且只能用 TCP 3306 port。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-policy
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
name: api-pod
ports:
- protocol: TCP
port: 3306
如果叢集裡有很多個 api-pod
(例如 dev/test/prod namespace 各有一份),單純用 podSelector 會讓所有 API Pod 都能連到 DB。
這時就要搭配 namespaceSelector
,只允許 prod namespace 的 API Pod,但記得 prod namespace 要記得設定 labels 標籤喔!
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-policy
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
name: api-pod
namespaceSelector:
matchLabels:
name: prod
ports:
- protocol: TCP
port: 3306
假設我們現在有一台外部備份伺服器 IP:192.168.5.10
,也要能連進 DB。
這時候就不能用 Pod/Namespace 選擇器,而是改用 ipBlock
來開白名單:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-policy
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
name: api-pod
namespaceSelector:
matchLabels:
name: prod
- ipBlock:
cidr: 192.168.5.10/32
ports:
- protocol: TCP
port: 3306
在 ingress
的 from
區塊裡面,底下其實是一個 list。
podSelector
和 namespaceSelector
,那就代表必須同時滿足 Pod 的標籤 與 Namespace 的標籤,才能被允許通過。以下圖為例:
podSelector
和 namespaceSelector
在同一組內,代表 必須同時符合 Pod 與 Namespace 的條件(AND)。我們繼續沿用前面有一台外部備份伺服器 IP:192.168.5.10
,而我們如果要讓 DB Pod 主動把資料推送到外部備份伺服器(Port 80),就要加上 Egress
:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-policy
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
name: api-pod
namespaceSelector:
matchLabels:
name: prod
- ipBlock:
cidr: 192.168.5.10/32
ports:
- protocol: TCP
port: 3306
egress:
- to:
- ipBlock:
cidr: 192.168.5.10/32
ports:
- protocol: TCP
port: 80
建立一個 Network Policy
,讓 Internal 應用程式只能對 payroll-service:8080
與 db-service:3306
發送 Egress 流量。使用下方給定的規格。
# np-def.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: internal-policy
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
name: payroll
- podSelector:
matchLabels:
name: mysql
ports:
- protocol: TCP
port: 8080
- protocol: TCP
port: 3306
kubectl create -f np-def.yaml
今天我們實作了 Network Policy,看到它如何幫助我們在叢集內建立「網路防火牆」。從最基礎的 ingress/egress 控制,到更進階的 namespaceSelector、ipBlock,都能靈活組合,讓 Pod 之間的通訊更安全、更可控。讓我們能真正落實「最小權限原則」,避免服務之間互相亂串。
回顧這幾天的學習路線:從 Pod、Deployment、Service 到今天的 Network Policy,我們一步步把應用「跑起來」並且「安全起來」。不過,Pod 除了網路需要規範外,它該跑在哪台 Node 上,其實也很關鍵。GPU Pod 要落在有 GPU 的 Node,資料庫 Pod 不能隨便被排到低效能的 Node。明天我們就來看 Taints & Tolerations、Node Selector、Node Affinity,如何決定 Pod 的最佳落點。