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列表。
标签选择器如何匹配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对象仍会为兼容性保留,但已不再是大规模场景的主路径。
从创建到生效:一次完整的观察过程
假设已有一个Deployment管理3个app: web的Pod。执行kubectl apply -f web-svc.yaml后,可以按以下顺序验证:
kubectl get svc web-svc确认ClusterIP已分配,例如10.96.0.15。kubectl get endpoints web-svc查看后端地址,正常应列出3个IP:8080。kubectl get endpointslice -l kubernetes.io/service-name=web-svc查看切片,若端点较少通常只有1片。- 手动把某个Pod的标签改为
app: api,再查Endpoints,会发现该IP消失;改回后重新出现。这直接验证了selector的实时匹配。 - 用
kubectl run tmp --rm -it --image=busybox -- wget -qO- http://web-svc在集群内访问,确认流量可达。
小结与关键判断依据
Service通过“稳定入口 + 标签选择 + 自动后端维护”三件套,把易变的Pod抽象为稳定服务。判断一个Service是否工作正常,最直接的依据是kubectl get endpoints是否有预期地址;判断后端发现机制是否健康,则看EndpointSlice是否随Pod状态实时更新。掌握port/targetPort/nodePort的区别、selector的等值匹配语义,以及Ready状态对Endpoints的影响,就掌握了Service对象基础的全部要点。