Kubernetes ClusterIP 端口深度解析:虚拟服务与流量转发机制
事情的起因是创建了一个 NodePort 类型 Service,其端口映射关系为 8000:30948/TCP。既然30948是在每个node开的端口,那8000是开在哪的呢?出于好奇回顾了一下K8s的Cluster IP和Service
端口映射关系解析
在 Kubernetes 的 NodePort Service 中,端口配置遵循以下格式:
<ClusterIP 端口>:<NodePort 端口>/<协议>
- 8000:Service 的 ClusterIP 端口(集群内部访问端口)
- 30948:NodePort 端口(节点外部访问端口)
访问测试结果
1. 在宿主机上访问 IP:8000
curl http://<节点IP>:8000
结果:连接失败
原因:
- 8000 端口仅在集群内部监听(通过 Service 的 ClusterIP)
- 节点操作系统没有在 8000 端口监听请求
2. 在宿主机上访问 IP:30948
curl http://<节点IP>:30948
结果:成功访问服务
原因:
- kube-proxy 在所有节点上监听了 30948 端口
- 流量会被转发到 Service 的后端 Pod
技术原理详解
1. NodePort 工作原理
当创建 NodePort Service 时:
-
kube-proxy 在所有节点上打开指定端口(30948)
-
创建 iptables/IPVS 规则:
-A KUBE-NODEPORTS -p tcp --dport 30948 -j KUBE-SVC-XXXXXX
-
流量转发路径:
外部用户 → 节点IP:30948 → kube-proxy → Service → Pod
2. ClusterIP 端口用途
-
集群内部访问入口:
# 在集群内部Pod中访问 curl http://my-dep.default.svc.cluster.local:8000
-
服务发现的基础端口
实际验证步骤
1. 查看 Service 完整定义
kubectl describe svc my-dep
输出关键部分:
Port: 8000/TCP
TargetPort: 80/TCP # 后端Pod实际端口
NodePort: 30948/TCP
Endpoints: 10.244.1.2:80,10.244.2.3:80 # 后端Pod IP
2. 测试端口访问
# 1. 访问NodePort (应成功)
curl http://<任一节点IP>:30948# 2. 访问ClusterIP端口 (应失败)
curl http://<节点IP>:8000# 3. 在集群内部访问 (在Pod中执行)
kubectl run test --image=busybox -it --rm --restart=Never -- \wget -qO- http://my-dep:8000
3. 检查节点端口监听
# 在K8s节点上执行
sudo netstat -tuln | grep 30948
# 应输出: tcp6 0 0 :::30948 :::* LISTEN
那么回到最开始的问题,8000端口开在哪呢?
如果你采用的是原生搭建k8s,那么你一定会记得初始化的时候有这样一个命令
kubeadm init \
--apiserver-advertise-address=172.31.0.4 \
--control-plane-endpoint=cluster-endpoint \
--image-repository registry.cn-hangzhou.aliyuncs.com/k8s_images \
--kubernetes-version v1.20.9 \
--service-cidr=10.96.0.0/16 \
--pod-network-cidr=192.168.0.0/16
在这里就指定了创建svc和pod的网段
集群内部访问端口的本质
ClusterIP 端口是 Kubernetes 服务抽象层的核心设计。
ClusterIP 端口的三层抽象
1. 虚拟 IP 层 (Service ClusterIP)
- 非真实接口:ClusterIP (如
10.96.91.238
) 是 kube-proxy 创建的虚拟 IP - 无端口监听:节点操作系统上没有进程真正监听 8000 端口
- 内核级拦截:通过 Linux 内核的 netfilter 框架实现流量拦截
2. 规则转发层 (kube-proxy)
kube-proxy 创建转发规则(以 iptables 为例):
# 查看 Service 规则链
sudo iptables -t nat -L KUBE-SERVICES# 示例输出
KUBE-SVC-XYZ tcp -- anywhere 10.96.91.238 tcp dpt:8000
具体规则细节:
# DNAT 规则
-A KUBE-SVC-XYZ -m statistic --mode random --probability 0.333 -j KUBE-SEP-111
-A KUBE-SVC-XYZ -m statistic --mode random --probability 0.5 -j KUBE-SEP-222
-A KUBE-SVC-XYZ -j KUBE-SEP-333# 终结点规则
-A KUBE-SEP-111 -p tcp -m tcp -j DNAT --to-destination 10.244.1.2:80
-A KUBE-SEP-222 -p tcp -m tcp -j DNAT --to-destination 10.244.1.3:80
-A KUBE-SEP-333 -p tcp -m tcp -j DNAT --to-destination 10.244.2.4:80
3. 真实端点层 (Pod)
-
实际端口监听:在 Pod 内部的容器端口(如配置的 80 端口)
-
Endpoint 对象管理:
apiVersion: v1 kind: Endpoints metadata:name: my-dep subsets: - addresses:- ip: 10.244.1.2- ip: 10.244.1.3- ip: 10.244.2.4ports:- port: 80protocol: TCP
流量转发全路径
当集群内部客户端访问 10.96.91.238:8000
时:
-
客户端发起请求:
resp, err := http.Get("http://10.96.91.238:8000")
-
内核网络栈拦截:
- 目标 IP 匹配 Service CIDR (如 10.96.0.0/16)
- 进入
KUBE-SERVICES
链
-
DNAT 转换:
- 根据 iptables 规则
- 目标 IP:Port 被替换为 Pod IP:Port (如 10.244.1.2:80)
-
路由到目标 Pod:
- 通过 CNI 插件创建的网络路由
- 流量进入 Pod 网络命名空间
-
容器接收请求:
- 容器内进程监听 80 端口
- 处理请求并返回响应
与 NodePort 的关键区别
特性 | ClusterIP 端口 (8000) | NodePort 端口 (30948) |
---|---|---|
可见性 | 仅集群内部可见 | 可从集群外部访问 |
实现层级 | 内核网络栈 (L3/L4) | 用户空间监听 (L4) |
监听位置 | 无真实监听,仅规则 | kube-proxy 进程真实监听 |
访问控制 | 受网络策略控制 | 受节点防火墙控制 |
性能开销 | 低(内核转发) | 中(用户态转发) |
数据包变化 | 目标地址被修改 | 目标地址不变 |
查看内核规则
在任意节点执行:
# 查看NAT表规则
sudo iptables -t nat -L KUBE-SERVICES -n --line-numbers# 查找Service规则
sudo iptables -t nat -L KUBE-SVC-$(kubectl get svc my-dep -o jsonpath='{.spec.ports[0].name}') -n
为什么需要 ClusterIP
-
稳定访问端点
Pod 可能随时重建,但 Service IP 保持不变 -
负载均衡
自动将流量分发到多个后端 Pod -
服务发现
通过 DNS 名称解耦服务位置 -
流量策略
支持会话保持、流量权重等高级特性 -
安全隔离
默认仅集群内部可访问,减少攻击面
生产环境实践
-
避免直接使用 NodePort 配合 Ingress 或 LoadBalancer 使用:
apiVersion: networking.k8s.io/v1 kind: Ingress metadata:name: my-ingress spec:rules:- http:paths:- path: /pathType: Prefixbackend:service:name: my-depport:number: 8000 # 使用ClusterIP端口
-
自定义 NodePort 范围 修改 apiserver 配置:
apiServer:extraArgs:service-node-port-range: "30000-35000"
-
防火墙规则 仅开放必要的 NodePort 端口:
sudo ufw allow 30948/tcp