Appearance
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、端口、selector | kubectl get svc、kubectl describe svc |
| EndpointSlice | 保存真正可转发的 Pod IP 和端口 | kubectl get endpointslice |
| kube-proxy / eBPF 数据面 | 在节点上把 ClusterIP 请求转发到 Pod IP | iptables-save、ipvsadm、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.localDNS 解析成功只说明名字能指向 ClusterIP。后端 Pod 是否 Ready、能否接收请求,还要看 EndpointSlice 和 Pod 状态。
常见排查
| 现象 | 方向 |
|---|---|
| DNS 不解析 | CoreDNS Pod 状态、Pod DNS 配置、NetworkPolicy 阻拦 |
| Service 无后端 | selector 和 Pod labels 不匹配、Pod readiness 未通过 |
| 直连 Pod 通、Service 不通 | kube-proxy、EndpointSlice、Service 端口映射 |
| 同节点通、跨节点不通 | CNI、节点路由、防火墙 |