作者:Viswajith Venugopal
Kubernetes 中的網路策略用於指定 Pod 組之間以及其與外部網路端點之間的通訊,它就像是 Kubernetes 的防火牆。與大多數 Kubernetes 物件一樣,網路策略非常靈活,功能也很強大。如果瞭解應用程式中服務的確切通訊模式,我們就可以通過網路策略將通訊限制在我們想要範圍之內。
Ingress 與 Egress
網路策略可用於指定 Pod 的入口(Ingress)流量和出口(Egress)流量,其規則如下:
- 如果允許叢集外部網路端點到 Pod 的通訊,那麼該端點可以訪問 Pod。
- 如果允許 Pod 到叢集外部網路端點的通訊,那麼 Pod 也可以訪問該端點。
- 如果 Pod(A)到 Pod(B)的流量被允許,那麼流量可以通過 A 的出口以及 B 的入口。注意這是單向的,流量從 B 到 A,那麼只能從 B 的出口到 A 的入口。
設定 Ingress
在完成入口網路策略的設定併成功執行後,我們再設定出口網路策略。這樣做的原因有兩個:首先,一次執行兩項操作比較困難,而且我們很難知道是由於入口還是出口配置導致的網路連線失敗;其次,出口網路策略通常更難以實施。限制出口可能會引發各種錯誤,從而影響應用程式執行。
雖然確定網路端點到 Pod 的通訊非常簡單,但要確定 Pod 到網路端點的連線方式會比較複雜。之所以出現這一問題,這是因為:
- 作為常規功能的一部分,Deployment 通常會查詢一堆外部服務,根據它們在訪問這些服務時處理超時的方式,功能可能會受到一些細微而又難以觀察的影響。
- Deployment 需要能夠與 DNS 伺服器通訊,以便和其他任何伺服器通訊,除非這些伺服器直接通過 IP 與服務聯絡。
對 Pod Egress 進行隔離
每個網路策略都有一個 podSelector 欄位,該欄位會選擇一組(0 個或多個)Pod。當網路策略選擇了一個 Pod 時,就稱該網路策略適用於該 Pod。
此外,每個網路策略都可以根據 policyTypes 欄位的值應用於入口和出口。如果 YAML 中未指定此欄位,它的值會基於策略中的入口、出口規則預設設定,但預設設定並不靠譜,我們最好進行明確的設定。
一般情況下,如果沒有任何出口網路策略適用於 Pod,那麼它就是非隔離的出口。這裡要注意,隔離是針對入口和出口獨立評估的。一個 Pod 的入口和出口可以都隔離,也可以都不隔離,甚至進行單個隔離。如果一個 Pod 的出口沒有進行隔離,那麼所有的流量都可以從這個 Pod 中出來。
當一個出口網路策略適用於某個 Pod 時,該 Pod 的出口就會被隔離。對於隔離的 Pod,只有在網路策略允許的情況下,才允許網路出口,也就是網路策略白名單。
設定出口網路策略的第一步是對 Pod 進行出口隔離。我們最好先應用“預設拒絕所有”的策略,將所有 Pod 的出口都隔離了。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all-egress
spec:
podSelector: {}
egress:
- to:
ports:
- protocol: TCP
port: 53
- protocol: UDP
port: 53
policyTypes:
- Egress
不過要注意,預設情況下這個策略允許流量連線到任意 IP 上的 53 埠,以方便 DNS 查詢。因此,儘管它可以防止出口出現問題,但不能防止資料洩露,因為攻擊者可以將資料傳送到 53 埠。
如果想知道 Pod 使用的是哪些 DNS 伺服器,我們可以通過縮小訪問範圍進行確定。例如,要將 DNS 範圍縮小到僅提供 kube-dns 服務,可以執行以下操作:
標記 kube-system 名稱空間:
kubectl label namespace kube-system networking/namespace=kube-system
應用以下網路策略:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all-egress
spec:
podSelector: {}
egress:
- to:
- namespaceSelector:
matchLabels:
networking/namespace: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: TCP
port: 53
- protocol: UDP
port: 53
policyTypes:
- Egress
重要提示:由於網路策略是帶名稱空間資源,因此每個名稱空間都需要建立此策略。我們可以執行以下命令:
kubectl -n <namespace> create -f <filename>
另外,最好不要將其應用於 kube-system 名稱空間,因為它可能會影響叢集功能。
明確 Pod 到網際網路的出口
在每個名稱空間中都採用了 default-deny-all-egress (預設拒絕所有出口)策略後,Pod 將無法連線到網際網路,但是在大多數的應用程式中,會有一些 Pod 需要連線到網際網路。對此,有一種設定方法:為允許連線網際網路的 Pod 指定標籤,並建立一個針對這些標籤的網路策略。例如,以下網路策略允許具有 networking/allow-internet-egress=true 標籤的 Pod 傳送流量至所有網路端點(包括叢集外部的端點)。另外,還是要注意,我們必須為每個名稱空間建立該策略:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: internet-egress
spec:
podSelector:
matchLabels:
networking/allow-internet-egress: "true"
egress:
- {}
policyTypes:
- Egress
這個策略比預設設定具有更高的安全性。不過對於更嚴格的策略集,理想情況下最好指定更細粒度的 CIDR 塊,並明確列出允許的埠和協議。
明確 Pod 間的點對點通訊
如果將 Pod 隔離,然後明確 Pod 到 Pod 之間的通訊之後,在應用正常工作時,我們會發現,在使用 default-deny-all-egress 策略後,所有的通訊都被限制了。對此,我們在入口方向上將每個連線放入白名單後,還需要把出口方向上的連線也放入白名單。無論是遵循上面的建議,允許所有名稱空間內部通訊,還是將各個 Pod 之間的連線列入白名單,或者採用自定義的方法,對於構建的每個入口策略,都需要制定相應的出口策略。
補充出口網路策略
對於從一組 Pod 到另一組 Pod 之間進行通訊的入口策略,構建對應的出口策略非常簡單。首先,將 policyTypes 欄位更改為僅包含 Egress 的陣列,將 spec.podSelector 放在 spec.egress.to.podSelector 中,刪除 ingress.from,並將從中提取的 ingress.from.podSelector 放到新的出口策略 spec.podSelector 中。這樣就完成了名稱空間內的一個入口策略。
至於跨名稱空間的策略,如果已經用 network/namespace: 用標籤標記了每個名稱空間:
kubectl label namespace <name> networking/namespace=<name>
那麼,我們需要在入口策略中選擇 ingress.from.namespaceselector 的名稱空間作為出口策略的 metadata.namespace,並將在入口策略的 metadata.namespace 中指定的名稱空間,放到出口策略的 egress.to.namespaceSelector 欄位中。
這是一個例子:
最後,上述建議僅僅提供了一個學習網路策略的起點,實際上它會複雜得多。如果想更詳細地對它們進行了解,最好多多檢視 Kubernetes 教程以及更多的網路策略配置。