跳过正文
  1. 所有文章/

一次服务请求如何到达pod

·3487 字·7 分钟
目录

流量路径图
#

graph TD
    Client[客户端] -->|域名解析| DNS[DNS / CoreDNS]
    DNS -->|返回 IP| Client
    Client -->|HTTP/HTTPS| Ingress[Ingress Controller
可选] Ingress -->|路由规则转发| Service[Service] Client -->|NodePort/LoadBalancer
直接访问| Service Service -->|查询就绪 Pod 列表| EP[Endpoint / EndpointSlice] EP -->|写入转发规则| KubeProxy[kube-proxy
iptables / IPVS] KubeProxy -->|负载均衡转发| Pod[Pod]

各组件的职责与作用
#

从流量路径图,分析各组件的职责与作用。

DNS
#

Kubernetes 集群内使用 CoreDNS 作为 DNS 服务器,负责将 Service 名称解析为可访问的 IP 地址。

核心作用
#

  • 服务发现:Pod 通过 Service 名(如 my-service)即可访问目标服务,无需硬编码 IP。
  • 域名补全:自动根据 Pod 所在 Namespace 补全 FQDN,简化调用方式。
  • 动态更新:CoreDNS 通过 Watch kube-apiserver 实时感知 Service/Endpoint 变化,DNS 记录自动保持最新。

工作流程
#

  1. Pod 发起请求:应用代码访问 my-service.default.svc.cluster.local(或简写 my-service)。
  2. 查询本地 DNS 配置:Pod 的 /etc/resolv.conf 中配置了 nameserver(指向 CoreDNS 的 ClusterIP,通常是 10.96.0.10)和 search 域(如 default.svc.cluster.local svc.cluster.local cluster.local)。
  3. 补全域名:如果应用只写了短名(如 my-service),kubelet 会依据 search 域依次尝试补全:
    • my-service.default.svc.cluster.local
    • my-service.svc.cluster.local
    • my-service.cluster.local
  4. CoreDNS 解析:CoreDNS 收到查询后,查找其内存中缓存的 Service 记录:
    • ClusterIP 类型 Service:返回 Service 的 ClusterIP(A 记录)。
    • Headless ServiceclusterIP: None):直接返回后端所有就绪 Pod 的 IP 列表(多条 A 记录)。
  5. 返回结果:Pod 拿到 IP 后发起 TCP/UDP 连接。
graph TD
    A[Pod 发起域名请求] -->|读取 /etc/resolv.conf| B[获取 nameserver 和 search 域]
    B -->|短名补全| C{域名是否为 FQDN?}
    C -->|是| D[直接查询 CoreDNS]
    C -->|否| E[按 search 域依次补全尝试]
    E --> D
    D --> F{Service 类型?}
    F -->|ClusterIP| G[返回 Service ClusterIP]
    F -->|Headless| H[返回所有就绪 Pod IP]
    G --> I[Pod 发起 TCP/UDP 连接]
    H --> I

注意:CoreDNS 通过 Watch kube-apiserver 中的 Service 和 Endpoint 资源来实时更新 DNS 记录,无需手动配置。

Ingress(可选)
#

Ingress 是 Kubernetes 中用于管理集群外部 HTTP/HTTPS 流量进入集群的资源对象。它本身只是一组路由规则的声明,真正执行流量转发的是 Ingress Controller(如 Nginx Ingress Controller、Traefik 等)。

核心作用
#

  • 七层路由:根据域名(Host)和路径(Path)将请求路由到不同的 Service。
  • TLS 终止:在入口处统一处理 HTTPS 证书,后端 Service 只需处理 HTTP。
  • 统一入口:多个 Service 共用一个外部 IP/域名,通过路由规则区分,避免每个服务都暴露一个 LoadBalancer。

工作流程
#

  1. 用户创建 Ingress 资源,定义路由规则(如 api.example.com/v1 → service-a)。
  2. Ingress Controller 监听 kube-apiserver,感知到 Ingress 规则变化。
  3. Ingress Controller 将规则转换为自身的配置(如 Nginx 的 nginx.conf),并 reload 生效。
  4. 外部流量到达 Ingress Controller 后,根据 Host 和 Path 匹配规则,转发到对应的 Service ClusterIP。
graph TD
    A[外部请求到达 Ingress Controller] --> B{匹配 Host 域名}
    B -->|api.example.com| C{匹配 Path 路径}
    B -->|web.example.com| D{匹配 Path 路径}
    B -->|未匹配| E[返回 404 或默认后端]
    C -->|/v1| F[转发到 service-a]
    C -->|/v2| G[转发到 service-b]
    D -->|/| H[转发到 service-web]
    F --> I[Service → Pod]
    G --> I
    H --> I

何时不需要 Ingress:集群内部 Pod 互相访问时直接通过 Service 即可;外部流量也可以通过 NodePort 或 LoadBalancer 类型的 Service 直接暴露,但这样每个服务需要独立的端口或外部 IP。

Service
#

Service 是 Kubernetes 中最核心的服务发现和负载均衡抽象。它为一组功能相同的 Pod 提供一个稳定的虚拟 IP(ClusterIP)和 DNS 名称,屏蔽了后端 Pod 动态变化的细节。

核心作用
#

  • 稳定入口:Pod 随时可能被重建、IP 会变,但 Service 的 ClusterIP 和 DNS 名始终不变。
  • 负载均衡:通过 kube-proxy 将流量均匀分发到后端多个 Pod。
  • 服务发现:其他 Pod 通过 Service 名(DNS)即可访问,无需关心具体 Pod IP。

Service 类型:

类型说明
ClusterIP(默认)仅集群内部可达,分配一个虚拟 IP
NodePort在每个节点上暴露一个端口(30000-32767),外部可通过 节点IP:端口 访问
LoadBalancer依赖云厂商,自动创建外部负载均衡器并分配公网 IP
ExternalName将 Service 映射为一个外部 DNS 名(CNAME),不创建 ClusterIP

NodePort 如何在每个节点上暴露端口?

NodePort 并不是在节点上启动一个监听进程,而是由 kube-proxy 在每个节点的 iptables/IPVS 中写入规则

  1. 用户创建 NodePort 类型 Service,apiserver 分配一个端口(如 30080)。
  2. 每个节点上的 kube-proxy 监听到 Service 变化,在本节点的 iptables/IPVS 中添加规则:任何到达本机 30080 端口的流量 → DNAT 到后端 Pod IP:Port
  3. 外部请求 任意节点IP:30080 → 内核 netfilter 匹配 iptables 规则 → 转发到某个后端 Pod(可能在本节点,也可能跨节点)。

因此,即使某个节点上没有对应的 Pod,访问该节点的 NodePort 一样能到达其他节点上的 Pod——流量会被 iptables 规则跨节点转发。

LoadBalancer 如何感知后端 Pod?

LoadBalancer 类型的 Service 并不直接感知 Pod IP,它的架构是分层的:

  1. 用户创建 LoadBalancer 类型 Service → Kubernetes 自动创建一个 NodePort(如 30080)。
  2. 云厂商的 Cloud Controller Manager(运行在控制面)监听到该 Service,执行以下操作:
    • 通过 kube-apiserver 获取当前集群所有 Ready 状态的 Node IP 列表
    • 读取 Service 上分配的 NodePort 端口号(如 30080)。
    • 调用云厂商 API,创建外部负载均衡器(如 AWS ELB、阿里云 SLB),并将后端池设置为 node-1:30080, node-2:30080, ...
    • 持续 Watch Node 资源变化:节点新增时自动将其加入 LB 后端池,节点下线时自动移除。
  3. 外部 LB 的后端池始终与集群节点保持同步,由 Cloud Controller Manager 负责维护。
  4. 流量路径:客户端 → 外部 LB → 某个节点的 NodePort → kube-proxy iptables/IPVS → Pod

也就是说,外部 LB 只关心节点 IP + NodePort,不需要知道 Pod IP;Pod 级别的负载均衡仍然由节点上的 kube-proxy 完成。

Pod 级别的转发细节:

当流量到达某个节点的 NodePort 后,内核的 netfilter 框架按照 kube-proxy 预先写入的规则进行处理:

  1. DNAT(目标地址转换):iptables/IPVS 规则将目标地址从 节点IP:NodePort 改为某个后端 PodIP:ContainerPort,选择哪个 Pod 由负载均衡算法决定(iptables 用随机概率,IPVS 支持轮询、最小连接等)。
  2. 路由判断
    • 如果目标 Pod 就在本节点:数据包直接通过本机网桥(如 cbr0/cni0)转发到 Pod 的 veth 网卡。
    • 如果目标 Pod 在其他节点:数据包经由节点间的 overlay 网络(如 VXLAN)或路由(如 BGP)转发到目标节点,再由该节点转发到 Pod。
  3. SNAT(源地址转换,可选):默认情况下,跨节点转发时会做 SNAT(将源 IP 改为本节点 IP),确保回包能原路返回。如果设置了 externalTrafficPolicy: Local,则跳过 SNAT 保留客户端真实 IP,但流量只会转发到本节点上的 Pod。

实际上,无论哪种 Service 类型,流量到达节点后的 Pod 转发逻辑完全一致,区别仅在于流量如何到达节点:

类型流量到达节点的方式到达节点后的 Pod 转发
ClusterIP集群内 Pod 直接访问 ClusterIP同一套 iptables/IPVS 规则做 DNAT
NodePort外部直接访问 节点IP:NodePort同一套 iptables/IPVS 规则做 DNAT
LoadBalancer外部 LB 转发到 节点IP:NodePort同一套 iptables/IPVS 规则做 DNAT

LoadBalancer 本质上就是 在 NodePort 前面加了一层外部负载均衡器,到达节点之后的转发链路完全一致

部分云厂商支持"直通 Pod"模式(如 AWS 的 IP 模式 Target Group、阿里云的 ENI 模式),此时 Cloud Controller Manager 会直接读取 EndpointSlice 中的 Pod IP 写入 LB 后端池,跳过 NodePort 层,减少一跳延迟。

工作流程
#

  1. 用户创建 Service,通过 selector 指定匹配的 Pod 标签。
  2. Endpoint Controller 自动收集匹配且就绪的 Pod IP,生成 Endpoint/EndpointSlice。
  3. kube-proxy 监听 Service 和 Endpoint 变化,将规则写入节点的 iptables 或 IPVS。
  4. 当流量到达 Service ClusterIP 时,由 iptables/IPVS 规则进行 DNAT,转发到某个后端 Pod IP。
graph TD
    A[请求到达 Service ClusterIP] --> B[kube-proxy 匹配 iptables/IPVS 规则]
    B --> C{负载均衡选择}
    C --> D[Pod-1]
    C --> E[Pod-2]
    C --> F[Pod-3]
    G[用户创建 Service
selector: app=web] --> H[Endpoint Controller 收集就绪 Pod IP] H --> I[生成 Endpoint/EndpointSlice] I --> B

Endpoint / EndpointSlice
#

Endpoint 和 EndpointSlice 是 Service 背后维护就绪 Pod IP 列表的资源对象。它们是 Service 能够正确转发流量的数据基础。

核心作用
#

  • 记录后端地址:存储与 Service selector 匹配且通过 readinessProbe 的所有 Pod 的 IP:Port
  • 动态更新:Pod 创建/销毁/就绪状态变化时,Endpoint Controller 自动增删对应条目。
  • 驱动 kube-proxy:kube-proxy 监听 EndpointSlice 的变化来更新转发规则。

Endpoint vs EndpointSlice:

维度EndpointEndpointSlice
粒度一个 Service 对应一个 Endpoint 对象,包含所有 Pod IP一个 Service 可对应多个 EndpointSlice,每个最多 100 个端点
性能Pod 数量大时,单次更新需传输整个列表分片更新,减少 apiserver 和 kube-proxy 的负载
版本v1(旧)discovery.k8s.io/v1(推荐,K8s 1.21+ 默认)

工作流程
#

  1. Pod 启动并通过 readinessProbe → Endpoint Controller 将其 IP 加入 EndpointSlice。
  2. Pod 失败或被删除 → Endpoint Controller 将其 IP 从 EndpointSlice 中移除。
  3. kube-proxy 感知变化 → 更新 iptables/IPVS 规则 → 流量不再转发到不可用的 Pod。
graph TD
    A[Pod 启动] --> B{readinessProbe 通过?}
    B -->|是| C[Endpoint Controller 将 Pod IP 加入 EndpointSlice]
    B -->|否| D[不加入,不接收流量]
    C --> E[kube-proxy 感知变化]
    E --> F[更新 iptables/IPVS 转发规则]
    F --> G[流量可达该 Pod]

    H[Pod 异常/删除] --> I[Endpoint Controller 移除 Pod IP]
    I --> E