Skip to content

Service 与转发

Pod 会重建、漂移、扩缩,IP 跟着变。如果调用方直接连 Pod IP,每次 Pod 重建都要更新配置或 DNS——在经常滚动更新和自动扩缩的集群里,这套手动流程很快会崩。

Service 解决的就是这个问题:给调用方提供稳定的名称和虚拟 IP(ClusterIP),底层 Pod 怎么变,这个入口不变。

Service 和 EndpointSlice

Service 通过 selector 匹配 Pod,控制器生成 EndpointSlice 保存后端 Pod IP 列表:

验证这个链条:

bash
kubectl get svc web -n demo                  # Service 的 ClusterIP 和端口
kubectl get endpointslice -n demo -l kubernetes.io/service-name=web  # 后端 IP 列表
kubectl get pod -n demo -l app=web -o wide   # Pod IP 和 Ready 状态

EndpointSlice 为空→Service 没有后端→请求返回"connection refused"。原因通常是 selector 没匹配到任何 Ready Pod,或者 Pod 的 readinessProbe 没通过。

这条链路里有三个层面:

层面作用常见排查入口
Service给调用方一个稳定入口,包含 ClusterIP、端口、selectorkubectl get svckubectl describe svc
EndpointSlice保存真正可转发的 Pod IP 和端口kubectl get endpointslice
kube-proxy / eBPF 数据面在节点上把 ClusterIP 请求转发到 Pod IPiptables-saveipvsadm、CNI 组件日志

ClusterIP——集群内部的服务发现

只创建最简的 ClusterIP Service:

yaml
apiVersion: v1
kind: Service
metadata:
  name: web
  namespace: demo
spec:
  type: ClusterIP
  selector:
    app: web
  ports:
    - port: 80        # Service 端口
      targetPort: 8080 # Pod 容器端口

port 是 Service 对外的端口,targetPort 是 Pod 内容器监听的端口。不写 targetPort 时默认等于 port。不写 type 时默认就是 ClusterIP。不写 clusterIP 时,API Server 从 Service CIDR 里自动分配一个。

创建后 Pod 内可以通过 DNS 名访问:

bash
curl http://web.demo.svc.cluster.local

同 namespace 可以直接用短名 web。跨 namespace 必须用完整域名——DNS 名里 namespace 写错是常见问题,curl web 在 demo namespace 能通,在 default namespace 不通。

ClusterIP 只在集群内部可达。集群外的流量到不了 ClusterIP——要暴露到外部,用 NodePort、LoadBalancer 或 Gateway/Ingress。

NodePort——在节点上开端口

NodePort 在每个工作节点上打开相同的端口,外部通过 <任意节点IP>:<NodePort> 访问 Service:

yaml
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30080  # 不写时系统从 30000-32767 自动分配

请求路径:

集群外部需要知道至少一个节点的 IP,且节点挂了需要切换 IP。

NodePort 适合实验和裸机临时暴露。生产里 NodePort 前面通常还有 HAProxy、LVS 或云负载均衡做统一入口和高可用——NodePort 本身不提供负载均衡和高可用。

LoadBalancer——分配外部 IP

LoadBalancer 依赖云控制器或裸机实现来分配外部 IP:

环境实现
公有云云厂商 SLB/ELB/NLB
裸机MetalLB、kube-vip、外部 LB
本地测试minikube tunnel、kind + 额外组件

裸机集群直接创建 LoadBalancer Service 时,EXTERNAL-IP 一直显示 <pending>——不是 Service 写错了,是集群里没有能分配外部 IP 的控制器。

kube-proxy——把 Service 规则落到节点

Service 和 EndpointSlice 是 API 对象,kube-proxy 负责把它们变成节点上的实际转发规则:

模式实现方式特点
iptables规则链转发通用,但 Service 多了之后规则数量暴涨,排查时 iptables-save 输出很重
IPVS内核级负载均衡适合大规模 Service,支持多种调度算法
bash
kubectl -n kube-system get cm kube-proxy -o yaml | grep mode   # 确认模式
ipvsadm -Ln                        # IPVS 模式查看转发规则
iptables-save | grep KUBE-SVC | head -50    # iptables 模式

Service 转发不通时的排查顺序:

bash
# 1. 直连 Pod IP 测试——判断 Pod 本身通不通
curl -v http://<pod-ip>:8080

# 2. 走 Service ClusterIP——判断 Service 到 Pod 的链路
curl -v http://<cluster-ip>:80

# 3. 走 DNS 名——判断 DNS 解析
curl -v http://web.demo.svc.cluster.local

直连 Pod IP 通但 Service 不通→问题在 Service 端口映射、selector、EndpointSlice、kube-proxy 或节点转发规则。Pod IP 就不通→问题在 CNI、NetworkPolicy 或应用本身没在监听。

DNS——Service 名称到 ClusterIP

CoreDNS 负责把 Service 的 DNS 名解析成 ClusterIP。它不负责 Service 到 Pod 的转发:

bash
kubectl -n kube-system get pods -l k8s-app=kube-dns

# 临时 Pod 内验证
kubectl run dns-test --rm -it --image=busybox:1.36 --restart=Never -- sh
nslookup kubernetes.default.svc.cluster.local
nslookup web.demo.svc.cluster.local

DNS 解析成功只说明名字能指向 ClusterIP。后端 Pod 是否 Ready、能否接收请求,还要看 EndpointSlice 和 Pod 状态。

常见排查

现象方向
DNS 不解析CoreDNS Pod 状态、Pod DNS 配置、NetworkPolicy 阻拦
Service 无后端selector 和 Pod labels 不匹配、Pod readiness 未通过
直连 Pod 通、Service 不通kube-proxy、EndpointSlice、Service 端口映射
同节点通、跨节点不通CNI、节点路由、防火墙