Appearance
Service 控制面与节点代理
Service 把“我要访问这组后端”声明成一个稳定入口;它不等于一个常驻的用户态代理进程。Kubernetes 需要把这个声明逐步变成两类可用状态:API 中当前可选的后端,以及每个 Node 能快速执行的本地转发规则。
text
Service selector + Pod labels + Pod readiness
-> EndpointSlice controller
-> EndpointSlice(API 中的可选后端)
-> kube-proxy 或 eBPF 数据面 watch
-> Node 本地的 Service / endpoint 转发状态
-> 新连接命中一个后端1. 谁拥有哪一段事实
| 对象或组件 | 它拥有的状态 | 不负责什么 |
|---|---|---|
| Service | 名称、ClusterIP、端口、selector、流量策略 | 直接保存每个 Pod 是否可用 |
| Pod | 标签、IP、Ready condition、生命周期 | 决定自己属于哪个 Service |
| EndpointSlice controller | 根据 selector 和 Pod 条件物化 endpoint 集合 | 为每个包执行负载均衡 |
| kube-proxy | 在 Node 维护 Service 到 endpoint 的可执行规则 | 创建 Pod 网络或判断业务健康 |
| CNI / eBPF 数据面 | 连接、路由、策略和可能的 Service 实现 | 维护 Service selector 的业务语义 |
把这些状态分开,才能解释“Service 存在但连不上”:Service API 对象、EndpointSlice、节点规则和目标进程监听,至少有四个独立的正确性条件。
2. EndpointSlice:把选择器变成可消费的后端集合
对有 selector 的 Service,EndpointSlice controller 根据标签选择 Pod,并将可用地址、端口、地址类型和条件写入一个或多个 EndpointSlice。大规模 Service 会拆成多个 Slice,避免任何单对象无限变大。
text
Pod label matches selector
+ Pod 具备 IP
+ endpoint 条件允许接收流量
-> EndpointSlice 中出现地址和端口其中 readiness 不能简化成“Pod 已创建”。通常只有 endpoint 条件允许的后端才被常规 Service 流量选择;终止中的 Pod、publishNotReadyAddresses、serving 与 terminating 条件会改变边界。排障时应读 EndpointSlice 的 endpoints[].conditions,不要只看 kubectl get pods 是否为 Running。
没有 selector 的 Service 是另一条边界:Kubernetes 不会自动替它创建 EndpointSlice,使用者必须自己维护后端。这常用于把集群内入口指向外部系统;也意味着后端正确性不再由 Pod label 自动收敛。
3. Node 如何把 API 状态变成快路径
kube-proxy 在每个 Node watch Service 和 EndpointSlice。它先维护内存中的 Service/endpoint 视图,再在同步时将差异编译为 Node 可执行的规则。传统后端包括 iptables 或 IPVS;较新的 kube-proxy 可使用 nftables。eBPF CNI 也可能替代 kube-proxy 的主要 Service 路径,但这不是 kube-proxy 本身的工作。
text
API watch event
-> kube-proxy 本地对象缓存
-> syncProxyRules
-> iptables / IPVS / nftables 规则或表
-> 新连接匹配 ClusterIP:port
-> DNAT 或等价映射到 endpoint IP:targetPort这里的设计取舍很明确:API Server 不在每个包上参与决策。它只传播声明状态;Node 物化一份本地数据面,报文才能在不读取 etcd 的前提下转发。代价是状态传播有延迟,排障必须分别验证控制面已收敛和本地规则已更新。
4. 一次连接与 endpoint 变化
Service 的负载均衡通常以连接为粒度,而不是每个 TCP 包随机选择后端。首次匹配时,数据面选择一个 endpoint 并建立 NAT 或等价的流状态;后续同一连接要保持一致,回包才能正确回到客户端。
text
client -> ClusterIP:443
-> 选择 10.2.4.18:8443
-> 保存连接映射
-> 后续同一流量继续到 10.2.4.18:8443EndpointSlice 删除一个后端后,新连接应不再选择它;已有连接是否继续、何时断开,取决于协议、conntrack/NAT 状态、应用优雅终止和具体数据面。发布系统不能只依赖“endpoint 已移除”来保证长连接立即迁走。
5. 用四段证据排查 Service 不通
| 层 | 先确认什么 | 常见断点 |
|---|---|---|
| 声明 | Service 的 port、targetPort、selector 与类型 | selector 写错、targetPort 不对应 |
| 后端事实 | EndpointSlice 是否存在且有 ready endpoint | Pod 未 Ready、端口未解析、无 selector 却没人维护 Slice |
| Node 物化 | 目标 Node 已消费变更并安装规则 | kube-proxy 异常、规则同步延迟、模式判断错误 |
| 目标可达性 | endpoint IP 可路由,进程监听并允许回程 | CNI 路由、NetworkPolicy、MTU、进程未监听 |
不要跳过第二层。一个没有 endpoint 的 Service 在不同实现中可能表现为拒绝、超时或无可用后端;这首先是后端收敛问题,不是应用 DNS 问题。
6. 从实现读架构
读源码时先跟状态流,而不是先读规则生成细节:
text
Service / Pod 变化
-> EndpointSlice controller 写 API
-> kube-proxy config watcher 收到 Slice
-> serviceChangeTracker / endpointsChangeTracker 合并变化
-> syncProxyRules 物化本地状态Kubernetes 主仓库的常用入口:
text
pkg/controller/endpointslice/endpointslice_controller.go
pkg/proxy/config/config.go
pkg/proxy/iptables/proxier.go
pkg/proxy/ipvs/proxier.go
pkg/proxy/nftables/proxier.go路径和内部对象会随 Kubernetes 版本演进,阅读时要固定 tag;重要的是识别这条架构链:声明对象 -> 派生对象 -> 本地物化 -> 数据面执行。
资料与边界
- Kubernetes:Service、EndpointSlices、Virtual IPs and Service Proxies,访问于 2026-07-31。
- Kubernetes 源码:EndpointSlice controller、kube-proxy,访问于 2026-07-31。
- Linux Netfilter:Connection tracking,访问于 2026-07-31。
本文描述的是 Kubernetes 的对象与职责关系。ClusterIP 如何落实为规则、是否使用 conntrack、是否由 eBPF 接管,取决于安装的 kube-proxy 模式、CNI、内核能力和节点配置,需要在目标环境验证。