本文介绍 Kubernetes (K8s) 中 Service (SVC) 是如何将流量路由到其后端 Endpoints (即 Pod) 的原理。由 Kubernetes 集群中的多个核心组件协同工作的结果。我们可以将其分解为两大阶段:服务发现 和 流量转发。
服务发现 (Service Discovery)
解决 我该把请求发往哪里?
当一个 Pod(我们称之为客户端 Pod)想要访问另一个服务(比如一个名为 my-app 的 Service)时,它首先需要知道把网络请求发送到哪个 IP 地址。这时,Kubernetes 内置的 DNS 服务 CoreDNS 就登场了。
- 自动生成的 DNS 记录:当你创建一个 Service 时,Kubernetes 会自动在内部的 CoreDNS 中为其创建一个 DNS A 记录。这个记录的格式通常是
<service-name>.<namespace>.svc.cluster.local。例如,在 default 命名空间下创建一个名为 my-app 的 Service,它对应的 DNS 名称就是 my-app.default.svc.cluster.local。
- 解析 Service Name 到 ClusterIP:这个 DNS 记录会解析到该 Service 的 ClusterIP。ClusterIP 是一个由 Kubernetes 分配的、稳定的、仅在集群内部可访问的虚拟 IP 地址。
- Pod 内的 DNS 查询:客户端 Pod 的
/etc/resolv.conf 文件被自动配置为使用集群内的 CoreDNS 服务器。因此,当你在代码中使用 http://my-app 这样的服务名称时,Pod 的 DNS 解析器会向 CoreDNS 发起查询。
- 获取 ClusterIP:CoreDNS 收到查询后,会返回
my-app 这个 Service 对应的 ClusterIP。
至此,客户端 Pod 已经知道了请求的目标 IP 地址,也就是这个虚拟的 ClusterIP。
流量转发 (Traffic Forwarding)
虚拟 IP 背后真正的目的地是谁?
现在客户端 Pod 向这个虚拟的 ClusterIP 发送数据包了。但 ClusterIP 本身并不与任何网络设备绑定,它只是一个占位符。真正将流量从这个虚拟 IP 转发到后端真实 Pod IP 的幕后英雄是 kube-proxy。
kube-proxy 是一个运行在 Kubernetes 集群中每个 Node 上的网络代理和负载均衡器。它会监视 (watch) Kubernetes API Server 上关于 Service 和 Endpoints (或 EndpointSlice) 对象的变化,然后根据这些变化在节点上设置相应的转发规则。
这个转发过程主要通过以下几种模式实现:
核心组件:Endpoints Controller
在讨论 kube-proxy 的模式之前,必须先了解 Endpoints Controller。它是 kube-controller-manager 的一部分,负责维护 Service 和 Pod 之间的关联关系。
- 监视:它会持续监视 Service 及其关联的 Pod (通过 Service 的
selector 标签选择器)。
- 更新 Endpoints 对象:当一个 Service 所关联的 Pod 发生变化时(例如,Pod 创建、销毁、IP 变更、健康检查失败),Endpoints Controller 会立即更新与该 Service 同名的 Endpoints 对象。这个对象里包含了所有健康后端 Pod 的实际 IP 地址和端口列表。
- **通知
kube-proxy**:kube-proxy 通过监视 Endpoints 对象的变化,来获取最新的、可用的后端 Pod IP 列表。
kube-proxy 的工作模式
kube-proxy 将流量从 Service ClusterIP 路由到后端 Pod IP,主要有以下三种模式:
iptables 模式 (默认模式,最常用)
这是当前 Kubernetes 的默认模式,稳定且可靠。
- 原理:
kube-proxy 会为集群中的每一个 Service 创建一系列的 iptables 规则。这些规则被写入到每个节点的 nat 表中。
- 工作流程:
- 捕获流量 (DNAT):当一个数据包的目的地址是某个 Service 的 ClusterIP:Port 时,它会被
KUBE-SERVICES 这条 iptables 链捕获。
- 负载均衡:这条链会再跳转到对应 Service 的专属链 (例如
KUBE-SVC-XXX)。在这个专属链里,包含了到每一个后端 Pod 的转发规则。kube-proxy 会使用 statistic 模块,通过概率(默认是均等的)随机选择一个后端 Pod。
- 修改目标地址:选定一个后端 Pod 后,
iptables 规则会执行 DNAT (Destination Network Address Translation),将数据包的目标 IP 和端口从 Service 的 ClusterIP:Port 修改为被选中的 Pod 的实际 IP:Port。
- 路由转发:修改完目标地址后,Linux 内核就会根据节点的路由表,将这个数据包正确地转发给目标 Pod(无论这个 Pod 是在当前节点还是在其他节点)。
- 优点:非常成熟和稳定。
- 缺点:当 Service 数量非常多(成千上万)时,
iptables 规则会变得很长,查找匹配规则的性能会下降,因为 iptables 是一个链式匹配的结构。
IPVS (IP Virtual Server) 模式
为了解决 iptables 模式在大规模集群下的性能问题,Kubernetes 引入了 IPVS 模式。
- 原理:IPVS 本身就是 Linux 内核中用于高性能负载均衡的一个模块。
kube-proxy 在此模式下,不再生成大量的 iptables 规则,而是通过 netlink 接口去创建和管理 IPVS 规则。
- 工作流程:
kube-proxy 会为每个 Service 创建一个 IPVS 虚拟服务器 (Virtual Server),并将 Service 的 ClusterIP:Port 作为其地址。
- 然后,它会将从 Endpoints 对象中获取到的后端 Pod IP:Port 列表作为这个虚拟服务器的 真实服务器 (Real Server)。
- 当访问 Service ClusterIP 的流量到达节点时,会被内核的 IPVS 模块直接接管。IPVS 使用更高效的哈希表来存储规则,能够非常快速地找到对应的虚拟服务器,并根据预设的负载均衡算法(如轮询、最少连接等)选择一个真实服务器(Pod),然后将流量转发过去。
userspace 模式 (已废弃)
这是最古老、性能最差的模式,不推荐使用。它在用户空间实现了一个真正的代理进程。所有到 ClusterIP 的流量都会先进入内核,然后被重定向到用户空间的 kube-proxy 进程,kube-proxy 再与后端某个 Pod 建立连接,并在两者之间来回转发数据。这个过程涉及多次内核态和用户态的切换,开销巨大。
总结流程
将整个流程串联起来:
- 创建与关联:管理员创建 Deployment 和 Service。Endpoints Controller 发现新 Pod 并将其 IP 添加到与 Service 关联的 Endpoints 对象中。
- 规则同步:每个节点上的
kube-proxy 检测到 Service 和 Endpoints 的变化,并根据所选模式(如 iptables)在节点上创建或更新相应的转发规则。
- 服务发现:客户端 Pod 通过查询集群 DNS (CoreDNS) 将 Service 名称 (
my-app) 解析为虚拟的 ClusterIP。
- 流量发出:客户端 Pod 向这个 ClusterIP 发送请求数据包。
- 转发与路由:数据包到达节点(可以是客户端 Pod 所在的节点,也可以是其他节点),被节点上的
iptables 或 IPVS 规则捕获。
- 负载均衡与 DNAT:规则根据负载均衡策略选择一个健康的后端 Pod,并将数据包的目标地址从 ClusterIP 修改为该 Pod 的真实 IP。
- 到达终点:内核将修改后的数据包通过 CNI 提供的集群网络路由到最终的目标 Pod。
通过这套精巧的设计,Kubernetes 实现了应用与其底层 Pod 实例的解耦,为开发者提供了稳定、可靠的服务访问入口,同时保证了整个系统的动态性和可扩展性。