本文已发布一年以上。较旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已失效。

Kubernetes 1.27:避免为 NodePort 服务分配端口时的冲突

在 Kubernetes 中,Service 可以用于为一组 Pod 上运行的应用提供统一的流量入口。客户端可以使用 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 Service 的 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
pie showData title 30000-32767 "静态" : 86 "动态" : 2682

很小范围:30000-30015

范围属性
service-node-port-range30000-30015
区间偏移0
静态区间起始-
静态区间结束-
动态区间起始30000
动态区间结束30015
pie showData title 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
pie showData title 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
pie showData title 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
pie showData title 30000-38191 "静态" : 128 "动态" : 8064