本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。
Kubernetes 1.27:避免在為 NodePort 服務分配埠時發生衝突
在 Kubernetes 中,Service 可以用於為在一組 Pods 上執行的應用程式提供統一的流量端點。客戶端可以使用 Service 提供的虛擬 IP 地址(或稱 **VIP**)進行訪問,Kubernetes 為訪問不同後端 Pod 的流量提供負載均衡,但 ClusterIP 型別的 Service 僅限於為叢集內的節點提供訪問,而來自叢集外部的流量無法被路由。解決這個問題的一種方法是使用 `type: NodePort` 的 Service,它會為叢集中所有節點的特定埠設定一個對映,從而將外部流量重定向到叢集內部。
Kubernetes 如何為 Service 分配節點埠?
當建立 `type: NodePort` 的 Service 時,其對應的埠會透過以下兩種方式之一進行分配:
動態分配:如果 Service 的型別是 `NodePort`,並且你沒有在 Service 的 `spec` 中明確設定 `nodePort` 值,那麼 Kubernetes 控制平面會在建立時自動為其分配一個未使用的埠。
靜態分配:除了上述的動態自動分配外,你還可以明確指定一個在 NodePort 埠範圍配置內的埠。
你手動分配的 `nodePort` 值在整個叢集中必須是唯一的。如果嘗試建立一個 `type: NodePort` 的 Service,並明確指定一個已經被分配的節點埠,將會導致錯誤。
為什麼需要為 NodePort Service 預留埠?
有時,你可能希望讓一個 NodePort Service 執行在眾所周知的埠上,以便叢集內外的其他元件和使用者可以使用它們。
在一些複雜的叢集部署中,Kubernetes 節點和其他伺服器混合在同一網路中,可能需要使用一些預定義的埠進行通訊。特別是,一些基礎元件不能依賴於支援 `type: LoadBalancer` 服務的 VIP,因為該叢集的虛擬 IP 地址對映實現也依賴於這些基礎元件。
現在假設你需要將 Kubernetes 上的 Minio 物件儲存服務暴露給執行在 Kubernetes 叢集外的客戶端,並且約定的埠是 `30009`,我們需要建立一個如下所示的 Service:
apiVersion: v1
kind: Service
metadata:
name: minio
spec:
ports:
- name: api
nodePort: 30009
port: 9000
protocol: TCP
targetPort: 9000
selector:
app: minio
type: NodePort
然而,如前所述,如果 `minio` Service 所需的埠 (30009) 沒有被預留,並且在 `minio` Service 建立之前或同時,另一個 `type: NodePort`(或可能是 `type: LoadBalancer`)的 Service 被建立並動態分配,那麼 TCP 埠 30009 可能會被分配給那個其他的 Service;如果發生這種情況,`minio` Service 的建立將會因為節點埠衝突而失敗。
如何避免 NodePort Service 埠衝突?
Kubernetes 1.24 為 `type: ClusterIP` 的 Service 引入了變更,將叢集 IP 地址的 CIDR 範圍劃分為兩個塊,採用不同的分配策略以減少衝突風險。在 Kubernetes 1.27 中,作為一個 alpha 功能,你可以為 `type: NodePort` 的 Service 採用類似的策略。你可以啟用一個新的特性門控 `ServiceNodePortStaticSubrange`。開啟此功能後,你可以為 `type: NodePort` 的 Service 使用一種不同的埠分配策略,並降低衝突的風險。
NodePort 的埠範圍將根據公式 `min(max(16, nodeport-size / 32), 128)` 進行劃分。該公式的結果將是一個介於 16 和 128 之間的數字,步長會隨著 NodePort 範圍的增大而增加。公式的結果決定了靜態埠範圍的大小。當埠範圍小於 16 時,靜態埠範圍的大小將設定為 0,這意味著所有埠都將動態分配。
動態埠分配預設將使用上段範圍,一旦該範圍用盡,將使用下段範圍。這將允許使用者在下段範圍使用靜態分配,且衝突風險較低。
示例
預設範圍:30000-32767
範圍屬性 | 值 |
---|---|
service-node-port-range | 30000-32767 |
範圍偏移量 | min(max(16, 2768/32), 128) = `min(max(16, 86), 128)` = `min(86, 128)` = 86 |
靜態範圍起始 | 30000 |
靜態範圍結束 | 30085 |
動態範圍起始 | 30086 |
動態範圍結束 | 32767 |
非常小的範圍:30000-30015
範圍屬性 | 值 |
---|---|
service-node-port-range | 30000-30015 |
範圍偏移量 | 0 |
靜態範圍起始 | - |
靜態範圍結束 | - |
動態範圍起始 | 30000 |
動態範圍結束 | 30015 |
小範圍(下邊界):30000-30127
範圍屬性 | 值 |
---|---|
service-node-port-range | 30000-30127 |
範圍偏移量 | min(max(16, 128/32), 128) = `min(max(16, 4), 128)` = `min(16, 128)` = 16 |
靜態範圍起始 | 30000 |
靜態範圍結束 | 30015 |
動態範圍起始 | 30016 |
動態範圍結束 | 30127 |
大範圍(上邊界):30000-34095
範圍屬性 | 值 |
---|---|
service-node-port-range | 30000-34095 |
範圍偏移量 | min(max(16, 4096/32), 128) = `min(max(16, 128), 128)` = `min(128, 128)` = 128 |
靜態範圍起始 | 30000 |
靜態範圍結束 | 30127 |
動態範圍起始 | 30128 |
動態範圍結束 | 34095 |
非常大的範圍:30000-38191
範圍屬性 | 值 |
---|---|
service-node-port-range | 30000-38191 |
範圍偏移量 | min(max(16, 8192/32), 128) = `min(max(16, 256), 128)` = `min(256, 128)` = 128 |
靜態範圍起始 | 30000 |
靜態範圍結束 | 30127 |
動態範圍起始 | 30128 |
動態範圍結束 | 38191 |