Repository Reading Site
第十三课:Service、EndpointSlice、kube-proxy、CoreDNS 与服务发现原理
上一课我们把 HPA 讲清楚了,知道了: 但平台真正要跑起来,还差一条非常硬的链路: 如果这一层不懂,你会在真实生产里反复踩坑: 1. Pod 都是 `Running`,为什么服务还是不通 2. DNS 明明解析成功了,为什么 `wget` 还是报错 3. 为什么有的 Service 没 Endpoints 4. 为什么 Endpoints 明明有,访问还是
第十三课:Service、EndpointSlice、kube-proxy、CoreDNS 与服务发现原理
为什么这一课必须接在 HPA 之后
上一课我们把 HPA 讲清楚了,知道了:
- 什么时候该扩副本
- 什么时候该缩副本
- 指标从哪里来
- 为什么
requests会影响自动扩缩容
但平台真正要跑起来,还差一条非常硬的链路:
副本数变了之后,流量到底怎样稳定地找到这些 Pod。
如果这一层不懂,你会在真实生产里反复踩坑:
- Pod 都是
Running,为什么服务还是不通 - DNS 明明解析成功了,为什么
wget还是报错 - 为什么有的 Service 没 Endpoints
- 为什么 Endpoints 明明有,访问还是
Connection refused - Headless Service 和普通 Service 到底差在哪
- ClusterIP 为什么说是“虚拟 IP”
你以后想做到“架构搭建 + 问题排查 + 指导团队”,必须把这条服务发现链路吃透。
先建立一个核心认知:Service 解决的是“稳定入口”,不是“应用真的在监听”
Pod 天生不稳定:
- 会重建
- IP 会变
- 会扩容缩容
- 会跨节点调度
如果上游直接记 Pod IP,系统根本没法稳定运行。
所以 Kubernetes 需要一个抽象层:
给一组动态变化的 Pod,提供一个稳定名字和稳定访问入口。
这就是 Service 的本质。
但你要立刻补上一句非常重要的话:
Service 只负责把流量转到“它认为的后端”,不保证后端应用一定监听对了端口,也不保证应用逻辑一定健康。
这句话会直接决定你排障时的方向。
Service 到底由哪几个部件共同实现
很多新手会把 Service 理解成“一个对象”。
这不完整。
真正生效至少有四类角色在协作。
第一类:Service 对象
它描述:
- 我要暴露哪个端口
- 选择哪些 Pod 作为后端
- 类型是
ClusterIP、NodePort、LoadBalancer还是 Headless
第二类:EndpointSlice
它描述:
- 这个 Service 当前到底有哪些后端地址
- 每个后端对应什么 IP、什么端口
- 是否 ready / serving / terminating
这相当于 Service 的“后端成员表”。
第三类:kube-proxy
它负责:
- 监听 Service 和 EndpointSlice 变化
- 在每个节点上写入数据平面规则
- 把发往 ClusterIP 的流量转发到后端 Pod
在这套集群里,它实际运行在:
iptables模式
第四类:CoreDNS
它负责:
- 把
web-svc.service-lab.svc.cluster.local这类名字解析成 Service IP - 把 Headless Service 解析成一组 Pod IP
- 把集群外域名继续转发给上游 DNS
所以你以后要把服务发现理解成两条并行链:
- 名字解析链:Pod ->
/etc/resolv.conf-> CoreDNS -> DNS 结果 - 流量转发链:Pod -> ClusterIP -> kube-proxy 规则 -> Pod IP:Port
它们相关,但不是一回事。
先分清几个 Service 类型,不然后面会混
ClusterIP
默认类型。
作用:
只在集群内部提供一个稳定虚拟 IP。
这次实验里的 web-svc、web-no-endpoints、web-wrong-port 都是 ClusterIP。
Headless Service
也就是:
clusterIP: None
作用:
不提供 VIP,直接通过 DNS 返回后端 Pod IP 列表。
这次实验里的 web-headless 就是这个类型。
NodePort
作用:
在每个节点上开一个固定端口,把外部请求转到对应 Service。
这属于“让集群外流量能进来”的一种基础办法,但它仍然主要是四层能力。
LoadBalancer
作用:
让云厂商或负载均衡器对外提供入口,再转到 Service。
Ingress / Gateway API
它们和 Service 不是一个层次。
它们做的是:
七层 HTTP/HTTPS 路由。
也就是说:
- Service 更偏四层 TCP/UDP
- Ingress / Gateway 更偏七层路径、域名、TLS
你要有 OSI 和分层意识:
- DNS 是应用层协议,默认走 UDP/TCP 53
- Service ClusterIP 更多体现为三层 IP + 四层端口的转发表
- Ingress 是七层 HTTP 路由
这套真实集群里的服务发现底座长什么样
这一课我先核对了集群真实配置,而不是纸上谈兵。
1. Pod 默认用谁做 DNS
我在 service-lab/dns-client Pod 里看到:
search service-lab.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
这三行非常重要。
nameserver 10.96.0.10
说明 Pod 内默认 DNS 请求发给:
kube-dnsService
我进一步核对系统 Service,确实看到:
kube-dns的 ClusterIP 是10.96.0.10
search ...
说明你在 Pod 里只写短名时,系统会自动尝试拼接搜索域。
例如你执行:
nslookup web-svc
它可能依次尝试:
web-svc.service-lab.svc.cluster.localweb-svc.svc.cluster.localweb-svc.cluster.local
这也是为什么有时你会看到:
- 一边已经解析成功
- 一边仍然夹杂几行
NXDOMAIN
不是系统坏了,而是 DNS 搜索路径在工作。
options ndots:5
表示名字里点的数量不足 5 个时,会优先按搜索域扩展。
所以排障时如果你想要“干净证据”,最好直接查 FQDN。
2. CoreDNS 在这套集群里的配置
我检查了 kube-system/coredns ConfigMap,看到关键配置:
kubernetes cluster.local in-addr.arpa ip6.arpaforward . /etc/resolv.confcache 30loadbalance
这表示:
- 对
cluster.local域名,CoreDNS 走 Kubernetes 插件逻辑 - 对集群外域名,CoreDNS 转发给宿主机上游 DNS
- 结果会缓存 30 秒
- 多个结果可以做一定的负载均衡
3. kube-proxy 的真实模式
kube-proxy ConfigMap 里 mode 是空字符串,看起来像“没有配”。
但日志明确显示:
Using iptables proxyUsing iptables Proxier
所以在这套集群里,Service 数据平面实际是:
- kube-proxy 监听对象变化
- 在每台节点写入
iptables规则
这很关键,因为不同集群的数据平面实现可能不同:
iptablesIPVSnftables- eBPF / Cilium
但是不管实现细节怎么变,Service 的核心语义不变:
提供稳定虚拟入口,把流量转到后端。
ClusterIP 为什么说是“虚拟 IP”
这一点是 Service 的灵魂。
我在 dns-client 所在节点 wk-1 上直接查网卡地址:
- 查
10.108.57.14,没有 - 查
10.97.187.150,没有 - 查
10.98.244.141,没有
也就是说:
这些 Service IP 根本没有真正绑在宿主机网卡上。
那为什么还能访问?
因为请求并不是发到某个真实网卡进程上,而是被内核里的转发表规则拦住并改写了目的地址。
在这套集群里,这份“转发表”就是 kube-proxy 写下去的 iptables 规则。
所以你以后面试如果别人问:
Service ClusterIP 明明没有网卡绑定,为什么能通?
你就应该回答:
- 因为它是内核数据平面的虚拟入口
- 请求命中 kube-proxy 写的转发规则
- 再被 DNAT 到真实 Pod IP 和端口
这才叫真正理解。
正常样本:web-svc 到底发生了什么
这次实验里我创建了一个最小但很有教学价值的样本:
- Deployment:
web - 副本数:2
- 容器端口:
8080 - Service:
web-svc - Service 端口:
80 targetPort: 8080
两个 Pod 的真实信息例如:
10.244.119.193,节点us59006872805610.244.147.94,节点wk-1
对应的 Service 是:
web-svcClusterIP:10.108.57.14
对应的 Endpoints / EndpointSlice 是:
10.244.119.193:808010.244.147.94:8080
访问结果
我从 dns-client 里连续请求:
wget -qO- http://web-svc
返回内容交替出现:
pod=web-76d9b6dc8-2w9ltpod=web-76d9b6dc8-dc6p8
这证明:
- 请求先到
web-svc - 再被分发到两个不同后端 Pod
宿主机上对应的规则证据
在 wk-1 宿主机 iptables-save 里能看到:
10.108.57.14进入KUBE-SVC-HCMWRNUQQOC4KVG2- 这个链再按概率
0.5- 转到
10.244.119.193:8080 - 或转到
10.244.147.94:8080
- 转到
这件事说明两个非常重要的原理。
第一:Service 的负载均衡在这套集群里是通过 iptables 规则实现的
不是用户态代理进程在一跳一跳转发。
第二:这更像“连接级分发”,不是“每个 HTTP 请求都单独负载均衡”
我们这次之所以多次 wget 能看到不同 Pod,是因为每次 wget 都新建了一个 TCP 连接。
如果你的客户端长期复用同一条连接,后端选择未必每次变化。
这就是为什么理解:
- 四层连接
- conntrack
- 七层请求
之间的区别非常重要。
Headless Service:为什么 DNS 结果完全不一样
我同时创建了:
web-headless
它和 web-svc 选中的是同一组 Pod,但设置了:
clusterIP: None
查询结果显示:
web-svc.service-lab.svc.cluster.local -> 10.108.57.14web-headless.service-lab.svc.cluster.local -> 10.244.147.94, 10.244.119.193
这就是 Headless Service 的核心差异:
- 普通 Service:DNS 返回一个稳定 VIP
- Headless Service:DNS 直接返回后端 Pod 列表
使用场景
Headless 特别适合:
- StatefulSet
- 主从发现
- 需要客户端自己感知多个后端地址
- 需要稳定网络身份的系统
这也是为什么你前面学 StatefulSet 时必须配 Headless Service。
第一个故障样本:web-no-endpoints
这是非常高频的一类真实事故。
我故意创建了一个 Service:
- 名字:
web-no-endpoints - ClusterIP:
10.97.187.150 - selector 多加了一个不存在的标签
结果是:
- DNS 解析成功
- 但
Endpoints是<none> EndpointSlice也是空的wget http://web-no-endpoints报:Connection refused
这说明什么
说明问题根本不在 DNS。
因为:
- 名字能解析
- Service 对象也存在
真正出问题的是:
- Service 找不到后端 Pod
宿主机上的证据更直接
在 iptables-save 里我看到了:
"service-lab/web-no-endpoints:http has no endpoints" -j REJECT
这句话的含义非常硬核:
kube-proxy 已经知道这个 Service 没后端,所以它直接下发了一条拒绝规则。
这就是为什么你会得到 Connection refused。
以后你怎么排这类问题
如果你看到:
- DNS 成功
- 但 Service 不通
Endpoints是空的
那优先查:
- Service
selector和 Podlabels是否匹配 - Pod 是否
Ready - Pod 是否被 Service 控制器剔除
这类问题本质上是:
- 选择器 / 就绪状态 / 后端成员表问题
不是 DNS 故障。
第二个故障样本:web-wrong-port
这类问题比“无 Endpoints”更隐蔽。
我故意创建了另一个 Service:
- 名字:
web-wrong-port - ClusterIP:
10.98.244.141 - selector 正常
- 但
targetPort故意写成了9090
而 Pod 实际监听的端口是:
8080
结果是什么
我看到:
- DNS 解析成功
Endpoints也不是空的- 但成员表里端口是:
10.244.119.193:909010.244.147.94:9090
wget http://web-wrong-port仍然报:Connection refused
为什么会这样
因为 Service 控制器并不会替你验证:
- Pod 里这个端口上到底有没有进程在监听
它做的只是:
- 按 selector 找 Pod
- 按 Service 的
targetPort生成后端地址
于是 kube-proxy 很老实地把流量 DNAT 到:
10.244.119.193:909010.244.147.94:9090
但容器里根本没人监听 9090,所以连接被对端拒绝。
这是非常典型的排障训练
你要把两类失败分开:
web-no-endpoints
- DNS 成功
- Endpoints 空
- 根因在 selector / readiness
web-wrong-port
- DNS 成功
- Endpoints 有值
- 根因在
targetPort/ 容器监听端口
很多人只会说“Service 不通”,但讲不清故障层次。
真正的工程师必须把它拆到这个粒度。
为什么对象刚创建时,Endpoints 会先是空的
这一课里我在对象刚创建的瞬间就看到过:
web-svc的Endpoints先是<none>
但过一会又恢复成两个后端。
这个现象很适合教学,因为它正好说明:
Service 后端不是“Pod 被创建了就算”,而是“Pod ready 了才算”。
这次 web Deployment 配了 readiness 探针。
所以控制器在构建 EndpointSlice 时,会考虑:
- Pod 是否已经 ready
这也解释了为什么生产里有时会出现:
- Pod 已经 Running
- 但 Service 还暂时没有后端
原因可能不是 Pod 不存在,而是:
- Pod 还没有通过 readiness
这条链你如果和第十课的探针结合起来看,就彻底打通了。
Endpoints 和 EndpointSlice 到底是什么关系
你在很多教程里仍然会看到 Endpoints,但现代 Kubernetes 更推荐关注:
EndpointSlice
原因是:
Endpoints是老对象- 大规模后端时扩展性较差
EndpointSlice更适合分片、扩展和多协议场景
这次实验里你可以同时看到两者:
Endpoints/web-svcEndpointSlice/web-svc-gmbrv
它们表达的是同一组后端事实,只是形式不同。
生产里很多组件,包括 kube-proxy,本质上都越来越依赖 EndpointSlice。
所以你以后排障时,建议优先看:
kubectl get endpointslices -n <ns>
DNS 成功,不代表服务一定通
这是这节课最重要的判断句之一。
我们已经用真实样本证明了三种不同状态:
状态 1:DNS 成功,Service 也通
样本:
web-svc
状态 2:DNS 成功,但 Service 无后端
样本:
web-no-endpoints
状态 3:DNS 成功,Service 也有后端,但端口错
样本:
web-wrong-port
所以你以后绝不能把:
- “域名能解析”
等同于:
- “服务一定正常”
DNS 只回答:
这个名字解析成什么地址。
它不回答:
- 这个地址有没有后端
- 后端端口对不对
- 应用协议是否真的正常
作为专家,你必须知道的 Service 数据平面变体
这套集群里是 iptables,但你不能把 Service 理解成“永远就是 iptables”。
现实里常见几种实现:
iptables
特点:
- 普遍
- 简单
- 规则量大时可读性和性能一般
IPVS
特点:
- 更偏内核负载均衡
- 大规模 Service 时更高效
nftables
特点:
- 新一代 Linux 包过滤框架
- 某些新版本环境会倾向它
eBPF / Cilium
特点:
- 把 Service 转发做进更底层的数据平面
- 可观测性和性能都可能更好
- 但理解门槛更高
所以真正的专家口径应该是:
Service 语义稳定,但数据平面实现可以变化。
最后给你一条标准排障路径
以后遇到 Service / DNS 问题,尽量按这个顺序查。
第一步:先看 Pod 内 DNS 配置
kubectl exec -n <ns> <pod> -- cat /etc/resolv.conf
回答:
- 用哪个 nameserver
- 搜索域是什么
ndots是多少
第二步:查名字能不能解析
kubectl exec -n <ns> <pod> -- nslookup <name>
kubectl exec -n <ns> <pod> -- nslookup <fqdn>
最好优先查 FQDN,减少搜索域带来的噪音。
第三步:查 Service 自己怎么定义
kubectl get svc -n <ns> <name> -o wide
重点看:
selectorporttargetPorttypeClusterIP
第四步:查后端成员表
kubectl get endpoints,endpointslices -n <ns>
这里能直接把问题切成两半:
- 后端为空
- 后端存在但行为异常
第五步:查 Pod 是否 Ready、端口是否真在监听
kubectl get pod -n <ns> -o wide
kubectl logs -n <ns> <pod>
kubectl exec -n <ns> <pod> -- netstat -lnt
第六步:必要时下沉到节点
iptables-save
ipvsadm -Ln
具体查什么,取决于集群数据平面实现。
如果像这套集群一样是 iptables,你就应该去找:
KUBE-SERVICESKUBE-SVC-*KUBE-SEP-*
这节课你必须真正掌握的结论
- Service 解决的是给动态 Pod 集合提供稳定入口,不是保证应用一定健康。
- Service 生效依赖 Service 对象、EndpointSlice、kube-proxy、CoreDNS 多个组件协作。
- 普通 Service 返回一个 VIP,Headless Service 返回一组 Pod IP。
- ClusterIP 通常是虚拟 IP,没有绑在真实网卡上。
- DNS 成功不代表服务一定通,因为问题可能出在后端成员表或端口映射。
Endpoints空,优先查 selector 和 readiness。Endpoints不空但仍然拒绝连接,优先查targetPort和容器监听端口。- 这套集群里 kube-proxy 实际运行在
iptables模式,但别把实现细节当成 Service 唯一语义。
你现在具备了什么能力
学完这一课,你已经能把下面这些高频生产问题分层看待:
- DNS 是否正常
- Service 对象是否正确
- EndpointSlice 是否正确生成
- kube-proxy 是否正确编程数据平面
- 应用监听端口是否和 Service
targetPort一致
这就是从“会创建 Service”走向“会排 Service 事故”的分水岭。