上一篇文章我们终于把 Deployment 玩明白了:副本、滚动更新、回滚、标签选择器,都在集群里挨个验证过。正当我准备把一组 Pod 的访问地址写进前端配置时,问题来了——用 kubectl get pod -o wide 查到的 Pod IP,第二天再看全变了。有个 Pod 所在节点出了故障,被调度到另一台节点重新拉起,IP 从 10.244.1.6 变成了 10.244.2.15。如果前端服务要调用后端三个副本,这个 IP 写谁都不靠谱。
在 Kubernetes 里,任何依赖具体 Pod IP 的方案都是脆弱的,因为 Pod 本身就是“一次性的”。这个问题的标准解法就是 Service,而 Service 里最基础、最常用、同时也是默认值的 Type,就是 ClusterIP。这篇咱们就专门把 ClusterIP 讲透:它到底是什么、流量怎么进去的、怎么创建一个能用的、以及遇到访问不通时怎么排。适合已经能熟练操作 Pod 和 Deployment、但对服务发现还处于“知其然不知其所以然”阶段的同学。
1. Pod IP 天生就是“一次性”的:Service 存在的底层原因
1.1 IP 漂移不是故障,是 Kubernetes 的正常状态
很多刚接触 Kubernetes 的同学,遇到 Pod 重建后 IP 变了,第一反应是“我是不是配置错了”。实际上这不仅不是问题,反而是 K8s 的设计预期。Pod 就像临时容器,节点故障、手动删除、滚动更新、资源回收,都会触发重建,重建后的 Pod 会拿到一个全新的 IP,和之前的毫无关系。
具体来说,Pod 的 IP 由 CNI 插件(Flannel、Calico 等)从节点的 Pod CIDR 网段里分配。比如我用 kubeadm 搭的 v1.26.0 集群,Pod 网段是 10.244.0.0/16,每个节点划分一个 /24 子网,Pod 被调度到哪个节点,就从那个节点的子网里拿地址。这带来一个结果:Pod 的 IP 和它所在的节点强相关,而 Pod 在节点之间的迁移又是常态,所以 IP 随时可能变。
如果只是 IP 变化还好说,更要命的是 Deployment 滚动更新时,旧的 ReplicaSet 缩到 0,新的 ReplicaSet 从 0 拉起,整个过程 Pod 的名字和 IP 全部换了一遍。靠 IP 访问后端,相当于在一个随时会变动的人员名单上写死了一个工位号,人走了工位就空了。
1.2 光有 Deployment 还不够:稳定名字、稳定入口、负载均衡缺一不可
这时候你可能会说,Deployment 不是给 Pod 提供了一个稳定的名字吗?比如 web-6d8b9ff9-tk4xr。这个名字确实有规律,但它只对单个副本稳定,副本被删除重建后名字会变成 web-6d8b9ff9-xxxx2,而且 Deployment 一扩容就是 3 个、5 个副本,你不可能把每个副本的唯一名字都写进调用方配置。
就算你写了个脚本动态查询 Pod 名字和 IP,还有一个问题绕不开:多副本的负载均衡怎么做?轮询、随机、按客户端 IP 哈希?谁来维护可用后端列表?某个 Pod 因为 OOM 或探针失败变得不健康,调用方怎么知道要避开它?这些逻辑如果每个微服务都自己实现一遍,那 Kubernetes 的“平台能力”就白白浪费了。
Service 这个抽象,解决的核心问题就是“一组提供相同服务的 Pod,作为整体怎么被稳定地找到”。它不关心具体哪个 Pod,只关心你定义好的标签选择器:只要 Pod 的标签匹配,就会被纳入后端列表;不健康了,会被自动剔除;扩容了,自动加进来。调用方从头到尾只需要记住一个名字。
1.3 Service 的本质:一组 Pod 的稳定“服务门牌号”
用一句话总结:Service 是 Kubernetes 里对“一组提供相同服务的 Pod”的稳定访问入口。它本身不创建 Pod,也不直接管理 Pod,它通过标签选择器动态关联一组 Pod,这组 Pod 可以随时扩缩容、滚动升级,调用方完全不需要感知。
Service 有一个 Type 字段,决定这个入口暴露到哪个范围。Kubernetes 里常见的有四种:ClusterIP、NodePort、LoadBalancer、ExternalName。其中 ClusterIP 是默认值,也是最核心的一类,NodePort 和 LoadBalancer 本质上都是建立在 ClusterIP 之上的扩展。换句话说,把 ClusterIP 理解透了,后面两种类型基本就是加一层“对外暴露”的壳。这篇我们先把地基夯实。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. ClusterIP 的原理:虚拟 IP、kube-proxy 与 Endpoints 的三角关系
2.1 ClusterIP 是一个“不存在的 IP”,但路由规则真实存在
ClusterIP 这个 IP 有点特殊:你不会在任何节点的 ip addr 输出里看到它,也没有哪个网卡绑定它。它由 API Server 在 Service 创建时从集群的 service CIDR 网段里分配,比如默认的 10.96.0.0/12。它只存在于 iptables/ipvs 规则、DNS 记录和各种客户端配置里,没有任何对应的实体设备。
这有点像公司前台的总机号码:总机本身不接电话,但它背后对应着一组随时变化的坐席列表。外部拨打总机,电话会被转接到任何一个空闲的坐席;ClusterIP 做的是类似的事——访问 ClusterIP:Port,流量被转发到任意一个健康的 Pod 的 targetPort。
也正因为它是虚拟 IP,它天然不会“漂移”:只要 Service 不删除,这个 IP 一直有效。哪怕后端 Pod 全挂了再拉起,ClusterIP 也不会变。这就是“稳定入口”这个承诺的物理基础。
注意:不要在宿主机或集群外的机器上 ping ClusterIP,也不要在本地直接 curl 它。ClusterIP 默认只在集群网络内部路由,集群外不可达——这是它的设计边界,不是故障。
2.2 kube-proxy 和它的两种核心模式:iptables 与 ipvs
虚拟 IP 上的流量怎么被转发到具体 Pod?答案在 kube-proxy 这个组件里。kube-proxy 以 DaemonSet 方式运行在每个节点上,它持续 watch API Server 中 Service 和 Endpoints 的变化,然后把变化转换成节点上真实的转发规则。
历史上出现过三种实现方式:
- userspace 模式:最早的实现,由 kube-proxy 进程本身做代理,性能差,现在已经基本没人用了。
- iptables 模式:默认模式。kube-proxy 为每个 Service 生成一组 iptables 规则,当数据包的目标地址是
ClusterIP:Port时,通过 DNAT 规则改写为某个后端 Pod 的IP:Port。 - ipvs 模式:kube-proxy 把规则写入内核的 IPVS 表,由内核态完成负载均衡,支持 rr、wrr、lc、sh 等更多调度算法,性能更好,适合大规模集群。
iptables 模式的处理流程可以这样理解:某个 Pod 发起请求,目标地址是 ClusterIP,数据包路由到宿主机后,命中 KUBE-SVC-XXXX 链,根据规则随机 DNAT 到某个后端 Pod IP,返回时再把源地址还原。因为选择是随机的,所以多次访问同一个 ClusterIP,可能每次落到不同的后端 Pod 上。
ipvs 模式依赖宿主机内核加载了 ip_vs 相关模块,如果不满足会回退到 iptables。刚开始学习阶段,iptables 模式完全够用,不建议一上来就折腾 ipvs。想查当前 kube-proxy 用的哪种模式,可以看它的启动参数:
bash复制kubectl -n kube-system get ds kube-proxy -o yaml | grep -A2 'command:'
参数里能看到 --proxy-mode=iptables 或 --proxy-mode=ipvs 的字样。大多数默认集群都在 iptables 模式。
2.3 Endpoints:Service 和后端 Pod 之间的自动通讯录
Service 怎么知道哪些 Pod 是后端?靠 selector。你在 Service 的 spec 里写 selector: app: web,API Server 就会持续扫描带这个标签的 Pod,并把它们的 IP:Port 写入一个叫 Endpoints 的对象里。Endpoints 的名字与 Service 同名,可以直接查看:
bash复制kubectl get endpoints web
如果 Service 有三个副本,Endpoints 里会有三条记录;某个副本 readiness 探针失败或被删除,Endpoints 会自动少一条;新副本 Ready 了,Endpoints 会自动加上。这个同步几乎是实时的,不需要你手工干预。
从数据链路看,Service 本身不直接连 Pod,它只是“通讯录”的发起方,真正的转发表是 Endpoints + kube-proxy 规则。所以排查 Service 问题时,第一件事永远是看 Endpoints 有没有内容。如果 Endpoints 为空,Service 写得再漂亮,流量也是进黑洞。
2.4 集群 DNS:ClusterIP 之外的第二个稳定入口
ClusterIP 确实是稳定的,但你不会每次都去 kubectl get svc 查 IP 对吧。集群里的 CoreDNS 会为每个 Service 自动创建一条 DNS 记录,格式是 <service-name>.<namespace>.svc.cluster.local.。
在同一个命名空间里,其他 Pod 可以直接用 Service 名字访问,比如 curl http://web:80;跨命名空间则写全一点,比如 curl http://web.default.svc.cluster.local:80。这样服务发现就不需要硬编码 IP,也不需要额外部署注册中心,Kubernetes 原生的服务发现在这一层就闭环了。
这里有个值得注意的区别:普通 ClusterIP Service 的 DNS 记录返回的是 Service 的虚拟 IP;如果某个 Service 设置了 clusterIP: None(即 Headless Service),DNS 记录返回的就是所有后端 Pod 的真实 IP 列表。这是两种完全不同的语义,后面第 4 节会展开。
3. 动手操作:写一个带 hostname 的 Deployment,再挂一个 ClusterIP Service
3.1 准备一个能返回自身 hostname 的测试应用
为了直观验证负载均衡,我们需要一个能在响应里暴露自己身份的服务。Nginx 默认首页不会显示主机名,我们可以用启动命令在容器启动时把 hostname 写进 index.html。
yaml复制apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
command: ["/bin/sh", "-c"]
args:
- |
echo "hostname: $(hostname)" > /usr/share/nginx/html/index.html
nginx -g 'daemon off;'
保存为 deployment-http-echo.yaml,然后执行:
bash复制kubectl apply -f deployment-http-echo.yaml
kubectl get pod -l app=web -o wide
等三个 Pod 都 Running 之后,每个 Pod 的首页内容就是 hostname: web-xxx。这种“能暴露自身身份”的后端,后面测试负载均衡时你会直观看到流量到底分配给了谁。
3.2 Service YAML 逐字段拆解
接下来创建一个 Service,类型指定为 ClusterIP:
yaml复制apiVersion: v1
kind: Service
metadata:
name: web
spec:
type: ClusterIP
selector:
app: web
ports:
- name: http
port: 80
targetPort: 80
每个字段说明如下:
type: ClusterIP:这个字段其实可以省略,因为 ClusterIP 就是默认值。但系列文章里写出来更明确。selector.app: web:必须和 Deployment 里 Pod 的 labels 完全一致。如果 Pod 标签写的是app: nginx,而 Service 写app: web,Endpoints 就是空的。port: 80:Service 对外监听的端口,客户端访问的入口。targetPort: 80:流量转发到 Pod 时的容器端口。可以是数字,也可以是容器端口名称(比如写http)。如果省略 targetPort,默认与 port 相同。protocol:默认 TCP,按需写 UDP 等。
执行创建并查看结果:
bash复制kubectl apply -f service-web.yaml
kubectl get svc web
输出里 Service 的 TYPE 一列是 ClusterIP,CLUSTER-IP 一列会有一个 10.96.x.x 的地址,具体网段取决于 apiserver 启动参数里的 service-cluster-ip-range。
3.3 三种方式验证 Service 生效
ClusterIP 只在集群内可达,所以验证一定要在“集群内部的 Pod 里”进行。我通常会拉起一个临时的 busybox Pod 作为测试客户端:
bash复制kubectl run tmp-client --rm -it --image=busybox -- sh
进入容器之后,有三种验证方式:
- 直接访问 ClusterIP:
bash复制wget -qO- http://10.96.x.x:80
会看到返回的 hostname: web-xxx。
- 用 Service 名字访问,验证 DNS 解析:
bash复制wget -qO- http://web:80
结果应该一致。注意这里我们没有写命名空间,因为 busybox Pod 和 Service 在同一个 default 命名空间,直接用短名字即可。
- 循环访问,观察负载均衡效果:
bash复制for i in $(seq 1 10); do wget -qO- http://web; echo; done
会看到 hostname 在三个后端之间随机出现,大概率不是严格的一、二、三轮流,而是随机分布。这正好印证了前面说的 iptables 模式随机选择的逻辑。
如果你在宿主机上直接
curl 10.96.x.x:80发现不通,不用慌。ClusterIP 不是给集群外用的,一定要进入集群内验证。这个误区新手必踩,而且是排障时最常见的浪费时间点。
3.4 看 Endpoints 如何跟随 Pod 变化
再开一个终端,观察 Endpoints 的实时变化:
bash复制kubectl get endpoints web -w
初始三条记录。然后执行:
bash复制kubectl scale deploy web --replicas=1
你会看到 Endpoints 秒级变成一条。再扩容回 3,Endpoints 又会自动恢复三条。
这个“自动跟随 Pod 变化更新”的能力,就是 Service 与普通负载均衡配置最大的差别:你不需要手工增删后端,只要标签匹配,它就自动纳管;Pod 不健康了,它会自动摘除。理解了这一点,后面很多疑难排障会好解得多。
4. ClusterIP 容易忽略的四个细节:会话保持、端口命名、手动 Endpoints、Headless
4.1 默认调度是“随机”不是“轮询”,粘性会话靠 sessionAffinity
很多人第一次做循环访问测试,发现结果不是严格的轮流,会有点意外。这其实是 iptables 模式的正常表现:DNAT 规则用 --random,每次新建连接从可用后端里随机挑一个,连接一旦建立,后续流量都维持到同一个后端,直到连接断开。这和负载均衡里常说的 round-robin 不是一回事。
如果业务有会话保持需求,要求同一个客户端 IP 的请求都落在同一个后端(比如本地 Session 没有外置),可以在 Service 里设置:
yaml复制spec:
sessionAffinity: ClientIP
sessionAffinityTimeoutSeconds: 10800
设置之后,kube-proxy 会基于客户端 IP 做哈希,同一个来源 IP 的连接倾向落在同一个后端。注意这个粒度是“客户端 IP”,不是“Pod IP”,而且它只对从集群内部发起的访问有意义,集群外流量根本到不了 ClusterIP。
我的习惯是:除非业务明确有会话保持需求,否则不开。开了反而容易让某个后端压力扎堆,而且出问题时很难解释为什么流量分布不均匀。
4.2 多端口 Service 的端口命名规范
如果一个 Service 要暴露多个端口,比如 80 和 443,或者一个 TCP 一个 UDP,那每个端口都必须写 name,而且名字必须符合 IANA 服务命名规范,比如 http、https、dns、metrics。
为什么要强调这个?因为像 Istio、Linkerd 这类服务网格组件,是靠端口名的 http 前缀来判断协议的,进而决定要不要做 L7 路由、熔断、故障注入。名字不规范或缺失,网格层会直接跳过这个端口。示例:
yaml复制spec:
type: ClusterIP
selector:
app: my-app
ports:
- name: http
port: 80
targetPort: 8080
- name: https
port: 443
targetPort: 8443
多端口时还有一个坑:同一份配置里所有端口名必须唯一,重复会导致创建报错。另外,单端口时可以省略 name,但一旦要加第二个端口,Kubernetes 会要求你补齐所有端口的 name,否则校验不通过。
4.3 没有 Selector 的 Service:把集群外服务“包装”进集群
前面说 Service 靠 selector 自动生成 Endpoints,但有一种情况是故意不写 selector:把集群外部的服务伪装成集群内部服务。典型场景是数据库跑在云上的 RDS,或者某个老服务还跑在集群外的虚拟机上。
做法是创建一个和外部服务同名的 Service,再手动创建一个同名 Endpoints,把外部 IP:Port 填进去:
yaml复制apiVersion: v1
kind: Service
metadata:
name: external-db
spec:
type: ClusterIP
ports:
- port: 3306
targetPort: 3306
---
apiVersion: v1
kind: Endpoints
metadata:
name: external-db
subsets:
- addresses:
- ip: 192.168.1.100
ports:
- port: 3306
集群内访问 external-db:3306,流量会被转发到 192.168.1.100:3306。业务代码完全无感——它只知道自己连的是 external-db。
注意这种模式下没有健康检查,外部后端 IP 变了需要手工更新 Endpoints。另外,Kubernetes 1.21 之后默认启用了 EndpointSlice 机制,你手动创建 Endpoints 对象时系统会自动派生对应的 EndpointSlice,不需要额外操心,但排查时要知道这一层映射关系存在。
4.4 Headless Service:ClusterIP: None 的独立玩法
还有一种特殊的 ClusterIP:Headless Service,配置方法是在 spec 里写 clusterIP: None。它不分配虚拟 IP,也没有负载均衡,DNS 查询这个 Service 名时,返回的是所有后端 Pod 的真实 IP 列表,相当于把“选哪个后端”的决定权从 kube-proxy 交还给了客户端。
典型用途是 StatefulSet 这类有状态应用,每个 Pod 需要有持久稳定的 DNS 标识,比如 mongo-0.headless-svc.default.svc.cluster.local;或者某些数据库客户端自己实现了集群拓扑和负载均衡,不需要 Service 帮忙选后端。
容易混淆的一点是:Headless Service 的 type 仍然是 ClusterIP,只是把 clusterIP 字段设成了 None,所以类属上它还是 ClusterIP 家族的一员,只是行为完全不同。
5. 什么时候只用 ClusterIP?和 NodePort、LoadBalancer 的选型边界
5.1 三种 Type 的包含关系
把三种常用类型放在一起看,关系其实很清晰:
| Type | 是否分配 ClusterIP | 集群内访问 | 集群外访问 | 典型用途 |
|---|---|---|---|---|
| ClusterIP | 是 | 支持,且默认 | 不支持 | 内部微服务调用、中间件通信 |
| NodePort | 是 | 支持 | 通过每个节点的 IP:NodePort 访问 | 临时对外暴露、Ingress Controller 入口 |
| LoadBalancer | 是 | 支持 | 通过云厂商 LB 访问 | 公有云上直接对外提供服务 |
关键结论:NodePort 和 LoadBalancer 都是 ClusterIP 的超集,它们都会额外分配一个 ClusterIP,集群内部访问依然走 ClusterIP。NodePort 只是在 Service 上追加了“节点端口”这个外部入口,LoadBalancer 又在 NodePort 之上对接了云厂商的 LB。所以 ClusterIP 是理解所有 Service 类型的地基。
5.2 内部调用场景为什么首选 ClusterIP
我的判断标准很简单:调用方和提供方都在集群内,默认只用 ClusterIP;需要被集群外部访问但不想引入额外组件,选 NodePort,适合开发和演示;需要公网稳定入口和自动健康检查,选云厂商 LB 搭配 LoadBalancer 类型;需要 HTTP 域名、路径路由,选 Ingress,而不是直接给每个服务都上 LoadBalancer。
在企业内部,最典型的架构是:微服务之间互相调用,全部用 ClusterIP;只有最外层的网关服务通过 NodePort 或 LoadBalancer 暴露,再靠 Ingress 做域名路由。如果每个服务都直接上 LoadBalancer,云厂商 LB 数量会迅速爆炸,账单更爆炸。
另外,数据库、Redis、消息队列这类中间件,除非有外部接入需求,否则也应该用 ClusterIP 藏在集群内部。对外暴露的面越小,安全面越小。
5.3 对外暴露的常见组合:Ingress + ClusterIP
Ingress 本质是一组反向代理规则,它把外部 HTTP(S) 请求路由到集群内部的 Service 上,而这组 Service 绝大多数是 ClusterIP。Ingress Controller(Nginx Ingress、Traefik 等)部署好后,通过 NodePort 或 LoadBalancer 暴露给外部,真正干活时它访问的是各个服务的 ClusterIP。
这个分层非常关键:
- Ingress 负责 L7 路由,比如域名、路径、TLS 证书。
- ClusterIP Service 负责 L4 转发和后端实例管理。
- Pod 负责业务逻辑。
每一层职责单一,出了问题也容易定位:域名解析不对查 Ingress,IP 不通查 Service,业务报错查 Pod。理解了这一层,你对 Kubernetes 流量路径的理解基本就通了。
6. ClusterIP 访问不通?按这条链路逐层排查
6.1 第一层:Service 与 Endpoints 是否健康
ClusterIP 不通,第一反应不应该是“重装集群”,而是一层层看,从数据源头开始:
bash复制kubectl get svc web
kubectl get endpoints web
kubectl describe svc web
依次确认:Service 是否存在、Type 是否正确、CLUSTER-IP 是否为空、Endpoints 是否有内容。Endpoints 为空是最常见的原因,往下再确认两件事:
bash复制kubectl get pod -l app=web
kubectl describe pod <pod-name>
一是 Service 的 selector 与 Pod labels 是否匹配,二是 Pod 是否 Running 且 Ready。很多初学同学在 Pod 的 readinessProbe 上踩坑:Pod 明明是 Running,但探针失败,导致它被踢出 Endpoints 列表,流量自然进不去。
6.2 第二层:集群内部的 DNS 与 Pod 网络
如果 Endpoints 有内容,在集群内访问还是不通,问题可能出在 DNS 或 Pod 网络。拉起临时测试容器:
bash复制kubectl run tmp-client --rm -it --image=busybox -- sh
先看 DNS 配置:
bash复制cat /etc/resolv.conf
nameserver 一般指向 kube-system 里的 CoreDNS ClusterIP,常见的是 10.96.0.10。再测解析:
bash复制nslookup web
如果 Service 名解析不了,检查 CoreDNS 本身:
bash复制kubectl -n kube-system get pod -l k8s-app=kube-dns
如果 CoreDNS 处于 CrashLoopBackOff 状态,DNS 问题就是根因。CoreDNS 起不来通常和网络插件、节点污点有关,那是另一个话题,但排查顺序上要先排除 DNS 故障,否则后面的网络排障全是徒劳。
这里还有一个高频误区:在宿主机上执行 nslookup web 失败,于是以为 DNS 坏了。宿主机根本不在集群的 DNS 体系里,必须到 Pod 里测才有效。
6.3 第三层:kube-proxy 与节点 iptables/ipvs 规则
再往下就是 kube-proxy 层。ClusterIP 的流量转发依赖每个节点上的 kube-proxy 进程,它如果没运行或者规则没同步,ClusterIP 就是不通的。
bash复制kubectl -n kube-system get pod -o wide | grep kube-proxy
确认每个节点上的 kube-proxy 都是 Running 状态。如果某个节点的 kube-proxy 异常,对应节点上的 Pod 访问 ClusterIP 就会失败,其他节点正常——这是典型的“局部不通”现象,很容易误判为 Service 本身的问题。
想看规则有没有写入,在对应节点上执行(需要 root):
bash复制iptables -t nat -L KUBE-SERVICES -n | grep <ClusterIP>
能看到 DNAT tcp -- 0.0.0.0/0 <ClusterIP> 这类记录就说明规则在。找不到的话,多半是 kube-proxy 同步异常,可以尝试重建该节点的 kube-proxy Pod:
bash复制kubectl -n kube-system delete pod <kube-proxy-xxxxx>
它会自动重建并重新同步规则。如果是 ipvs 模式,用 ipvsadm -Ln 查看对应的虚拟服务器条目。
6.4 常见原因速查表与对应命令
把上面排查思路整理成一张速查表,建议收藏:
| 现象 | 可能原因 | 排查命令 |
|---|---|---|
| Endpoints 为空 | selector 与 Pod labels 不匹配 | kubectl get pod -l app=web |
| Endpoints 为空 | readiness 探针失败,Pod 被摘除 | kubectl describe pod <pod> |
| Pod 内 curl ClusterIP 超时 | kube-proxy 异常或规则未同步 | kubectl -n kube-system get pod -l k8s-app=kube-proxy |
| Pod 内 curl Service 名解析失败 | CoreDNS 异常 | kubectl -n kube-system get pod -l k8s-app=kube-dns |
| 宿主机 curl ClusterIP 不通 | 正常现象,ClusterIP 仅集群内部可达 | 到 Pod 内测试 |
| targetPort 错误导致转发失败 | Service targetPort 与容器端口不一致 | kubectl describe svc web |
| Pod 正常但流量不均衡 | iptables 模式随机选择,不是轮询 | 循环请求观察 hostname |
最后分享一个我的排查习惯:不管 Service 类型是 ClusterIP、NodePort 还是 LoadBalancer,我都会先把 Endpoints 列表看清楚再动手查网络。Endpoints 有内容,说明控制面已经认可这个 Service 是健康的;没有内容,后面查再多的网络规则都是白费力气。
另一个反直觉的记忆方法是:ClusterIP 更像“服务总机”而不是“服务器地址”。它自己不处理业务,只负责把电话转给正在值班的后端;值班的人可以随时换,总机号码不变。把这个模型记在脑子里,随机转发、自动剔除不健康后端、NodePort 只是加了个外部入口这些行为,就全都顺了。下一篇我们在这个基础上,把 NodePort 和 Ingress 串起来,看看流量从公网到 Pod 的完整路径到底长什么样。
