说明:本文由 Codex 根据作者提供的主题、素材与公开资料辅助生成,属于 AI 生成/整理内容,非作者原创。请读者自行甄别并交叉验证。
阅读边界#
本文只回答一个运行时问题:一次访问普通 Service 的请求,怎样从服务名/ClusterIP 到达某个 Pod IP。重点是 DNS、EndpointSlice、节点数据面、DNAT、CNI/Calico 和逐层排障。
Service 的 selector、端口字段、四种 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 形态 | 请求的入口/解析结果 | 是否走本页主链路 |
|---|---|---|
ClusterIP | DNS 返回 ClusterIP,或客户端直接访问 ClusterIP | 是。 |
NodePort | 客户端先到 NodeIP:nodePort,随后进入 Service 后端选择 | 后半段是;还需检查节点端口、防火墙和外部流量策略。 |
LoadBalancer | 先经过云或 LB 控制器提供的外部入口 | 取决于实现,常在进入节点或直接进入 Pod 前接入同一后端集合。 |
Headless(clusterIP: None) | DNS 直接返回后端 Pod 地址 | 否;客户端自行选择地址,通常没有 ClusterIP DNAT。 |
ExternalName | DNS 返回外部域名的 CNAME | 否;不创建常规 Service 转发规则。 |
第一层:DNS 把名字变成入口#
普通 Service 的 A/AAAA 记录解析到 ClusterIP。位于相同 namespace 的 Pod 可以用 web,跨 namespace 时至少要写 web.default;完整形式通常是:
web.default.svc.cluster.localHeadless 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 yamlEndpointSlice 的端点条件要拆开读:
| 条件 | 在转发链路中的意义 |
|---|---|
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 实现模式如下:
| 实现 | 节点上可观察的对象 | 运行时含义 |
|---|---|---|
| iptables | netfilter NAT 规则 | 匹配 Service 后经规则链选择端点并 DNAT。 |
| IPVS | IPVS 虚拟服务器和真实服务器 | 内核 IPVS 负责端点调度,仍需周边规则处理部分入口/出站路径。 |
| nftables | nftables 规则集 | 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-save或ipvsadm中没有 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 -> 目标 PodCalico 在这一段处理 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'tcpdump 与 conntrack 的地址应替换为真实 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把问题定位到这一层后,再阅读对应的深读文章并检查当前集群的实际实现,通常比泛泛地“重启网络组件”更快、更安全。