LabHub
学习 学习路径 课程

CKAD — Kubernetes 应用开发者

Service 卖的不是 IP,是「名字」

在 LabHub 中继续学习

一句话总结

Pod IP 是每次重启都会改变的临时地址。Service 在其上提供不变的名称与虚拟 IP,并通过 label selector 持续更新后端 Pod。因此 Service 的本质不是 load balancing,而是间接寻址(indirection)

概念图: 不变的名称与虚拟 IP · 间接寻址(indirection) · 第一台节点的真实 IP · 无人能找到入口

为什么需要它

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;headlessclusterIP: 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,从一个入口按hostpath分流到多个 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-SERVICESKUBE-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。**目标是准确编写对象,原理通过测验确认。