Service:Pod 的稳定访问入口
K8s 中 Pod 的生命周期是短暂的——调度、重启、扩缩容都会改变 Pod IP。如果客户端直接用 Pod IP 访问服务,Pod 一重建 IP 就变了,所有连接瞬间断裂。Service 的出现正是为了解决这个问题:它为一组 Pod 提供一个永不变化的访问入口(IP + DNS 名),客户端只需要知道 Service 的地址,不需要关心背后是哪些 Pod。
为什么需要 Service#
Pod 的 IP 不稳定,原因有三:
- Pod 重建 IP 变化——Pod 崩溃重启、节点故障后重新调度,新 Pod 分配新 IP
- 扩缩容 Pod 数量变化——HPA 扩容产生新 Pod,缩容删除旧 Pod,后端列表实时变动
- 滚动更新 Pod 替换——新旧 Pod 交替,客户端不应感知这个过程
Service 在 Pod 和客户端之间插入了一层抽象:
客户端 → Service(稳定 ClusterIP / DNS 名) → 后端 Pod(动态 IP 列表)Service 通过 label selector 关联 Pod,自动维护一个健康的后端 Pod 列表(Endpoints)。Pod 增减、IP 变化,Service 的访问地址始终不变。客户端只与 Service 交互,后端 Pod 的变动完全透明。
这和 Nginx 反向代理的思路类似,但 Service 是 K8s 内建机制,不需要额外部署代理组件。
四种 Service 类型对比#
K8s 提供四种 Service 类型,分别解决不同范围的访问需求:
| 类型 | 说明 | 适用场景 | 局限 | 端口范围 |
|---|---|---|---|---|
| ClusterIP | 分配一个集群内部可达的虚拟 IP,集群外不可访问 | 集群内部服务间调用(微服务 A → 微服务 B) | 只在集群内可达,外部无法直接访问 | 无限制(Service port 自定义) |
| NodePort | 在 ClusterIP 基础上,在每个节点上开放一个端口(30000-32767) | 开发测试环境临时暴露服务、无 LoadBalancer 的环境 | 端口范围受限且不固定;客户端需知道节点 IP;不适合生产暴露 | 30000-32767(默认) |
| LoadBalancer | 在 NodePort 基础上,向云平台申请一个外部负载均衡器(AWS ELB/阿里云 SLB) | 生产环境对外暴露 HTTP/API 服务 | 依赖云平台;每个 Service 占一个 LB,成本高;只能用云上集群 | LB 监听端口自定义 |
| ExternalName | 不做任何代理,只在 CoreDNS 里把 Service 名映射成一条 CNAME 指向外部域名 | 把集群外服务(如托管数据库 db.xxx.rds.amazonaws.com)伪装成集群内 Service 名 | 只是 DNS 别名,没有 ClusterIP、不经 kube-proxy、无 selector/Endpoints、不做负载均衡 | 无(纯 DNS 层) |
类型递进关系:LoadBalancer 包含 NodePort,NodePort 包含 ClusterIP。即:
- ClusterIP:集群内可达
- NodePort = ClusterIP + 节点端口暴露
- LoadBalancer = NodePort + 云 LB
ExternalName 是个例外,不在这条递进链上——它本质是 DNS CNAME 别名,不分配任何 IP、不走 kube-proxy,仅用于给外部服务起一个集群内的 DNS 名。
生产环境对外暴露服务通常用 LoadBalancer 或 Ingress(后续章节讲解)。NodePort 只适合临时暴露或内部测试。ClusterIP 是最常用的类型,集群内服务间调用几乎都用它。
Service YAML 示例#
ClusterIP(默认类型)#
集群内部服务间调用最常用的配置:
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
type: ClusterIP # 默认值,可省略
selector:
app: user-service # 通过 label 关联 Pod
ports:
- port: 80 # Service 暴露的端口(客户端访问端口)
targetPort: 8080 # Pod 容器监听的端口(流量转发目标)客户端在集群内访问:http://user-service:80(DNS 自动解析为 ClusterIP)。也可以用完整域名:http://user-service.default.svc.cluster.local:80。
NodePort#
在节点上开放端口,让集群外可以访问:
apiVersion: v1
kind: Service
metadata:
name: web-app
spec:
type: NodePort
selector:
app: web-app
ports:
- port: 80 # Service ClusterIP 端口(集群内访问)
targetPort: 8080 # Pod 容器端口
nodePort: 30080 # 节点上暴露的端口(集群外访问)集群外访问:http://<任意节点IP>:30080。不指定 nodePort 时 K8s 自动从 30000-32767 范围分配一个。
LoadBalancer#
云上生产环境对外暴露服务的标准方式:
apiVersion: v1
kind: Service
metadata:
name: api-gateway
spec:
type: LoadBalancer
selector:
app: api-gateway
ports:
- port: 80
targetPort: 8080创建后 K8s 向云平台申请一个 LB,kubectl get svc api-gateway 的 EXTERNAL-IP 列会显示 LB 的外部 IP。集群外访问:http://<EXTERNAL-IP>:80。
LoadBalancer 的
EXTERNAL-IP显示<pending>表示 LB 还在创建中,通常几秒到几分钟。本地集群(如 minikube)没有云 LB 支持,EXTERNAL-IP 会一直 pending。
Endpoints 与 kube-proxy#
Endpoints:Service 后端 Pod 列表#
Service 的 selector 找到匹配 label 的 Pod 后,K8s 自动创建一个同名的 Endpoints 对象,记录所有健康 Pod 的 IP 和端口:
kubectl get endpoints user-service
# NAME ENDPOINTS AGE
# user-service 10.244.1.5:8080,10.244.2.3:8080,10.244.3.7:8080 5m当 Pod 增减或 IP 变化时,Endpoints 自动更新。Service 的流量转发实际上就是基于这份后端列表实现的——客户端请求到达 Service IP,被转发到列表中的某个 Pod IP。
Endpoints vs EndpointSlice:老的
Endpoints对象把一个 Service 的所有后端塞进单个对象,端点多了(约 1000 个以上)会有性能和更新放大问题。自 1.21 起,K8s 默认改用 EndpointSlice(把后端切成多个小片,每片默认最多 100 个端点),kube-proxy 也已改为监听 EndpointSlice。Endpoints对象仍保留做向后兼容,排查时kubectl get endpointslices能看到更贴近实际的后端列表。
没有 selector 的 Service:如果 Service 不定义 selector,K8s 不会自动创建 Endpoints。你可以手动创建 Endpoints 对象,把 Service 的流量指向集群外的目标(比如外部数据库),实现"集群内 DNS 名 → 外部 IP"的映射。
kube-proxy:流量转发的实现者#
Endpoints 告诉 Service "后端 Pod 在哪",但实际把流量从 Service IP 转发到 Pod IP 的是 kube-proxy——每个节点上运行的网络代理组件。
kube-proxy 有几种转发模式:
| 模式 | 原理 | 特点 |
|---|---|---|
| iptables | 用 iptables 规则拦截 Service IP 的流量,随机 DNAT 到某个 Pod IP | 当前默认模式;规则随 Service/Pod 数量近似线性增长,大规模集群下规则同步和转发都会变慢 |
| IPVS | 用 Linux IPVS(内核级虚拟服务器)做负载均衡,支持多种调度算法(rr/lc/hash 等) | 大规模集群性能更好;需要内核加载 IPVS 模块;配置略复杂 |
| nftables | 用 nftables 替代 iptables,端点变更和转发都更高效 | 1.33 GA,规模化下优于 iptables;需内核 5.13+,仅 Linux;尚未成为默认,需显式开启 |
大多数中小集群用默认的 iptables 模式就够了。Service 数量上千、规则同步变慢时,可考虑 IPVS 或较新的 nftables 模式。(早期的 userspace 模式已在 1.26 移除,不再可选。)
iptables 模式的随机转发是纯概率轮询(不保证均匀),如果后端 Pod 数量少且连接数少,可能出现流量倾斜。IPVS 的 rr(round-robin)算法更均匀。但对大多数应用来说,iptables 的随机分布已经足够。
实战要点#
- selector 与 Pod label 必须匹配——Service 的
selector和 Pod 的labels是关联的唯一依据。写错一个 label,Service 找不到 Pod,Endpoints 为空,流量无处可去。排查 Service 不工作时,先检查kubectl get endpoints <service-name> - port 与 targetPort 区分清楚——
port是客户端访问 Service 的端口,targetPort是 Pod 容器监听的端口。两者经常不同(如 Service:80 → Pod:8080) - ClusterIP 是最常用类型——集群内服务间调用用 ClusterIP 即可,不要为了"方便调试"就全用 NodePort,这会暴露不必要的端口
- ** readinessProbe 影响后端列表**——Pod 未通过就绪检查时不会被加入 Endpoints/EndpointSlice,即使 label 匹配。这保证了未就绪的 Pod 不接收流量
- externalTrafficPolicy 影响源 IP——NodePort/LoadBalancer 默认
externalTrafficPolicy: Cluster:流量可能被转发到其他节点的 Pod,途中做了 SNAT,后端看到的源 IP 是节点 IP 而非客户端真实 IP,但负载更均匀。改成Local则只转发给本节点上的 Pod,保留客户端真实源 IP、少一跳,代价是没有本地 Pod 的节点收不到流量、可能负载不均
小结#
Service 解决了 Pod IP 不稳定的根本问题,为后端 Pod 提供了固定的访问入口。四种类型:ClusterIP 解决集群内调用,NodePort 加上节点端口暴露,LoadBalancer 再叠加云平台负载均衡器,ExternalName 则是独立于这条链的 DNS 别名。YAML 配置的关键是 selector 匹配 Pod label、port 与 targetPort 正确对应。流量转发的底层机制由 EndpointSlice(维护后端 Pod 列表)和 kube-proxy(iptables/IPVS/nftables 转发规则)共同实现——理解这两层,Service 的工作原理就清晰了。下一章我们将学习 Ingress,它是 Service 的上层抽象,用域名和路径规则统一管理集群对外暴露的多个服务。