路线图

Service:Pod 的稳定访问入口

星辉 2026-07-02 阅读 3 min 572 字 路线图
Service:Pod 的稳定访问入口 封面

K8s 中 Pod 的生命周期是短暂的——调度、重启、扩缩容都会改变 Pod IP。如果客户端直接用 Pod IP 访问服务,Pod 一重建 IP 就变了,所有连接瞬间断裂。Service 的出现正是为了解决这个问题:它为一组 Pod 提供一个永不变化的访问入口(IP + DNS 名),客户端只需要知道 Service 的地址,不需要关心背后是哪些 Pod。


为什么需要 Service#

Pod 的 IP 不稳定,原因有三:

  1. Pod 重建 IP 变化——Pod 崩溃重启、节点故障后重新调度,新 Pod 分配新 IP
  2. 扩缩容 Pod 数量变化——HPA 扩容产生新 Pod,缩容删除旧 Pod,后端列表实时变动
  3. 滚动更新 Pod 替换——新旧 Pod 交替,客户端不应感知这个过程

Service 在 Pod 和客户端之间插入了一层抽象:

text
客户端 → 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(默认类型)#

集群内部服务间调用最常用的配置:

yaml
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#

在节点上开放端口,让集群外可以访问:

yaml
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#

云上生产环境对外暴露服务的标准方式:

yaml
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-gatewayEXTERNAL-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 和端口:

bash
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 的随机分布已经足够。


实战要点#

  1. selector 与 Pod label 必须匹配——Service 的 selector 和 Pod 的 labels 是关联的唯一依据。写错一个 label,Service 找不到 Pod,Endpoints 为空,流量无处可去。排查 Service 不工作时,先检查 kubectl get endpoints <service-name>
  2. port 与 targetPort 区分清楚——port 是客户端访问 Service 的端口,targetPort 是 Pod 容器监听的端口。两者经常不同(如 Service:80 → Pod:8080)
  3. ClusterIP 是最常用类型——集群内服务间调用用 ClusterIP 即可,不要为了"方便调试"就全用 NodePort,这会暴露不必要的端口
  4. ** readinessProbe 影响后端列表**——Pod 未通过就绪检查时不会被加入 Endpoints/EndpointSlice,即使 label 匹配。这保证了未就绪的 Pod 不接收流量
  5. 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 的上层抽象,用域名和路径规则统一管理集群对外暴露的多个服务。