Service 转发链路:从 ClusterIP 到 EndpointSlice 与 kube-proxy

This article is extracted from the chat log with AI. Please identify it with caution.

说明:本文由 Codex 根据作者提供的主题、素材与公开资料辅助生成,属于 AI 生成/整理内容,非作者原创。请读者自行甄别并交叉验证。

阅读边界#

本文只回答一个运行时问题:一次访问普通 Service 的请求,怎样从服务名/ClusterIP 到达某个 Pod IP。重点是 DNS、EndpointSlice、节点数据面、DNAT、CNI/Calico 和逐层排障。

Serviceselector、端口字段、四种 type、会话亲和和 traffic policy 属于资源模型,不在这里重复展开;请先看 Service 详解。想深入其中一层,可继续阅读 CoreDNS 详解kube-proxy 详解Calico 流量走向Pod 优雅退出

一次普通 ClusterIP 请求的主链路#

假设客户端 Pod 访问:

http://web.default.svc.cluster.local:80

web 的后端 Pod 监听 8080。典型链路是:

客户端应用
  -> Pod DNS resolver / CoreDNS:服务名解析为 ClusterIP
  -> ClusterIP:80:报文进入发起节点或入口节点的 Service 数据面
  -> EndpointSlice:数据面依据已同步的可用后端集合选择一个端点
  -> DNAT:目的地址改为 PodIP:8080
  -> CNI / Calico:按到该 Pod IP 的本地或跨节点路径转发,并执行相关策略
  -> 目标 Pod 的网络命名空间和应用监听端口

这是一条便于理解的主线,不是严格的串行进程调用:

  • DNS 只完成名称解析,不选择后端,也不转发业务流量。
  • ClusterIP 通常是虚拟 IP,而不是某块网卡上实际监听的地址。
  • kube-proxy 或 eBPF 数据面会提前监听 Service 与 EndpointSlice 的变化,在节点上编程规则;请求到达时不需要再访问 API Server。
  • DNAT 后,后半段面对的是实际的 Pod IP;同节点交付、跨节点路由、IPIP/VXLAN 封装和 NetworkPolicy 都属于 CNI 数据面范围。

先识别例外:不同 Service 形态的链路分叉#

这里的表只用于判断请求会不会走“ClusterIP -> EndpointSlice -> DNAT”主链路;字段定义与选型请读 Service 详解

Service 形态请求的入口/解析结果是否走本页主链路
ClusterIPDNS 返回 ClusterIP,或客户端直接访问 ClusterIP是。
NodePort客户端先到 NodeIP:nodePort,随后进入 Service 后端选择后半段是;还需检查节点端口、防火墙和外部流量策略。
LoadBalancer先经过云或 LB 控制器提供的外部入口取决于实现,常在进入节点或直接进入 Pod 前接入同一后端集合。
Headless(clusterIP: NoneDNS 直接返回后端 Pod 地址否;客户端自行选择地址,通常没有 ClusterIP DNAT。
ExternalNameDNS 返回外部域名的 CNAME否;不创建常规 Service 转发规则。

第一层:DNS 把名字变成入口#

普通 Service 的 A/AAAA 记录解析到 ClusterIP。位于相同 namespace 的 Pod 可以用 web,跨 namespace 时至少要写 web.default;完整形式通常是:

web.default.svc.cluster.local

Headless Service 返回的是后端地址集合,ExternalName 返回 CNAME,所以在开始查 kube-proxy 前先确认 Service 的形态。DNS 还支持命名端口的 SRV 记录,但 A/AAAA 解析本身不含 targetPort 选择或负载均衡逻辑。

在集群内用临时诊断 Pod 验证名称解析:

kubectl -n default run dns-debug --rm -it --restart=Never --image=busybox:1.36 -- nslookup web.default.svc.cluster.local

kubectl -n default run curl-debug --rm -it --restart=Never --image=curlimages/curl -- sh
# 进入 shell 后:
curl -sv http://web.default.svc.cluster.local:80/
curl -sv http://10.96.0.100:80/

如果镜像拉取受限,应在已有的调试 Pod 中执行等价命令。DNS 失败时优先核对 namespace、Service 名称、Pod 的 /etc/resolv.conf 和 CoreDNS;不要把 DNS 问题误判为 EndpointSlice 或 kube-proxy 问题。

第二层:EndpointSlice 决定哪些后端可被选择#

对带 selector 的 Service,控制器根据匹配 Pod 自动维护一个或多个 EndpointSlice。每个 Slice 记录地址族、后端端口和一部分端点;数据面需要汇总同一 Service 的所有 Slice,而不是只读其中一个。

kubectl -n default get svc web -o wide
kubectl -n default describe svc web
kubectl -n default get pods -l app.kubernetes.io/name=web -o wide --show-labels
kubectl -n default get endpointslice -l kubernetes.io/service-name=web -o wide
kubectl -n default get endpointslice -l kubernetes.io/service-name=web -o yaml

EndpointSlice 的端点条件要拆开读:

条件在转发链路中的意义
ready常规新流量是否应选择该端点。
serving端点是否仍可提供响应;终止期间可能仍为 true。
terminating对应 Pod 已进入删除流程。

一次正常的 readiness 变化大致是:

应用 readinessProbe 失败
  -> Pod Ready=False
  -> EndpointSlice ready=false
  -> 数据面完成同步后,新的普通 Service 流量不再选择该端点

删除 Pod 时,EndpointSlice 可能出现:

conditions:
  ready: false
  serving: true
  terminating: true

这表示应停止把普通请求送给它,但它可能还在处理已有连接。EndpointSlice 更新、节点规则收敛、Ingress/LB 摘除和应用退出是并发的,不能假设看到 Terminating 后所有报文都会瞬时消失。终止顺序、preStop 和连接排空请看 Pod 优雅退出

若 EndpointSlice 为空,首先检查 Service selector、Pod labels、namespace 和 Pod Ready;若 Slice 有地址但流量失败,继续核对 Slice 的后端端口、Service targetPort 与应用真实监听端口。

第三层:Service 数据面选择端点并做 DNAT#

kube-proxy 的职责#

在传统实现中,每个节点上的 kube-proxy 观察 Service 和 EndpointSlice,维护把 Service 入口映射到后端 Pod 的规则。新连接命中 Service 虚拟 IP(或 NodePort)后,内核中的规则按可用端点和策略选择后端,并将目标地址改写为 PodIP:targetPort

ClusterIP:80
  -> Service 匹配
  -> 选择一个可用 EndpointSlice endpoint
  -> DNAT 到 PodIP:8080
  -> conntrack 维护该连接后续报文及回包的映射

因此,kube-proxy 通常不是每个请求都经过的用户态反向代理进程。一次已建立连接不会在每个报文上重新随机选择 Pod;实际的连接保持、会话亲和和源地址改写还会受 Service 类型、traffic policy 与数据面实现影响。

Linux 上常见的 kube-proxy 实现模式如下:

实现节点上可观察的对象运行时含义
iptablesnetfilter NAT 规则匹配 Service 后经规则链选择端点并 DNAT。
IPVSIPVS 虚拟服务器和真实服务器内核 IPVS 负责端点调度,仍需周边规则处理部分入口/出站路径。
nftablesnftables 规则集kube-proxy 用 nftables 编程 Service 转发规则。

详细模式差异、规则结构和配置请转到 kube-proxy 详解。这里的排查目标只是确认:当前节点是否由预期的数据面接管了这个 Service,并已收到正确的后端集合。

kube-proxy 与 eBPF 的边界#

一些 CNI 的 eBPF 数据面(包括相应配置下的 Calico)可以接管 Service 负载均衡,因而不依赖 kube-proxy 的 iptables/IPVS 规则。它们仍以 Kubernetes 的 Service 和 EndpointSlice 状态为输入,但在 eBPF 程序与 map 中完成选择、改写或转发。

所以:

  • 如果集群使用 kube-proxy,检查 kube-proxy 状态和它所选模式的规则。
  • 如果集群启用了 eBPF kube-proxy replacement,iptables-saveipvsadm 中没有 Service 规则不代表故障;应按该 CNI 的 eBPF 可观测性和配置排查。
  • 不要在不了解实际数据面所有者时同时修改 kube-proxy 与 CNI 配置。先确认集群安装方式和控制面配置。

查看 kube-proxy(仅在它确实作为 Service 数据面时):

kubectl -n kube-system get daemonset kube-proxy -o wide
kubectl -n kube-system get pods -l k8s-app=kube-proxy -o wide
kubectl -n kube-system logs daemonset/kube-proxy --tail=100

发起请求或接收 NodePort 的相关节点上,按实际模式检查其一即可:

# iptables 模式
iptables-save | grep -E 'KUBE-SVC|KUBE-SEP'

# IPVS 模式
ipvsadm -Ln

# nftables 模式
nft list ruleset | grep -i kube

命令不存在或没有匹配结果可能只是该节点未使用相应模式;不要把三种输出混在一起作为成功条件。

第四层:DNAT 之后由 CNI/Calico 把包送到 Pod#

当目的地址已变为 PodIP:targetPort,Service 的后端选择阶段已经结束。此后 CNI 决定如何将包送到 Pod:

同节点:Node 路由 / veth -> 目标 Pod
跨节点:到目标 Pod CIDR 的路由或隧道 -> 目标节点 -> veth -> 目标 Pod

Calico 在这一段处理 Pod 网络路由、可能的 BGP/IPIP/VXLAN 封装,以及网络策略。具体的报文走向见 Calico 流量走向。NetworkPolicy 的最终效果应针对 DNAT 后的真实 Pod 连接验证,具体挂载点随 CNI 数据面而异。

一个非常有效的隔离方法是从同一个客户端/调试 Pod 比较 Service IP 和 Pod IP:

# 找到某个候选后端
kubectl -n default get pods -l app.kubernetes.io/name=web -o wide

# 在同一个调试 Pod 内比较(示例)
curl -sv http://10.96.0.100:80/
curl -sv http://10.244.1.23:8080/

直接访问 Pod IP 只用于诊断,不能替代业务使用 Service。若 Pod IP 也不通,优先检查应用监听、CNI 路由、Calico 与 NetworkPolicy;若 Pod IP 通而 ClusterIP 不通,重点回到 Service、EndpointSlice 和该节点的数据面。

# 先查看 namespace 内声明的策略
kubectl -n default get networkpolicy

# 仅当节点上已能定位到该 Pod 的网络命名空间时
ip netns exec <pod-netns> ip addr

不同容器运行时暴露网络命名空间的方式不同;ip netns exec 不是所有节点上的通用第一步。它适合在已定位到目标 Pod 网络命名空间后,进一步确认接口和地址。

分层排查手册#

按顺序缩小范围,避免一开始就在节点上翻规则:

层次验证问题典型现象下一步
1. 名称服务名是否解析成预期结果nslookup 失败或返回意外地址namespace、CoreDNS、Headless/ExternalName 语义。
2. 对象Service 的 selector、端口与策略是否正确Service 没有后端describe svc、Pod labels、Ready 和 EndpointSlice。
3. 后端应用与 Pod 网络是否可达Pod IP 也失败监听端口、CNI、Calico、NetworkPolicy。
4. 虚拟入口ClusterIP 是否能到已知后端Pod IP 通、ClusterIP 不通发起节点的 kube-proxy/eBPF 数据面、targetPort、EndpointSlice。
5. 外部入口NodePort / LoadBalancer 是否把流量送入集群仅外部失败节点防火墙、安全组、LB 健康检查和 externalTrafficPolicy
6. 下线状态请求是否打到正在终止的副本发布期间偶发 5xx/连接重置ready/serving/terminating、摘流量延迟、应用 drain。

一组最小命令序列:

# 1. 对象与后端集合
kubectl -n default get svc web -o wide
kubectl -n default describe svc web
kubectl -n default get pods -l app.kubernetes.io/name=web -o wide
kubectl -n default get endpointslice -l kubernetes.io/service-name=web -o wide

# 2. 从集群内同类网络位置测试 DNS、VIP 与 Pod IP
kubectl -n default run net-debug --rm -it --restart=Never --image=curlimages/curl -- sh

# 3. 在相关 Node 上进一步追包(需要节点访问权限)
tcpdump -ni any host 10.96.0.100 or host 10.244.1.23
conntrack -L -p tcp | grep -E '10.96.0.100|10.244.1.23'

tcpdumpconntrack 的地址应替换为真实 ClusterIP、Pod IP 和协议;大规模节点上 conntrack -L 可能很重,宜先限制条件或使用更精确的过滤。

快速归因#

名称不通
  -> CoreDNS、namespace、Service 名称

EndpointSlice 为空或端口不对
  -> selector、Pod labels、Ready、targetPort、应用监听

Pod IP 不通
  -> 应用、CNI/Calico、路由、NetworkPolicy

Pod IP 通而 ClusterIP 不通
  -> Service、EndpointSlice、发起节点 kube-proxy / eBPF 数据面

只有 NodePort / LoadBalancer 不通
  -> 节点端口、防火墙/安全组、外部 LB、externalTrafficPolicy

把问题定位到这一层后,再阅读对应的深读文章并检查当前集群的实际实现,通常比泛泛地“重启网络组件”更快、更安全。

参考资料#

本文共 4048 字,创建于 Jun 28, 2026

相关标签: Kubernetes, DevOps, ByAI