2. Service对象基础

在Kubernetes中,Pod是短暂的:副本被重建、节点故障迁移、滚动更新替换,都会让Pod的IP地址不断变化。如果客户端直接访问Pod IP,任何一次调度或重建都会导致连接失效。Service正是为解决这一问题而存在的抽象层——它为一组功能相同的Pod提供一个稳定的虚拟IP(ClusterIP)和DNS名称,并把流量按规则转发到当前健康的Pod上。理解Service,关键要抓住三件事:Service对象本身如何定义、它如何通过标签选择器找到Pod、以及它背后的Endpoints与EndpointSlice如何被自动维护。

Service的定义与核心字段

Service是一个集群级别的API对象,属于v1核心组。它的核心职责是:声明一个稳定的访问入口,并描述“这个入口应该把流量交给哪些Pod”。因此Service的定义中最重要的两部分是端口映射和标签选择器。端口部分用port声明Service自身暴露的端口,用targetPort声明流量最终要到达容器上的哪个端口;选择器部分用selector以键值对形式匹配Pod的标签。

需要特别区分三个端口概念:port是Service的虚拟端口,客户端访问ClusterIP时使用它;targetPort是后端容器实际监听的端口,可以写成数字,也可以写成容器端口的名称;nodePort仅在type: NodePort时出现,表示在每个节点上额外开放的端口。三者关系是:客户端 → ClusterIP:port → 选中Pod的IP:targetPort。

一个完整的Service YAML示例

下面是一个典型的ClusterIP类型Service,用于把前端流量转发到带app: web标签的Pod的8080端口:

apiVersion: v1
kind: Service
metadata:
  name: web-svc
  namespace: default
  labels:
    app: web
spec:
  type: ClusterIP
  selector:
    app: web
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 8080
  sessionAffinity: None

逐字段看:spec.selector是app: web,意味着所有带该标签且处于Ready状态的Pod都会被纳入后端;ports[0].port: 80让Service在ClusterIP的80端口监听;targetPort: 8080把请求转发到Pod的8080端口;sessionAffinity: None表示默认的随机负载均衡,若设为ClientIP则同一客户端IP会被固定到同一后端。创建后可用kubectl get svc web-svc查看分配的ClusterIP,用kubectl describe svc web-svc查看Endpoints列表。

selector为空(未写selector)的Service不会自动生成Endpoints,这类Service通常用于手动管理后端,或作为ExternalName类型使用。若selector写错,Service会创建成功但Endpoints为空,表现为“服务存在但访问不通”。

标签选择器如何匹配Pod

Service的selector是基于等值匹配的,即key: value形式,多个键值对之间是逻辑与(AND)关系。它不支持matchExpressions这类集合表达式,这是它与Deployment、NetworkPolicy选择器的重要区别。匹配过程由Endpoints控制器(kube-controller-manager中的endpoint-controller)持续执行:它监听Service和Pod的变化,凡是标签满足selector、且Pod处于Ready状态、且未被删除的,都会被加入后端列表。

这里有一个容易忽略的细节:Pod的readinessProbe结果直接影响是否被纳入Endpoints。一个标签匹配但未通过就绪探针的Pod,不会出现在Endpoints中,因此不会被转发流量。这正是Service能实现“只把流量发给健康Pod”的机制来源。

Endpoints与EndpointSlice的生成机制

Endpoints是与Service同名的对象,记录当前匹配到的后端地址列表,每个地址包含IP和端口。它的结构是subsets[].addresses[]与subsets[].ports[]的组合。当Pod扩缩容、重建、就绪状态变化时,Endpoints控制器会实时更新这个对象。kube-proxy在各节点上监听Endpoints变化,据此更新iptables或IPVS规则,从而让ClusterIP的流量真正落到正确的Pod上。

随着集群规模增大,单个Endpoints对象可能包含成千上万个地址,任何一次变更都要整体推送,造成API Server和kube-proxy的压力。为此Kubernetes引入了EndpointSlice:它把一个大Endpoints按上限(默认每个Slice最多100个端点)切分成多个较小的对象,并打上kubernetes.io/service-name标签关联到Service。EndpointSlice还带有addressType(IPv4/IPv6/FQDN)和endpoint.conditions(ready、serving、terminating)等更丰富的状态字段,使拓扑感知路由、流量分发策略等能力成为可能。在较新版本中,EndpointSlice是默认且推荐的后端发现机制,Endpoints对象仍会为兼容性保留,但已不再是大规模场景的主路径。

Service → 标签选择器 → Endpoints/EndpointSlice → Pod 的发现链路 Service: web-svc type: ClusterIP port: 80 targetPort: 8080 selector: app=web ClusterIP: 10.96.0.15 标签选择器匹配 app=web(等值 AND) 仅 Ready Pod 入选 Endpoints 控制器 监听 Service / Pod 变化 生成 Endpoints 对象 切分为 EndpointSlice 每片 ≤ 100 个端点 kube-controller-manager Endpoints / EndpointSlice 10.244.1.7:8080 ready 10.244.2.9:8080 ready 10.244.3.4:8080 notReady Pod A app=web · Ready Pod B app=web · Ready Pod C app=web · NotReady 虚线表示 NotReady 的 Pod 不进入转发列表,仅保留在 EndpointSlice 的 notReady 条件中

从创建到生效:一次完整的观察过程

假设已有一个Deployment管理3个app: web的Pod。执行kubectl apply -f web-svc.yaml后,可以按以下顺序验证:

  1. kubectl get svc web-svc确认ClusterIP已分配,例如10.96.0.15。
  2. kubectl get endpoints web-svc查看后端地址,正常应列出3个IP:8080。
  3. kubectl get endpointslice -l kubernetes.io/service-name=web-svc查看切片,若端点较少通常只有1片。
  4. 手动把某个Pod的标签改为app: api,再查Endpoints,会发现该IP消失;改回后重新出现。这直接验证了selector的实时匹配。
  5. 用kubectl run tmp --rm -it --image=busybox -- wget -qO- http://web-svc在集群内访问,确认流量可达。
常见排障点:Service能ping通ClusterIP并不代表后端正常,因为ClusterIP是虚拟地址,ICMP行为依赖实现;应使用curl/wget访问实际端口。若Endpoints为空,优先检查selector是否与Pod标签一致、Pod是否Ready、命名空间是否相同。

小结与关键判断依据

Service通过“稳定入口 + 标签选择 + 自动后端维护”三件套,把易变的Pod抽象为稳定服务。判断一个Service是否工作正常,最直接的依据是kubectl get endpoints是否有预期地址;判断后端发现机制是否健康,则看EndpointSlice是否随Pod状态实时更新。掌握port/targetPort/nodePort的区别、selector的等值匹配语义,以及Ready状态对Endpoints的影响,就掌握了Service对象基础的全部要点。