Repository Reading Site
本轮操作记录:Service、EndpointSlice、CoreDNS 与服务发现排障实验
这一轮我要把下面几件最核心的事做成真实证据: 1. Pod 内 DNS 请求到底发给谁 2. 普通 Service 和 Headless Service 的解析结果为什么不同 3. `Endpoints` / `EndpointSlice` 是怎样生成的 4. kube-proxy 怎样把 ClusterIP 转成 Pod IP 5. 为什么“DNS 成功但
本轮操作记录:Service、EndpointSlice、CoreDNS 与服务发现排障实验
本轮目标
这一轮我要把下面几件最核心的事做成真实证据:
- Pod 内 DNS 请求到底发给谁
- 普通 Service 和 Headless Service 的解析结果为什么不同
Endpoints/EndpointSlice是怎样生成的- kube-proxy 怎样把 ClusterIP 转成 Pod IP
- 为什么“DNS 成功但服务仍然不通”
- 为什么“无 Endpoints”和“targetPort 错误”是两类完全不同的故障
Step 1:先核对这套集群的服务发现底座
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n kube-system get svc kube-dns -o wide
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n kube-system get deploy coredns -o wide
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n kube-system get cm coredns -o yaml | sed -n '1,220p'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n kube-system get cm kube-proxy -o yaml | sed -n '1,260p'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n kube-system logs ds/kube-proxy --tail=120
为什么要先查这一层
Service / DNS 是否正常,不是只看业务对象就够了。
你至少要先确认:
kube-dns这个 Service 在不在- CoreDNS Pod 是否健康
- CoreDNS 的 Corefile 怎么写
- kube-proxy 到底用什么数据平面实现
我看到的结果
kube-dns Service
- ClusterIP:
10.96.0.10 - 端口:
53/UDP、53/TCP
CoreDNS
- Deployment
2/2ready - 两个 Pod 都运行在控制面节点
us480851516617a
CoreDNS Corefile 关键项
kubernetes cluster.local in-addr.arpa ip6.arpaforward . /etc/resolv.confcache 30loadbalance
kube-proxy
ConfigMap 里 mode 是空字符串,但日志明确显示:
Using iptables proxyUsing iptables Proxier
原理解释
这一步建立三条关键认知:
- Pod 内默认 DNS 并不是“直接去公网 DNS”,而是先打给集群里的
kube-dnsService。 - CoreDNS 对
cluster.local域名走 Kubernetes 插件逻辑,对其他域名再 forward。 - 这套集群里 Service 数据平面由 kube-proxy 用
iptables实现。
Step 2:创建实验命名空间和四类对象
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl apply -f manifests/13-service-dns
本轮创建了什么
命名空间
service-lab
正常工作负载
- Deployment
web - 2 个副本
- 容器监听
8080 - readiness 探针命中
/
正常 Service
web-svcport: 80targetPort: 8080
Headless Service
web-headlessclusterIP: None
故障 Service 1:无 Endpoints
web-no-endpoints- selector 故意多了不存在的
role: missing
故障 Service 2:端口错误
web-wrong-port- selector 正常
- 但
targetPort故意写成9090
调试客户端
- Pod
dns-client - 固定调度到
wk-1
为什么这样设计实验
因为我要同时构造三类非常常见、但容易混淆的真实现象:
- Service 正常转发
- DNS 正常,但 Service 没有后端
- DNS 正常,Service 也有后端,但端口映射错了
如果这三类现象你能分清,生产里排 Service 问题就已经过了一大关。
Step 3:观察对象刚创建时的瞬时状态
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n service-lab get all,endpoints,endpointslices
我看到的结果
刚创建完的瞬间,web-svc 显示:
endpoints/web-svc <none>
为什么这个瞬间很重要
因为它能直接说明:
Service 后端不是“Pod 存在就算”,而是“Pod ready 了才会进入成员表”。
这次 web Deployment 配了 readiness 探针,所以刚起容器时短暂没有 Endpoints 是正常现象。
原理解释
这一点和第十课的探针必须联动理解:
- Pod Running
- 不等于 Pod Ready
- Pod 不 Ready
- Service 就可能暂时没有后端
很多新手看到 <none> 就以为 selector 写错了,其实有时只是 Pod 还没通过 readiness。
Step 4:等待工作负载就绪,再看成员表
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n service-lab rollout status deployment/web --timeout=180s
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n service-lab wait --for=condition=Ready pod/dns-client --timeout=180s
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n service-lab get pod,svc,endpoints,endpointslices -o wide
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n service-lab get endpointslices -o yaml | sed -n '1,260p'
我看到的关键结果
Pod
dns-client:10.244.147.95,节点wk-1web-76d9b6dc8-2w9lt:10.244.147.94,节点wk-1web-76d9b6dc8-dc6p8:10.244.119.193,节点us590068728056
Service
web-svc:ClusterIP10.108.57.14web-headless:ClusterIPNoneweb-no-endpoints:ClusterIP10.97.187.150web-wrong-port:ClusterIP10.98.244.141
Endpoints
web-svc->10.244.119.193:8080,10.244.147.94:8080web-headless->10.244.119.193:8080,10.244.147.94:8080web-no-endpoints-><none>web-wrong-port->10.244.119.193:9090,10.244.147.94:9090
原理解释
这一步非常关键,因为它把故障切成了两层。
web-no-endpoints
说明:
- selector 没选到后端
- 或者后端都不 ready
web-wrong-port
说明:
- selector 选中了后端
- 但 Service 生成出来的目标端口是错的
这两种现象在用户眼里可能都叫“Service 不通”,但工程上根因完全不同。
Step 5:从 Pod 内看 DNS 配置
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n service-lab exec dns-client -- cat /etc/resolv.conf
我看到的结果
search service-lab.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
命令解释
这个命令看的是容器里的真实 DNS 客户端配置,不是宿主机配置。
它能直接回答:
- 当前 Pod 请求发给谁
- 搜索域是什么
- 短域名会怎样补全
原理解释
这一结果说明:
dns-client的 DNS 请求先发给10.96.0.1010.96.0.10正是kube-dnsService- 查询短名时会先按
service-lab.svc.cluster.local等搜索域补全 ndots:5会让短名查询夹杂一些搜索失败的噪音
所以以后你在 Pod 里执行 nslookup web-svc,如果看到几行 NXDOMAIN,不要慌。
先区分:
- 最终目标名字是否成功解析
- 噪音是不是搜索域扩展造成的
Step 6:验证正常 Service 与 Headless Service 的解析差异
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n service-lab exec dns-client -- nslookup web-svc.service-lab.svc.cluster.local
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n service-lab exec dns-client -- nslookup web-headless.service-lab.svc.cluster.local
我看到的结果
web-svc
Name: web-svc.service-lab.svc.cluster.local
Address: 10.108.57.14
web-headless
Name: web-headless.service-lab.svc.cluster.local
Address: 10.244.147.94
Name: web-headless.service-lab.svc.cluster.local
Address: 10.244.119.193
原理解释
这一步把普通 Service 和 Headless Service 的差异钉死了。
普通 Service
DNS 返回:
- 一个稳定的虚拟 IP
Headless Service
DNS 返回:
- 一组真实 Pod IP
这就是为什么 Headless Service 更适合:
- StatefulSet
- 客户端自己感知多个后端
- 需要稳定发现每个成员的场景
Step 7:验证正常 Service 的负载分发
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n service-lab exec dns-client -- \
sh -c 'for i in 1 2 3 4 5 6; do echo "--- request-$i"; wget -qO- http://web-svc; done'
我看到的结果
返回内容交替来自两个 Pod:
pod=web-76d9b6dc8-2w9ltpod=web-76d9b6dc8-dc6p8
同时还能看到:
- 一个 Pod 在
wk-1 - 一个 Pod 在
us590068728056
命令解释
这里用循环多次 wget 的目的不是图省事,而是为了验证:
- Service 是否真的把流量分发给多个后端
每次 wget 都会新建一个 TCP 连接,所以更容易看到连接级别的随机分发。
原理解释
这一步证明:
- Service 不只是“能解析”
- 它确实在做四层转发
但要注意,负载分发发生在连接层,不是 HTTP 语义层。
如果应用长连接很多,观察到的分布可能和你直觉不一样。
Step 8:验证“DNS 成功但无 Endpoints”的故障
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n service-lab exec dns-client -- \
nslookup web-no-endpoints.service-lab.svc.cluster.local
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n service-lab exec dns-client -- \
sh -c 'wget -T 2 -O- http://web-no-endpoints'
我看到的结果
DNS 查询
Name: web-no-endpoints.service-lab.svc.cluster.local
Address: 10.97.187.150
访问结果
Connecting to web-no-endpoints (10.97.187.150:80)
wget: can't connect to remote host (10.97.187.150): Connection refused
原理解释
这一步是最容易让人误判的。
因为你已经看到:
- 名字能解析
- Service 也有 ClusterIP
但仍然连接失败。
真正原因是:
web-no-endpoints的成员表为空
这证明:
DNS 层没有问题,问题在 Service 没有后端。
以后你遇到这类现象,第一排查动作应该是:
kubectl get endpoints,endpointslices -n <ns>
而不是先去改 CoreDNS。
Step 9:验证“Endpoints 有值但 targetPort 错误”的故障
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n service-lab exec dns-client -- \
nslookup web-wrong-port.service-lab.svc.cluster.local
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n service-lab exec dns-client -- \
sh -c 'wget -T 2 -O- http://web-wrong-port'
我看到的结果
DNS 查询
Name: web-wrong-port.service-lab.svc.cluster.local
Address: 10.98.244.141
访问结果
Connecting to web-wrong-port (10.98.244.141:80)
wget: can't connect to remote host (10.98.244.141): Connection refused
而成员表里又明确是:
10.244.119.193:909010.244.147.94:9090
原理解释
这说明:
- 不是 selector 问题
- 不是 DNS 问题
- 而是 Service 把流量转发到了错误端口
也就是:
- Pod 真实监听
8080 - Service 却把它写成
9090
这类问题本质上是:
targetPort- 容器监听端口
- 应用启动参数
之间不一致。
Step 10:下沉到节点,看 kube-proxy 实际写下去的 iptables
实际命令
ssh root@154.219.104.66 \
'sudo iptables-save | grep -n -C 2 -E "service-lab/web-svc|service-lab/web-no-endpoints|service-lab/web-wrong-port"'
ssh root@154.219.104.66 \
'sudo iptables-save | grep -n -C 2 10.108.57.14'
ssh root@154.219.104.66 \
'sudo iptables-save | grep -n -C 2 10.97.187.150'
ssh root@154.219.104.66 \
'sudo iptables-save | grep -n -C 2 10.98.244.141'
为什么要下沉到宿主机
因为到这一步,我们不想再停留在“应该是这样”的层面,而是要直接看:
- 内核规则到底怎么写的
这一步是很多高级排障和面试答题的分水岭。
我看到的关键结果
web-svc
可以看到:
10.108.57.14命中KUBE-SVC-HCMWRNUQQOC4KVG2- 然后按
0.5概率转发到:10.244.119.193:808010.244.147.94:8080
web-no-endpoints
直接看到:
"service-lab/web-no-endpoints:http has no endpoints" -j REJECT
web-wrong-port
可以看到:
10.98.244.141命中KUBE-SVC-P4EMT4RMUMWL2DCM- 再转发到:
10.244.119.193:909010.244.147.94:9090
原理解释
这一组规则把三种状态讲得非常透:
- 正常 Service:ClusterIP -> Service chain -> Endpoint chain -> PodIP:正确端口
- 无后端 Service:ClusterIP -> 直接 REJECT
- 错端口 Service:ClusterIP -> 正常 DNAT,但 DNAT 到错误端口
这就是为什么“同样都是连接拒绝”,底层原因可以完全不同。
Step 11:证明 ClusterIP 没有绑在网卡上
实际命令
ssh root@154.219.104.66 'ip addr | grep 10.108.57.14 || true'
ssh root@154.219.104.66 'ip addr | grep 10.97.187.150 || true'
ssh root@154.219.104.66 'ip addr | grep 10.98.244.141 || true'
我看到的结果
三个命令都没有任何输出。
原理解释
这就是最直接的证据:
Service ClusterIP 并不是一张真实网卡上的地址。
它之所以能工作,是因为请求被内核规则拦截并改写,不是因为某个进程真正 bind 了这个 IP。
以后如果有人问你:
ClusterIP 明明不存在于网卡上,为什么还能访问?
你就知道要从:
- 虚拟 IP
- kube-proxy
- DNAT
- 内核转发表
这条线回答。
Step 12:整理本轮最重要的结论
结论 1:DNS 和 Service 转发是两条链
DNS 负责:
- 名字 -> 地址
Service 数据平面负责:
- 地址 -> 后端 Pod
所以 DNS 成功不等于 Service 一定正常。
结论 2:Headless Service 和普通 Service 的核心差别是 DNS 返回内容
- 普通 Service 返回一个 VIP
- Headless 返回多个 Pod IP
结论 3:Endpoints 空和 targetPort 错是两类不同故障
前者重点查:
- selector
- labels
- readiness
后者重点查:
targetPort- 容器监听端口
- 应用启动参数
结论 4:这套集群的 Service 数据平面是 iptables
而且我们已经拿到了真实规则,不再只是停留在教材概念。
本轮最值得记住的命令与用途
kubectl exec -- cat /etc/resolv.conf
看 Pod 内真实 DNS 配置。
kubectl exec -- nslookup <name>
看名字是否能解析。
kubectl get svc,endpoints,endpointslices
看 Service 定义和后端成员表。
kubectl exec -- wget http://<svc>
看解析之后,真实连接是否成功。
iptables-save
看 kube-proxy 最终如何在节点上实现转发。
ip addr
验证 ClusterIP 是否真的绑定在网卡上。
这一课给你的专家能力提升点
如果你把这一轮实验真正吃透,你已经具备了下面这些生产级基本功:
- 能把 DNS 问题和 Service 转发问题拆开
- 能读
EndpointSlice判断后端成员是否正确 - 能区分“没后端”和“后端端口错”这两类事故
- 能下沉到节点看 kube-proxy 规则,不再只停留在
kubectl get层面
这就是从“会用 Service”进入“会排 Service / DNS 事故”的关键一步。