Service 卖的不是 IP,是「名字」
一句话总结
Pod IP 是每次重启都会改变的临时地址。Service 在其上提供不变的名称与虚拟 IP,并通过 label selector 持续更新后端 Pod。因此 Service 的本质不是 load balancing,而是间接寻址(indirection)。
为什么需要它
Pod 随时会死亡重建,每次 IP 都会不同。frontend 若直接知道 backend Pod IP,backend 每次重启都必须修改 frontend。scale out 后还有多个 IP,谁来管理列表也成了问题。
Service 用“一层名称”解决它。client 只知道 backend,Kubernetes 管理该名称实际解析到哪些 IP。名称由 DNS 解析:同一 namespace 使用 backend,其他 namespace 使用 backend.other-ns,完整形式为 backend.other-ns.svc.cluster.local。
没有间接地址时,其价值最痛切。家庭实验室从 3 节点扩到 7 节点,并把控制平面增加到 3 台后,打开 kubeadm-config 看到如下内容。
controlPlaneEndpoint: 10.0.0.120:6443 # ← cp-1 의 물리 IP
这里不是 VIP 或 DNS,而是第一台节点的真实 IP。cp-1 死亡后,etcd quorum 仍以 2/3 正常,其余 apiserver 进程也活着;但 7 台 worker 的 kubelet 与全部 kubeconfig 只知道这个地址,陷入无人能找到入口的状态。apiserver 证书 SAN 又没有其他控制平面 IP,直接连接也会因 TLS 验证失败。Service 对 Pod 做的正是这件事——后端无论怎样变化,client 所知名称保持不变。
它如何运作
Service spec 最常写错的是三个端口。
| 字段 | 谁使用 | 含义 |
|---|---|---|
port |
client | Service ClusterIP 上开放的端口 |
targetPort |
Pod | 实际容器监听端口(数字或端口名) |
nodePort |
集群外部 | 所有节点开放的端口(30000~32767) |
在 targetPort 使用容器端口的名称(targetPort: api),即使各容器实际端口号不同,也无需修改 Service,这就是 named port 的要点。
Service 类型逐层叠加:ClusterIP(仅集群内部)→ NodePort(ClusterIP + 节点端口)→ LoadBalancer(NodePort + 外部 LB)。此外还有两个没有 selector 的特殊类型。ExternalName 只创建 DNS CNAME;headless(clusterIP: None)不创建虚拟 IP,DNS 直接返回 Pod IP。
headless 与 StatefulSet 配套。把 headless Service 指定为 StatefulSet 的 serviceName,每个 Pod 都会获得独立 DNS 名称。
<파드이름>.<서비스이름>.<네임스페이스>.svc.cluster.local
cache-0.cache-hs.ckad-net.svc.cluster.local
数据库复制等具有“0 号是 primary”角色的 workload 需要独立地址,而不是 load balancing,因此使用这种组合。
Ingress 位于 L7,从一个入口按host与path分流到多个 Service。pathType 很重要:Prefix 按 path segment 前缀匹配(/api 也匹配 /api/users),Exact 完全匹配。只有 Ingress 对象什么也不会发生,必须有 Ingress controller 才会真正路由。
NetworkPolicy 默认为允许。没有 policy 时所有 Pod 可相互通信;一旦某个 Pod 匹配任一 policy,它就进入白名单模式,只允许明确列出的流量。policyTypes 只写 Ingress 不会控制出站流量。policy 只会相加——多个 policy 同时匹配时,允许项取并集。
在实际工作中会遇到的情况
家庭实验室以 kubeadm 1.34 + Cilium 1.20 重建时完全没有安装 kube-proxy(--skip-phases=addon/kube-proxy),随后实测 Service 是否工作。
kube-proxy 파드 수: 0
노드의 iptables KUBE- 체인 개수: 0
使用 kube-proxy 的集群会按服务数量产生数十至数百条 KUBE-SERVICES、KUBE-SVC-*、KUBE-SEP-* 链,这里为 0;frontend 和 backend 则直接保存在 eBPF map 中。
SERVICE ADDRESS BACKEND ADDRESS (REVNAT_ID) (SLOT)
10.96.0.10:53/TCP (1) 10.244.0.150:53/TCP (4) (1)
10.96.0.1:443/TCP (1) 10.0.0.120:6443/TCP (1) (1)
这里要学到的是:**Service 抽象保持不变,实现却可以彻底替换。**manifest 一个字都没变,Pod 使用服务名连接并获得 HTTP 200 的验证也照样通过。
NetworkPolicy 侧也有实测。Cilium L7 policy 在相同 IP 和端口上按 HTTP method 分流。
정책 적용 전: GET / → 200, POST / → 200
정책 적용 후: GET / → 200, POST / → 403
标准 NetworkPolicy 只能表达 L3/L4(Pod selector、port);method 与 path 属于 CNI 扩展(CiliumNetworkPolicy)或 Service Mesh。CKAD 范围是标准 NetworkPolicy,但应了解标准和扩展的边界。
最后还有一次 selector 事故。Service selector 与 Pod label 哪怕只差一个字符,endpoint 也会以空内容创建,既无错误也无 event。kubectl get endpoints <이름> 显示 <none> 时,十有八九是 selector 拼错。
后续实验要做什么
在 ckad-net namespace 创建 ClusterIP 与 NodePort,区分指定 port/targetPort/nodePort;用 named port 连接 targetPort;通过 headless Service + StatefulSet 确认 Pod 独立 DNS 规则;用 Ingress 编写 host/path routing;再用 NetworkPolicy 创建只允许 frontend 到 backend 的白名单。**本环境没有真实 CNI data plane,所以无法确认 policy 是否真的阻断 packet。**目标是准确编写对象,原理通过测验确认。