Service 详解:Kubernetes 集群的"服务发现与负载均衡器"

This article is extracted from the chat log with AI. Please identify it with caution.

AI 协助说明: 本页由 Codex 协助核验和修复站内链接;正文的技术事实和适用范围未因本次修改而改变,仍以文中来源及当前官方文档为准。

阅读边界#

本文把 Service 当作一个 API 资源 来讲:如何用 selector、端口、类型、亲和与流量策略描述“哪些后端以什么入口被访问”,以及如何在对象层定位配置错误。

本文不逐包展开 DNS、ClusterIP、EndpointSlice、DNAT、kube-proxy 或 eBPF 的运行时实现。遇到“Service 存在但请求不通”时,请接着阅读 Service 转发链路。相关专题还有 CoreDNS 详解kube-proxy 详解Calico 流量走向Pod 优雅退出

结论:Service 是稳定入口的声明,不是业务代理进程#

Pod 的 IP、数量和所在节点会随发布、扩缩容、故障恢复而变化。Service 用一个稳定的名称和(多数情况下)稳定的虚拟 IP,表示一组网络后端及其访问策略:

Service spec
  ├─ selector:匹配哪些 Pod
  ├─ ports:入口端口如何映射到后端端口
  ├─ type / traffic policy:从哪里进入、哪些端点可以被选中
  └─ EndpointSlice:控制器维护的实际后端快照

Service 不会替应用做健康检查、认证、重试或七层路由;它也不是网络防火墙。应用是否接收新流量主要由 readiness 和 EndpointSlice 状态决定,访问许可则由 NetworkPolicy、CNI 与外部网络策略共同决定。

Service 资源模型#

一个常见的集群内 HTTP Service:

apiVersion: v1
kind: Service
metadata:
  name: web
  namespace: default
spec:
  type: ClusterIP
  selector:
    app.kubernetes.io/name: web
  ports:
    - name: http
      protocol: TCP
      appProtocol: http
      port: 80
      targetPort: http
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800
  internalTrafficPolicy: Cluster
字段解决的问题要点
metadata.namespace/name对象作用域与 DNS 名称Service 只能选择同一 namespace 中的 Pod。
selector后端集合标签匹配后,控制器会维护对应的 EndpointSlice。
ports入口与后端端口映射见下一节;多端口 Service 应为每个端口命名。
type入口暴露方式默认是 ClusterIP,其余类型在此基础上增加或改变入口语义。
clusterIP / clusterIPs虚拟入口地址普通 Service 由集群分配;clusterIP: None 是 Headless Service。
sessionAffinity同一客户端是否尽量命中同一后端仅是 Service 层的客户端 IP 亲和,不等同于应用会话。
internalTrafficPolicy / externalTrafficPolicy可选择端点的范围是硬性范围约束,不是“尽量就近”的偏好。
trafficDistribution端点就近偏好由版本和数据面支持情况决定;它表达偏好,不替代 traffic policy。

appProtocol 是给实现者和工具的应用协议提示,不会替代 protocolprotocol 决定 TCP、UDP 或 SCTP(集群支持时)的传输协议。

selector、Pod 与 EndpointSlice#

对带 selector 的 Service,EndpointSlice 控制器根据匹配的 Pod 自动创建、更新 EndpointSlice;正常情况下,未就绪的 Pod 不会成为常规新流量的可选后端。不要把旧的 Endpoints 对象当作完整事实来源:它已被弃用,并且在大量后端时可能被截断;排查和自动化应读取 EndpointSlice。

EndpointSlice 中最值得在对象层观察的是:

状态含义
ready是否应作为常规新流量的可用端点。
serving终止中的端点是否仍在提供响应,便于连接排空。
terminating对应 Pod 已进入删除流程。

终止端点的状态传播与连接排空属于运行时话题;请看 Pod 优雅退出Service 转发链路

selector 的 Service#

Service 也可以不选择 Pod,例如后端是集群外地址或由自定义控制器维护。此时需自行创建 discovery.k8s.io/v1 的 EndpointSlice,并给它设置 kubernetes.io/service-name: <service-name> 标签。端点地址不能是另一个 Service 的 ClusterIP;Service VIP 不能再作为 Service VIP 的后端。

这是一种明确的运维接口,不应靠手改旧 Endpoints 对象实现。还要注意:无 selector 的 Service 无法通过 API Server 的 kubectl port-forward service/... 代理到任意外部端点。

porttargetPortnodePort#

ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: http
    nodePort: 30080
字段含义常见误解
portService 自己暴露的端口客户端访问的是 ServiceIP:port,不是容器监听端口。
targetPort后端端点接收流量的端口可以是数字,也可以是 Pod 中命名端口;数字正确不代表应用一定在监听。
nodePortNodePort / LoadBalancer 入口在节点上的端口不是所有 Service 都需要它;默认范围通常为 30000-32767,但由集群配置决定。
nameService 端口名多端口 Service 必须可区分端口;命名 targetPort 也有利于滚动变更。

对于 targetPort: http,应让后端 Pod 的相应容器声明同名端口,例如:

containers:
  - name: web
    ports:
      - name: http
        containerPort: 8080

这不会自动让进程监听 8080;应用仍需实际监听该端口。

Service 类型与使用决策#

类型或形态入口语义适合场景关键边界
ClusterIP(默认)集群内虚拟 IP服务间调用对外 HTTP/HTTPS 通常由 Ingress 或 Gateway 暴露。
NodePort每个节点 IP 上的同一端口,且通常仍有 ClusterIP调试、简单入口、外部 LB 后端需考虑节点防火墙、安全组、端口范围和源 IP。
LoadBalancer请求基础设施实现外部负载均衡器云或已部署 LB 控制器的生产入口行为、费用、健康检查和是否分配 NodePort 取决于实现。
ExternalNameDNS 返回 externalName 的 CNAME给外部域名提供集群内别名不创建 ClusterIP 或四层转发;HTTP Host 与 TLS SNI 可能和真实域名不匹配。
Headless(clusterIP: NoneDNS 直接返回后端地址,不分配普通 ClusterIPStatefulSet、客户端自行发现/选择后端它是 Service 形态而非独立 type;不要期待 Service VIP 负载均衡。

Headless Service 与 StatefulSet#

Headless Service 常和 StatefulSet 配合。StatefulSet 在 spec.serviceName 中引用它后,Pod 可以获得稳定的单体 DNS 名称:

<pod-name>.<service-name>.<namespace>.svc.cluster.local

这适合数据库、副本集等需要识别具体成员的系统;客户端要自行处理返回的多个地址和故障切换。无状态 Deployment 的普通服务调用通常更适合使用 ClusterIP Service。

会话亲和与流量策略#

sessionAffinity#

可选值是 None(默认)和 ClientIP。后者使来自同一客户端 IP 的连接在超时时间内尽量选择同一后端:

spec:
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800

它不能替代 Cookie、令牌或应用层一致性设计:经 NAT 后多个用户可能共用一个源 IP,连接重建、端点下线和数据面差异也会改变实际命中结果。

internalTrafficPolicyexternalTrafficPolicytrafficDistribution#

配置适用流量Cluster / 默认行为Local 行为与代价
internalTrafficPolicy集群内 Pod 发出的 Service 流量可在整个集群的可用端点中选择仅选择源 Pod 所在节点的端点;该节点没有端点时,对这个来源等同于没有后端。
externalTrafficPolicyNodePort / LoadBalancer 的外部入口流量可转发到其他节点的后端,源地址常会被改写仅选择入口节点本地端点,通常有利于保留源 IP;需要让外部 LB 和健康检查只把流量送到有本地后端的节点。

trafficDistribution 是“优先选择拓扑上更近端点”的提示;不要把它和上表的硬限制混为一谈。使用它前应核对 Kubernetes 版本、所用 kube-proxy 或 eBPF 数据面是否支持,并观察 EndpointSlice 的拓扑信息。

Service 与常见工作负载/入口的关系#

对象它负责什么与 Service 的正确分工
Deployment创建、更新、扩缩无状态 PodDeployment 管 Pod 副本,Service 用标签稳定地发现这些副本。二者没有直接从属关系。
StatefulSet稳定序号、存储和身份常用 Headless Service 做成员发现;需要统一入口时也可另建普通 Service。
IngressHTTP/HTTPS 主机、路径与 TLS 路由Ingress Controller 通常把七层路由的后端指向 Service;Service 不取代 Ingress 的七层能力。
NetworkPolicyPod 入/出站访问许可策略通常作用于后端 Pod,而不是“给 Service 加防火墙”。要按 DNAT 后的真实 Pod 流量验证。

对象级排查:先确认声明与后端集合#

以下命令只验证 Kubernetes 对象是否表达正确;DNS、节点规则、CNI 路由和 NetworkPolicy 的逐层排查见 Service 转发链路

1. 检查 Service 本身#

kubectl -n default get svc web -o wide
kubectl -n default describe svc web
kubectl -n default get svc web -o yaml
kubectl -n default get svc web -o jsonpath='{.spec.clusterIP}{"\n"}'

确认 namespace、typeselectorport/targetPort/nodePortclusterIP 以及两种 traffic policy 是否符合预期。LoadBalancer 还应查看 describe 中的事件和 status.loadBalancer

2. 用同一组标签检查 Pod 与就绪状态#

kubectl -n default get pods -l app.kubernetes.io/name=web -o wide --show-labels
kubectl -n default get pods -l app.kubernetes.io/name=web

最常见的错误是 Service selector 和 Deployment 模板标签不一致,或 Pod 尚未 Ready。此时应先修正标签、readinessProbe 或应用监听,不要先重启 kube-proxy。

3. 以 EndpointSlice 为准核对后端#

kubectl -n default get endpointslice -l kubernetes.io/service-name=web -o wide
kubectl -n default get endpointslice -l kubernetes.io/service-name=web -o yaml

# 仅用于兼容性观察;Endpoints API 已弃用,不应作为自动化依据
kubectl -n default get endpoints web

核对地址、端口和 ready/serving/terminating。如果没有 EndpointSlice,优先检查 selector、Pod namespace、Pod labels 与 Ready 状态;如果地址存在而端口错误,检查 targetPort 和应用实际监听端口。

4. 按入口类型补充验证#

现象对象层优先检查
LoadBalancer 没有外部地址kubectl describe svc 的事件、LB 控制器/云配额、loadBalancerClass 与注解。
NodePort 无法从外部进入nodePort 是否已分配,再转入节点防火墙、安全组和 externalTrafficPolicy 排查。
Headless DNS 返回不符合预期clusterIP: None、selector、EndpointSlice 与 publishNotReadyAddresses
ExternalName 访问失败externalName 是否是真实域名,以及客户端的 DNS、HTTP Host、TLS SNI 是否匹配。

最小实践清单#

  • 用稳定且专用的标签作为 Service selector,不要选择过宽的通用标签。
  • 为多端口 Service 和 Pod 端口命名,减少 targetPort 变更带来的发布风险。
  • 为承接流量的应用设置 readinessProbe,并在发布/缩容时验证 EndpointSlice 变化。
  • 默认从 ClusterIP 开始;只有明确需要时才选 NodePort、LoadBalancer 或 Headless。
  • 把网络访问控制写在 NetworkPolicy 和基础设施安全组中,不把 Service 当作安全边界。
  • 遇到“对象正确但访问失败”,停止在 Service YAML 上反复修改,按 运行时数据链路 分层定位。

参考资料#

本文共 3792 字,创建于 Dec 31, 2025

相关标签: Kubernetes, ByAI