CNI(Container Network Interface)不是一种具体网络,也不直接实现 Kubernetes Service。它是一套容器运行时与网络插件之间的接口规范:运行时用标准命令和参数调用插件,插件完成网络配置。

调度器只决定 Pod 放在哪个节点。Pod 创建时,kubelet 通过容器运行时创建 Pod sandbox;在 Linux 集群中,运行时通常再调用 CNI 插件,为这个网络命名空间配置接口、IP 地址和路由。

Pod sandbox 创建

容器运行时执行 CNI ADD

插件创建接口、分配 IP、写入路由

Pod 可以与集群网络通信

CNI 规范还定义了 DELCHECK 等操作,用于撤销网络配置或检查现有配置。它只规定调用协议,不规定底层必须使用 veth、隧道、BGP 或 eBPF。Calico、Cilium、Flannel 等是具体的 Pod 网络实现或相关产品,不是 CNI 规范本身。

Kubernetes 网络模型希望每个 Pod 拥有独立的集群内 IP,并能与其他 Pod 直接通信。跨节点连通具体使用路由、隧道还是底层网络能力,由选用的 Pod 网络实现决定。

CNI 不等于 Service

可以用“铺路”和“选目标”区分它们:

组件解决的问题
CNI / Pod 网络实现Pod 如何获得网络接口和 IP,数据包如何到达另一个 Pod
Service如何为一组不断变化的后端提供稳定入口
EndpointSlice当前有哪些后端地址,以及这些端点是否就绪、是否正在终止
kube-proxy 或替代数据面如何捕获发往 Service 虚拟 IP 的流量并选择后端
集群 DNS如何把稳定的 Service 名称解析为 ClusterIP 或端点地址
NetworkPolicy声明哪些网络流量被允许;通常仍由 Pod 网络实现负责执行

CNI 协议本身不追踪 Service 后端,也不负责服务发现。不过 Cilium 等完整网络方案可以同时实现 Pod 网络、NetworkPolicy 和 Service 数据面,因此部署产品的职责可能重叠,协议层的边界仍然存在。

Service 有哪些地址

Service 是 Kubernetes API 对象,不是一个持续运行、能够“重启”的进程。不同 Service 类型暴露的地址也不同:

类型客户端使用的入口说明
ClusterIP集群内部虚拟 IP 和 DNS 名称默认类型,只在集群网络内可达
NodePortNodeIP:NodePort在每个节点开放一个端口,同时仍有 ClusterIP;默认端口范围可配置
LoadBalancer外部负载均衡器提供的入口依赖云厂商或其他负载均衡实现,不保证一定是公网 IP
ExternalNameService DNS 名称DNS 返回指向 spec.externalName 的 CNAME,不设置流量代理
Headless Service一个或多个端点地址设置 clusterIP: None,没有统一 VIP,也不由 kube-proxy 负载均衡

普通 Service 的完整 DNS 名称通常形如:

my-service.default.svc.cluster.local

其中 cluster.local 是常见的集群域名,但可以被管理员修改。同一 Namespace 内通常直接使用 my-service;跨 Namespace 可以使用 my-service.other-namespace,不必一定写完整 FQDN。

ClusterIP 为什么看起来不会变

控制平面在创建普通 Service 时,从 Service IP 地址池中动态分配 ClusterIP,也允许创建时显式申请一个合法且未占用的地址。这个地址保存在 Service 对象中,不绑定到某块真实网卡。

只要同一个 Service 对象仍然存在,后端 Pod 重建、扩缩容、节点重启或 kube-proxy 重启都不会改变它的 ClusterIP。删除 Service 再重新创建则是一个新对象,动态分配的地址可能不同;显式申请原地址也只有在地址仍合法且未被占用时才会成功。

应用配置仍应优先使用 Service DNS 名称,而不是硬编码 ClusterIP。DNS 名称表达的是服务身份,也更容易适应集群迁移或 Service 重建。

EndpointSlice 记录真正的后端

带 selector 的 Service 会选中一组 Pod,控制平面的 EndpointSlice controller 据此维护 EndpointSlice。它记录后端 IP、端口、就绪状态、终止状态和拓扑信息,是 kube-proxy 路由 Service 内部流量时的事实来源。

Service: 10.96.0.20:80

EndpointSlice
  - 10.244.1.5:8080  ready
  - 10.244.2.7:8080  ready

Pod 的 readiness 变化通常会反映到端点的 ready 条件中,Service 数据面通常不把未就绪端点作为正常目标。但 publishNotReadyAddresses、所有端点都在终止等配置和边界情况会改变行为,因此不能简单理解成“EndpointSlice 里永远只有健康 Pod”。旧的 Endpoints API 已被 EndpointSlice 取代并进入弃用阶段,新实现应以 EndpointSlice 为主。

请求怎样从 Service 名称到达 Pod

以 Pod 访问 my-service 为例,完整链路可以拆成两段。

第一段是服务发现:

  1. kubelet 根据 Pod 的 DNS policy 配置容器内的 /etc/resolv.conf。常见的 ClusterFirst 策略会指向集群 DNS,并加入当前 Namespace 等搜索域。
  2. 应用查询 my-service 时,DNS 客户端根据搜索域尝试 my-service.<当前 namespace>.svc.<cluster-domain>
  3. CoreDNS 是常见的集群 DNS 实现。它根据 Kubernetes 中的 Service 和 EndpointSlice 信息回答查询:普通 Service 返回 ClusterIP,Headless Service 返回端点地址。

第二段是 Service 转发:

应用查询 Service DNS 名称

集群 DNS 返回 ClusterIP

数据包发往 ClusterIP:ServicePort

kube-proxy 或替代数据面选择 EndpointSlice 中的端点

目标被重定向到 PodIP:TargetPort

CNI 配置的 Pod 网络把数据包送到目标 Pod

默认实现中,每个节点上的 kube-proxy 监听 Service 和 EndpointSlice 变化,再把转发状态同步到节点。Linux 上的 kube-proxy 支持以下数据面:

  • iptables:当前仍是默认模式;
  • nftables:面向较新 Linux 内核的实现,也是官方推荐用于替代 IPVS 的方向;
  • ipvs:从 Kubernetes 1.35 起已弃用,不应再作为新集群的首选;
  • eBPF 数据面:不是 kube-proxy 的一种 mode,而是 Cilium 等实现提供的 kube-proxy 替代方案。

因此,“Service 流量的本质就是 DNAT”适合解释常见 kube-proxy 路径,但不是跨平台、跨实现的完整定义。更稳妥的理解是:Service 数据面捕获发往虚拟入口的流量,按 EndpointSlice 和流量策略选择端点,再由 Pod 网络完成投递。

排查时按层定位

Pod 长时间停在 ContainerCreating 且 Events 出现 sandbox、CNI 或 IP 分配错误时,应检查 CNI DaemonSet、节点网络状态和地址池,而不是先改应用镜像。

如果 Pod 已有 IP 且 PodIP 互通,但 Service 名称解析失败,应检查 Pod 的 DNS policy、/etc/resolv.conf 和集群 DNS。DNS 能返回 ClusterIP、直接访问 PodIP 也正常,但访问 Service 失败时,再检查 Service selector、EndpointSlice 和 kube-proxy 或替代数据面。

参考:CNI SpecificationServices, Load Balancing, and NetworkingServiceEndpointSlicesDNS for Services and PodsVirtual IPs and Service ProxiesNetwork Policies