Kubernetes ClusterIP 端口深度解析:虚拟服务与流量转发机制

事情的起因是创建了一个 NodePort 类型 Service,其端口映射关系为 8000:30948/TCP。既然30948是在每个node开的端口,那8000是开在哪的呢?出于好奇回顾了一下K8s的Cluster IP和Service


端口映射关系解析

在 Kubernetes 的 NodePort Service 中,端口配置遵循以下格式:

:/<协议>
  • 8000:Service 的 ClusterIP 端口(集群内部访问端口)
  • 30948:NodePort 端口(节点外部访问端口)
外部用户
节点IP:30948
集群内部Pod
Service IP:8000
Service
后端Pod

访问测试结果

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 时:

  1. kube-proxy 在所有节点上打开指定端口(30948)

  2. 创建 iptables/IPVS 规则:

    -A KUBE-NODEPORTS -p tcp --dport 30948 -j KUBE-SVC-XXXXXX
    
  3. 流量转发路径:

    外部用户 → 节点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 服务抽象层的核心设计。

请求 10.96.91.238:8000
Client Pod
Service ClusterIP
iptables/IPVS 规则
Pod 1:80
Pod 2:80
Pod 3:80

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.4
      ports:
      - port: 80
        protocol: TCP
    

流量转发全路径

当集群内部客户端访问 10.96.91.238:8000 时:

  1. 客户端发起请求

    resp, err := http.Get("http://10.96.91.238:8000")
    
  2. 内核网络栈拦截

    • 目标 IP 匹配 Service CIDR (如 10.96.0.0/16)
    • 进入 KUBE-SERVICES
  3. DNAT 转换

    • 根据 iptables 规则
    • 目标 IP:Port 被替换为 Pod IP:Port (如 10.244.1.2:80)
  4. 路由到目标 Pod

    • 通过 CNI 插件创建的网络路由
    • 流量进入 Pod 网络命名空间
  5. 容器接收请求

    • 容器内进程监听 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

  1. 稳定访问端点
    Pod 可能随时重建,但 Service IP 保持不变

  2. 负载均衡
    自动将流量分发到多个后端 Pod

  3. 服务发现
    通过 DNS 名称解耦服务位置

  4. 流量策略
    支持会话保持、流量权重等高级特性

  5. 安全隔离
    默认仅集群内部可访问,减少攻击面

生产环境实践

  1. 避免直接使用 NodePort 配合 Ingress 或 LoadBalancer 使用:

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: my-ingress
    spec:
      rules:
      - http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-dep
                port:
                  number: 8000  # 使用ClusterIP端口
    
  2. 自定义 NodePort 范围 修改 apiserver 配置:

    apiServer:
      extraArgs:
        service-node-port-range: "30000-35000"
    
  3. 防火墙规则 仅开放必要的 NodePort 端口:

    sudo ufw allow 30948/tcp
    

你可能感兴趣的:(K8s,kubernetes,容器,云原生)