本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

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-range30000-32767
範圍偏移量min(max(16, 2768/32), 128)
= `min(max(16, 86), 128)`
= `min(86, 128)`
= 86
靜態範圍起始30000
靜態範圍結束30085
動態範圍起始30086
動態範圍結束32767
餅圖顯示資料 標題 30000-32767 “靜態” : 86 “動態” : 2682

非常小的範圍:30000-30015

範圍屬性
service-node-port-range30000-30015
範圍偏移量0
靜態範圍起始-
靜態範圍結束-
動態範圍起始30000
動態範圍結束30015
餅圖顯示資料 標題 30000-30015 “靜態” : 0 “動態” : 16

小範圍(下邊界):30000-30127

範圍屬性
service-node-port-range30000-30127
範圍偏移量min(max(16, 128/32), 128)
= `min(max(16, 4), 128)`
= `min(16, 128)`
= 16
靜態範圍起始30000
靜態範圍結束30015
動態範圍起始30016
動態範圍結束30127
餅圖顯示資料 標題 30000-30127 “靜態” : 16 “動態” : 112

大範圍(上邊界):30000-34095

範圍屬性
service-node-port-range30000-34095
範圍偏移量min(max(16, 4096/32), 128)
= `min(max(16, 128), 128)`
= `min(128, 128)`
= 128
靜態範圍起始30000
靜態範圍結束30127
動態範圍起始30128
動態範圍結束34095
餅圖顯示資料 標題 30000-34095 “靜態” : 128 “動態” : 3968

非常大的範圍:30000-38191

範圍屬性
service-node-port-range30000-38191
範圍偏移量min(max(16, 8192/32), 128)
= `min(max(16, 256), 128)`
= `min(256, 128)`
= 128
靜態範圍起始30000
靜態範圍結束30127
動態範圍起始30128
動態範圍結束38191
餅圖顯示資料 標題 30000-38191 “靜態” : 128 “動態” : 8064