两个 Service 用了同一个端口号——K8s 的沉默最可怕

场景:新部署一个有 NodePort 30080 的 Service,kubectl apply 报错"port is already allocated",但查当前 namespace 的 Service 列表根本看不到 30080。另一个团队占用了端口——在另一个 namespace,你完全不知道 路径:坐标(创建失败但本地看不到占用)→ 分层(namespace → 集群级 NodePort 分配 → kube-proxy iptables 残留)→ 路径(3 条命令找到端口归属)→ 定位("换个 namespace 就没事了"的错觉)→ 标点(预防 + Check-list) K8s 版本:v1.28,kube-proxy iptables 模式

上篇讲了 NetworkPolicy——一条规则没配 5672,两个服务同时连不上。这篇我们看另一种"沉默拒绝"——两个 Service 用了同一个 NodePort,这次连错误信息都不给你。

部署完一个 NodePort: 30080 的 Service(节点端口类型 Service,在每个集群节点上开放一个固定端口,外部流量通过 NodeIP:NodePort 直达 Pod)。kubectl apply 返回了错误。

你查了一遍当前 namespace——没有 Service 用 30080。 又翻了一遍——还是没有。 "K8s 抽风了吧?换个端口。"

30081,apply 通过了。问题解决了?没有——你只是绕过了问题,不是找到了根因。

你不是端口冲突——你是看不到端口

【坐标】NodePort 创建失败,Error 说"已分配"

结论前置:kubectl apply 报错了,但 kubectl get svc 找不到

$ cat web-frontend.yaml
apiVersion: v1
kind: Service
metadata:
  name: web-frontend
  namespace: frontend
spec:
  type: NodePort
  selector:
    app: web-frontend
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30080

$ kubectl apply -f web-frontend.yaml
The Service "web-frontend" is invalid:
spec.ports[0].nodePort: Invalid value: 30080: provided port is already allocated

kubectl apply 报错 + 全集群查找 NodePort 占用

马上查自己 namespace 的所有 Service:

$ kubectl get svc -n frontend -o wide
NAME             TYPE        CLUSTER-IP     PORT(S)   AGE
web-frontend     ClusterIP   10.96.100.1    80/TCP    5m

等等——连 web-frontend 都没列出来(因为创建失败了),更别说看到 30080。你翻一遍所有 Service,确信没人用 30080。"K8s 的问题吧?换一个端口试试。"

nodePort: 30081,apply 通过。问题解决了?不是——根因没找到,30081 明天也会被另一个团队撞上。

证据展开:--all-namespaces 才看到真相

试一下 kubectl get svc --all-namespaces -o wide ——输出几十行,PORT(S) 列长这样:

9090:30900/TCP

NodePort 信息(上面说的节点端口)藏在 PORT(S) 列末尾,以 containerPort:NodePort 的格式混在端口列表里。一整页输出翻下来,你根本不会注意到 30080 出现了两次。

要用 grep 才能精准定位:

$ kubectl get svc --all-namespaces -o wide | grep 30080
backend       api-gateway     NodePort    10.96.150.1     8080:30080/TCP            20d

30080 被 backend namespace 的 api-gateway Service 占了。20 天前创建的,你完全不知道。

衔接:那如果两个 Service 同时用 30080 会怎样?

上述场景 kube-apiserver(Kubernetes API 服务器,集群的控制面核心,所有资源操作都通过它)阻止了冲突——第二个 Service 创建失败。但有一个更隐蔽的场景:两个 Service 并存了——kube-proxy(K8s 网络代理组件,运行在每个节点上,负责将 Service 流量转发到后端 Pod)没清理干净旧规则,新 Service 上来后 iptables 层出现两条竞争规则

【分层】从端口占用查看,到 iptables 规则冲突

NodePort 冲突排查全路径:创建失败 → 定位占用 → iptables 残留 → 修复

排查顺序

层级 排查对象 关键命令 异常信号
① namespace 层 当前 namespace Service kubectl get svc -n <ns> -o wide 看不到占用
② 集群层 全集群 Service NodePort kubectl get svc --all-namespaces \| grep <port> 另一个 namespace 占着
③ iptables 层 kube-proxy NodePort 规则 iptables -t nat -L KUBE-NODEPORTS -n 同端口多条规则
④ kube-proxy 层 kube-proxy 同步状态 kubectl logs -n kube-system kube-proxy-xxx 残留规则未清理

① namespace 层 — 查自己的 Service

第一直觉是查当前 namespace,结果没有任何 Service 用 30080。这就走入了第一个陷阱:NodePort 是集群级资源,但 kubectl get svc 不加 --all-namespaces 只查自己 namespace。

② 集群层 — --all-namespaces 找到真凶

全集群 Service 列表,grep 定位 30080 归属

$ kubectl get svc --all-namespaces -o wide | grep 30080
backend       api-gateway     NodePort    10.96.150.1     8080:30080/TCP            20d

NodePort 的分配由 kube-apiserver 维护:已分配的端口记录在 kube-apiserver 的内存中,同端口跨 namespace 也被阻止。但问题是:你看不到分配表。没有 kubectl get nodeport-allocations 这类命令。你只能通过 grep 全集群的 Service 输出反向推断。

这是 NodePort 冲突最核心的设计问题:端口分配是内置的、自动的,但查询接口是缺失的。

③ iptables 层 — kube-proxy 的残留规则

NodePort 冲突不仅在创建时发生。还有一种更隐蔽的情况:一个 Service 被删除后,kube-proxy 的 iptables 规则没有立即清理干净。新 Service 使用相同端口后,iptables 中出现两条指向不同后端的 DNAT 规则

iptables 残留规则:同端口 30080 两条 DNAT

$ iptables -t nat -L KUBE-NODEPORTS -n
Chain KUBE-NODEPORTS (1 references)
target     prot opt source     destination
DNAT       tcp  --  0.0.0.0/0  0.0.0.0/0   tcp dpt:30080 to:10.244.1.5:8080
DNAT       tcp  --  0.0.0.0/0  0.0.0.0/0   tcp dpt:30080 to:10.244.2.10:8080

同一个端口 30080 两条 DNAT 规则——kube-proxy 没清理干净的残留和新规则同时存在。 外部请求打到 30080,iptables 会匹配命中第一条规则,导致流量全部去到一个旧的 Pod IP。

这种现象的原因是 kube-proxy 的同步延迟或部分节点上的 kube-proxy Pod 重启丢失了状态:

$ kubectl logs -n kube-system kube-proxy-j2k9s --tail=20 | grep -i "sync"
I0711 10:15:23.456789       1 iptables.go:178] "Syncing iptables rules"
W0711 10:15:25.123456       1 iptables.go:205] "Received invalid service update, retrying"

kube-proxy Pod 状态 + 日志同步异常

当一个 Service 被删除,kube-proxy 需要触发一次全量同步来清理相关 iptables 规则。如果同步失败(API Server 连接中断、kube-proxy 重启),旧规则就留在了节点上。

衔接:找到了问题根源,那怎么预防下一次?

NodePort 冲突的根本原因不是技术问题——是可见性问题。集群里 50 个 namespace,谁都不知道端口 30080 已被占用。等创建时报错,已经是事后了。

【路径】🔍 3 条命令找到端口归属

结论前置:三板斧确定冲突

查找 NodePort 占用 3 条命令 + 预期输出

异常判断标准

命令 正常 异常
kubectl get svc -A \| grep 30080 唯一匹配 多条匹配(同端口多个 Service 残留)
kubectl describe svc NodePort 行 显示 NodePort: <port> <setNodePort> NodePort 为空 — 自动分配未完成
iptables -L KUBE-NODEPORTS 每个端口一条 DNAT 规则 同端口多条 DNAT — 规则冲突

衔接:3 条命令顺序即排查思路

先查集群 Service 看谁占着端口 → 再查 iptables 看 kube-proxy 有没有残留规则 → 两条都确认了再接下一步修复。中间跳一步都可能误判。

【定位】最常见的误判

❌ 错误方向:"换个端口就好了"

kubectl apply 报错 "port is already allocated"
  → "哦被占用了,换个端口 30081"
  → kubectl apply 成功
  → "好了,问题不大"

为什么这个路径是错的:

换端口看似解决了眼前问题,但: - 你没有确定 30080 被谁占着,下一次部署到另一个 namespace 还会撞 - 30081 可能也被部分占用了(自动分配范围中的端口),明天另一个团队又撞上 - 更危险的是:如果旧 Service 被删除后 iptables 规则残留,你用了它的旧端口就可能在流量转发层面出现冲突

一个端口撞了换一个——这不是排查,是掩耳盗铃。

✅ 正确方向:"找到端口归属 + 建立预防机制"

kubectl apply 报错 "port is already allocated"
  → kubectl get svc -A | grep 30080 → 找到 backend/api-gateway
  → 确认是否真的是两个团队需要同一个端口
  → 方案 A:团队协商,谁用不同的端口
  → 方案 B:建立 NodePort 分配表/静态分配策略
  → 方案 C:NodePort 范围扩容 + 团队分区

两者的本质差异:错误方向把端口冲突看作偶发事件绕过,正确方向把它当作集群资源管理问题系统性解决。

检查 iptables 规则是防止残留规则导致问题的最后一道防线。iptables -t nat -L KUBE-NODEPORTS | grep 30080 的输出是一条还是多条——这个判断决定了"修好了"还是"以为修好了"。

【标点】修复 + Check-list

修复方案 A:静态 NodePort 分配 + 团队分区

NodePort 静态分配表 + YAML 配置示例

将 NodePort 范围分成区域,每个团队分配一段(截图已展示完整映射),Service YAML 中显式指定分区内的静态端口即可。

修复方案 B:扩容 NodePort 范围 + 清理残留规则

# 1. 修改 kube-apiserver 启动参数扩大范围
# 在 /etc/kubernetes/manifests/kube-apiserver.yaml 中添加:
# --service-node-port-range=30000-33767

# 2. 节点上清理残留 iptables 规则
iptables -t nat -D KUBE-NODEPORTS -p tcp --dport 30080 \
  -j DNAT --to-destination 10.244.1.5:8080

# 3. 触发 kube-proxy 全量同步
kubectl rollout restart -n kube-system daemonset kube-proxy

修复命令 + Check-list 拷贝即用

修复方案 C:用 Ingress 替代 NodePort(生产环境推荐)

NodePort 本身是为开发测试设计的,生产环境建议使用 Ingress Controller(入口控制器,集群外部流量的统一入口,通过域名/路径规则将请求路由到内部 Service)暴露服务。Ingress 只需要一个 NodePort(供 Ingress Controller 使用),所有后端 Service 用 ClusterIP 即可。

NodePort 冲突排查 Check-list

□ kubectl get svc --all-namespaces -o wide | grep <port>
  → 列出全集群中使用了该 NodePort 的 Service
  → 确认是哪个 namespace 占用的

□ kubectl describe svc <name> -n <ns> | grep NodePort
  → 查看该 Service 的 NodePort 配置
  → 确认是显式指定的还是自动分配的

□ iptables -t nat -L KUBE-NODEPORTS -n | grep <port>
  → 查看节点上 kube-proxy 的 DNAT 规则
  → 同端口多条 → 残留规则未清理

□ kubectl logs -n kube-system -l k8s-app=kube-proxy | grep -i "sync"
  → 查看 kube-proxy 是否有同步异常
  → "Failed to sync" → kube-proxy 工作不正常

□ 检查集群 NodePort 范围配置
  → kubectl describe pod -n kube-system kube-apiserver-<node>
  → grep service-node-port-range 确认范围

附:完整命令清单

# === 1. NodePort 占用查找 ===
kubectl get svc --all-namespaces -o wide | grep <port>
kubectl describe svc <name> -n <namespace> | grep -A 2 "NodePort"

# === 2. 自定义列查询(只显示 Name + Namespace + NodePort) ===
kubectl get svc --all-namespaces -o custom-columns=\
'NAMESPACE:.metadata.namespace,NAME:.metadata.name,NODEPORT:.spec.ports[*].nodePort'

# === 3. iptables NodePort 规则检查 ===
iptables -t nat -L KUBE-NODEPORTS -n
iptables -t nat -L KUBE-NODEPORTS -n | grep <port>

# === 4. kube-proxy 状态 ===
kubectl get pods -n kube-system -l k8s-app=kube-proxy
kubectl logs -n kube-system -l k8s-app=kube-proxy --tail=50 | grep -i "sync\|error"

# === 5. NodePort 范围检查 ===
kubectl describe pod -n kube-system kube-apiserver-$(hostname) \
  | grep service-node-port-range

# === 6. 修复 ===
# 触发 kube-proxy 全量同步
kubectl rollout restart -n kube-system daemonset kube-proxy

# 手动清理残留 iptables 规则
iptables -t nat -D KUBE-NODEPORTS -p tcp --dport <port> \
  -j DNAT --to-destination <old-pod-ip>:<port>

NodePort 冲突的本质不是端口不够用——30000-32767 有 2767 个端口,远多于正常需求。是集群扩大了,但管理思维还停留在单 namespace 阶段。端口分配是集群级的,可见性却是 namespace 级的。

你现在集群的 NodePort 是谁在管?有没有一张分配表?如果没有——今天这篇文章就是你的第一张。

下篇我们聊 Pod 内无法访问集群外网络——所有 K8s 进程都正常,但 Pod 里 curl 外网超时。是 CNI 没配 NAT?还是节点路由少了一条?

🔗 个人博客:https://opencao.cn
📺 公众号:Ai拆代码的曹操
🌟 知识星球:Ai拆代码的曹操