☰
K8s网络体系全解:从Pod通信到Service、Ingress与网络策略
2026/10/1 11:46:48 网站建设 项目流程

很多同学接触 Kubernetes 的第一道坎,往往不是 YAML 语法,而是网络。明明 Pod 已经 Running 了,从外面就是访问不到;同一个 Service 下挂着两个副本,一个通一个不通;有人手滑改了节点上的 iptables 规则,整个集群的 Service 全挂了。这些场景我都在生产环境里一遍遍遇到过。K8s 网络是一整套由 Pod 网络、Service、DNS、Ingress、NetworkPolicy 和 CNI 插件共同组成的体系,想理清楚它,得先搞清楚每一层到底在解决什么问题,再到底层是怎么实现的。这篇文章就把这条主线完整梳理一遍,从概念到插件、从安全策略到排障手法都会涉及,适合刚上手 K8s 的开发和运维,也适合准备面试时快速建立整张地图。

1. K8s 网络到底在解决什么问题

1.1 容器时代为什么网络会乱套

先回想一下没有容器编排的时候。一个应用跑在虚拟机或物理机上,机器长年不变, IP 是稳定的,只要知道“这台机器在哪儿”,就能把流量送过去。这是传统网络运维最熟悉的世界。

容器时代把这套模式彻底打碎了。容器本身是进程级别的隔离,拥有独立的网络命名空间(Network Namespace),有自己的 eth0、自己的 IP。没有调度器介入时,你可以手动管理几台机器上的容器,问题不大。但 Kubernetes 引入调度器之后,Pod 会被随机安排到任何一台节点上,同一份 Deployment 的两个副本可能一个在华北节点的服务器 A,另一个在另一个机房的服务器 B。

更麻烦的是,Pod 会重建、会迁移。节点宕机、镜像更新、资源不足,任何一个原因都可能导致 Pod 被删除,然后在另一台节点上重新调度出来。新 Pod 的 IP 大概率跟原来不一样。如果沿用传统的“写死 IP”的通信方案,运维人员根本没法维护。

K8s 网络要解决的核心问题就是:让集群内部所有工作负载,像在同一个“大二层交换机”上一样可以直接通信;同时给外部一个稳定、可管理的访问入口。要做到这件事,不能靠人工绑定 IP,而是要靠一层抽象——把物理节点的网络细节隐藏起来。

1.2 K8s 网络模型的三大原则

Kubernetes 官方对集群网络提出了几条硬性要求,很多刚接触的同学觉得抽象,其实它们非常容易理解:

  • 所有 Pod 之间可以在没有 NAT(网络地址转换)的情况下互相通信。
  • 所有节点可以直接与所有 Pod 通信,Pod 也可以访问节点。
  • Pod 看到的自己的 IP,和其他人看到的 Pod IP,必须是同一个地址。

为什么强调“不需要 NAT”?因为 NAT 会破坏源地址信息,运维排障的时候很难追溯请求到底是从哪个 Pod 发出来的。而且 NAT 设备本身容易成为性能瓶颈和高可用隐患。K8s 的设计思路是“像对待传统主机一样对待 Pod”,给每个 Pod 一个全局可路由的 IP,让所有网络行为尽量透明。

这里要特别说明,K8s 本身并不实现网络。它只是定了一套标准,底层的数据通路完全交给 CNI 插件。CNI(Container Network Interface)插件负责干实事:在 Pod 创建时分配 IP、创建虚拟网卡、设置路由,在 Pod 删除时回收资源。你在集群里部署的 Flannel、Calico、Cilium,就是这一层的具体实现。

1.3 先建立一条流量主线

学习 K8s 网络,我建议不要一上来就背名词,而是先把一整条流量路径画出来。画出下面这条主线,之后所有细节都是在给这条路添砖加瓦:

用户请求 ↓ Ingress Controller(域名/路径路由,TLS 终止) ↓ Service(ClusterIP,虚拟 IP) ↓ Endpoints(一个或多个 Pod IP) ↓ Pod(容器网络命名空间) ↓ 容器进程监听端口

在这条主线上,每一层都有自己的规则和组件:Ingress Controller 负责 L7 路由,Service 负责 L4 负载均衡,Endpoints 维护后端列表,Pod 网络由 CNI 保证连通性。NetworkPolicy 则像一个闸门,横在流量路径上,决定哪些流量允许经过。

有了这条主线,后面再遇到任何网络问题,都可以按图索骥:先看流量到哪一步断了,再针对性排查对应组件。这是我在生产环境里效率最高的排障方式。

2. 核心网络对象逐个拆解:Pod、Service、DNS 与 Ingress

2.1 Pod 网络:为什么每个 Pod 都必须要有一个独立 IP

在 K8s 中,Pod 是网络资源的最小单位。一个 Pod 里可以跑多个容器,但这些容器共享同一个网络命名空间。K8s 是怎么做到的呢?秘密在“pause 容器”上。

每个 Pod 创建时,kubelet 会先启动一个基础设施容器,也就是 pause 容器。pause 容器在创建后便持有整个 Pod 的网络命名空间、IP 地址和端口监听权。后面的业务容器在启动时,会通过--network=container:pause容器ID直接加入这个命名空间。这样做的结果是,同一个 Pod 里的多个容器通过 localhost 就能互相访问,对外则共享同一个 Pod IP。

Pod IP 一般由 CNI 插件在节点上分配。以常见的 Flannel 为例,节点上会创建一个网桥(如 cni0),每个 Pod 通过一对 veth 虚拟网线接入网桥。流量从 Pod 的 eth0 发出,经过 veth 到达节点上的网桥,再根据路由规则进入真正的物理网卡并传出节点。

生产环境里要记住几个容易犯迷糊的点:

  • 不要把 Pod IP 写死到任何配置里。除非你用了 StatefulSet 且明确知道自己在做什么,否则 Pod IP 是易变的。
  • 集群里所有服务都应该通过 Service 或 DNS 访问,不要让业务代码直接依赖 Pod IP。
  • 同一个节点上的 Pod 通信和跨节点 Pod 通信是两条路径,排障时先判断源和目标在不在同一个节点。

2.2 Service 与 ClusterIP:为什么要用虚拟 IP 做负载均衡

Pod IP 不稳定,直接访问 Pod IP 是不现实的。Service 就是 K8s 提供的稳定访问抽象。

Service 通过标签选择器(label selector)匹配一组 Pod,并实时维护一个 Endpoints 列表。你不需要关心后端 Pod 怎么变,只要请求打到 Service 的虚拟 IP 上,K8s 就会把流量转发到某个健康的后端 Pod。

Service 的 ClusterIP 是一个虚拟 IP,在集群内部可达。它不会绑定到某个具体的设备上,而是由 kube-proxy 组件把转发规则写入本机的 iptables 或 IPVS 中。流量到达节点时,内核网络栈按规则做 DNAT,把目标地址从 ClusterIP 换成真实的 Pod IP。

这里有两个常见的面试点和排障点,我展开说一下。

第一,iptables 模式。这是 K8s 早期默认也很常见的模式。每个 Service 的转发规则由一串 iptables 链组成。当集群里 Service 数量很多时,规则数量会爆炸,新连接匹配规则的开销会显著上升。但优点是逻辑简单,几乎不需要额外配置。

第二,IPVS 模式。IPVS 工作在 Linux 内核的连接跟踪层,使用哈希表来管理转发规则,性能远好于 iptables,而且支持加权轮询等负载均衡算法。K8s 支持启用 IPVS 模式,只要在 kube-proxy 启动参数里指定--proxy-mode=ipvs。

在查看 iptables 模式的规则时,有一组命令我几乎每次排障都会用:

# 在节点上查看 Service 对应的 NAT 规则 iptables -t nat -L -n | grep <ClusterIP> iptables -t nat -L -n | grep <Namespace>.<ServiceName>

无论哪种模式,Service 都只处理四层(TCP/UDP)的负载均衡,不关心 HTTP 路径、域名这些七层信息。如果需要七层路由,要用到后面的 Ingress。

2.3 NodePort、LoadBalancer 与 ExternalIP 的区别

Service 默认只有集群内部可达。要让外部访问,K8s 提供了几种方式。

NodePort 是最容易理解的方式:它在集群的每个节点上开一个端口(默认范围 30000-32767),外部流量到达任意节点的这个端口后,会被 kube-proxy 转发到对应的 Service,再转到后端 Pod。它的缺点是端口数量有限,每个 Service 占一个端口,而且外部用户需要自己处理节点故障,因为访问的是一个固定的“节点 IP + 端口”。

LoadBalancer 是云环境下的标准方式。Service 类型设为 LoadBalancer 后,云厂商的控制器(CCM)会调用云 API 创建一个负载均衡器,把流量转发到各节点的 NodePort 或直接指向后端 Pod。这种方式把“访问入口高可用”这件事外包给了云平台,你不需要自己管理节点 IP。

热词里提到的 ExternalIPs,则是一个经常让人掉坑里的概念。它允许你在 Service 上手动指定一个或多个 IP:

apiVersion: v1 kind: Service metadata: name: web-lb spec: type: ClusterIP externalIPs: - 192.168.1.20 ports: - port: 80 targetPort: 8080

配置完成后,kube-proxy 会在所有节点上生成规则,把发往 192.168.1.20:80 的流量 DNAT 到后端 Pod。但是注意,外部流量要能到达这个 IP,必须由你在集群外部把路由指到某一台节点,或者由上游负载均衡器把流量打到该 IP。很多人配了 externalIPs 以后发现外部依然不通,原因就是没有解决外部路由问题。ExternalIPs 本身不会把 IP 绑定到任何网卡上,它只是一个转发规则。

生产环境的选型建议很简单:如果是云上,优先用 LoadBalancer;如果是在自建机房或内网环境,可以用 NodePort,或者让接入层的 Nginx/HAProxy 直接把流量转发到 Service 的 ClusterIP(如果接入层本身就在集群内部)。ExternalIPs 更多用于一些特殊场景,比如你需要把某个已有 IP 迁移到 Service 上,又不想改服务配置。

2.4 集群 DNS 与服务发现

K8s 里几乎不需要手工维护服务地址,靠的就是内置 DNS。集群里一般跑着 CoreDNS,它监听 API Server,把 Service 和 Pod 等对象的名字解析成 IP。

普通 Service 的完整 DNS 名称格式是:

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

例如在prod命名空间里有一个名为order-api的 Service,集群内的 Pod 可以直接访问order-api.prod.svc.cluster.local,或者简写为order-api.prod。如果在同一个命名空间内,直接写order-api也可以,因为 Pod 的 resolv.conf 里配置了搜索域。

这里有一个经常被忽略的性能问题:K8s 默认给 Pod 设置的ndots: 5,会使得解析order-api这样的短名字时,先在多个搜索域下拼成全名去查询,从而产生额外的 DNS 请求。如果业务对延迟非常敏感,DNS 解析的耗时可能会高到不可接受。此时可以通过dnsConfig调整 Pod 的搜索域和ndots,但前提是你确认业务不依赖默认搜索域。

Headless Service 也值得单独说一句。当 Service 的clusterIP被设置为None时,这个 Service 被称为 headless Service。它没有虚拟 IP,DNS 会直接返回后端所有 Pod 的 IP。这种方式适合某些需要客户端自行做负载均衡的场景,比如数据库连接池、gRPC 客户端。但要注意,客户端必须能正确处理多个 IP 的故障切换,否则一个后端挂了,调用依然会失败。

2.5 Ingress 与 Ingress Controller:七层入口的实现方式

很多初学者把 Ingress 当成一个具体的服务进程,其实不对。Ingress 只是一个 API 对象,负责描述“外部 HTTP/HTTPS 请求如何路由到内部 Service”。真正干活的是 Ingress Controller,比如 nginx-ingress、traefik、ingress-nginx 等。

Ingress 的典型配置如下:

apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: web-ingress spec: rules: - host: api.example.com http: paths: - path: /v1 pathType: Prefix backend: service: name: order-api port: number: 8080

Ingress Controller 会定期从 API Server 拉取 Ingress 对象,把规则转成自己的反向代理配置(Nginx 风格的就是生成 nginx.conf),并热加载。用户流量进来后,Controller 根据 Host 和 Path 匹配到对应的 Service,再转发到后端。

在架构上,Ingress Controller 通常需要暴露到集群外部。最常见的方式是给 Controller 本身创建一个 NodePort 或 LoadBalancer 类型的 Service。也就是说,流量链路变成:

用户 → 云负载均衡器 / 节点端口 → Ingress Controller Pod → Service → 后端 Pod

如果把每个业务 Service 都设置成 NodePort,会带来大量端口管理开销;而统一通过 Ingress 暴露,只需要暴露一个入口,根据域名和路径分流即可。这也是生产集群里最常见的对外方案。Ingress 同时还能承担 TLS 证书终止、限流、日志访问、跨域等网关职责。

也提醒一句:Ingress 资源并不是所有集群默认就有的。裸装 Kubernetes 后没有部署任何 Ingress Controller,创建 Ingress 对象不会有任何实际效果。这是一个非常典型的新手困惑。

3. CNI 插件选型:从 Flannel 到 Cilium,底层网络模型怎么选

3.1 CNI 标准:K8s 怎么把网络交给插件

K8s 自身不实现 Pod 网络,它只负责在 Pod 生命周期里调用一组标准接口,这个接口就是 CNI。CNI 规范定义了一些很简单但很重要的操作:ADD(创建网络)、DEL(删除网络)、CHECK(检查配置)。

Kubelet 在创建 Pod 时,会读取/etc/cni/net.d/目录下的配置文件,找到插件名称和参数,然后调用/opt/cni/bin/下面对应的插件二进制文件。插件收到调用后,会做三件事:

  1. 在网络命名空间里创建虚拟网卡(veth pair 的一端)。
  2. 分配 IP 地址(一般从节点或集群的 Pod CIDR 中分配)。
  3. 配置路由,让 Pod IP 能与其他节点互相访问。

这个设计的好处是解耦。K8s 开发者只需要关心接口,各种网络方案提供商可以自由地实现。也因为这一点,才有了琳琅满目的 CNI 插件。

3.2 常见 CNI 插件的核心原理对比

我把当前用得最多的几个插件放在一起对比,先看一张表:

插件主要网络模型原理特点适合场景注意点
FlannelVXLAN / host-gw节点上加网桥,跨节点用 VXLAN 隧道封装小型集群、学习环境性能有损耗,host-gw 要求节点二层互通
CalicoBGP / IPIP / eBPF每个节点作为路由器,通过 BGP 通告 Pod 网段中大型集群、需要 NetworkPolicyBGP 模式需要物理网络支持,IPIP 有额外封装
CiliumeBPF用 eBPF 程序直接在数据路径上转发,支持 L7 策略大型集群、高性能场景内核版本要求高,学习曲线陡
WeaveUDP / 快速路径自带加密与简易多主机网络小规模、跨云快速组网性能一般,社区热度下降
Kube-OVNOVS / OVN基于 Open vSwitch,支持 QoS、ACL、网关传统企业、复杂网络需求部署和运维成本较高

很多人刚开始接触集群时,默认装的都是 Flannel。它简单,确实适合入门。但当集群规模变大、业务流量变高后,会逐渐感受到 VXLAN 隧道封装带来的额外 CPU 开销。Calico 走的是纯三层路由方案,每个节点像一台路由器一样,通过 BGP 把 Pod 网段通告出去。因为不需要封装,性能比 VXLAN 好不少。Cilium 则把数据路径直接下沉到 eBPF,在 Linux 内核里就能完成转发和策略控制,性能最优,也支持非常丰富的可观测性能力,但内核版本和运维水平要求都比较高。

3.3 二层、三层、Overlay、Underlay,这些词到底是什么意思

理解 CNI 插件之前,得先把网络模型这几个词搞清楚。

Underlay 网络就是物理网络本身,交换机、路由器、物理网卡构成的那张网。Overlay 网络是在这张物理网之上,用隧道协议再构建的一张虚拟网络。比如 VXLAN 就是把原始网络包整个封装成 UDP 报文,多套一层“信封”,穿透物理网络到达目标节点后再解开。信封上写的是源节点和目标节点的物理 IP,里面的内容才是 Pod IP 之间的通信。这种方案的优点是底层网络完全不用改,缺点是封装与解封装会消耗 CPU,而且报文体积变大,可能超过 MTU 导致分片。

BGP 是边界网关协议,它原本是互联网路由器之间交换路由信息的协议。Calico 在启用 BGP 模式时,让每个节点上的 BIRD 进程与物理网络里的交换机建立 BGP 邻居关系,把 Pod 网段宣告出去。这样一来,物理网络的路由器就知道某个 Pod IP 应该从哪个节点路由出去。流量直接走物理网络,不需要额外封装,性能和传统虚拟机方案已经很接近了。

三种模式选型时,最核心的判断依据是:物理网络能不能按你的要求配合。如果只是机房内几台机器,二层互通,Flannel 的 host-gw 就能跑到不错的速度;如果网络设备支持 BGP,Calico 是更好的中大型集群选择;如果追求极致性能又有一批合适的内核版本节点,Cilium 值得投入精力。

3.4 安装 CNI 时最容易出错的地方

很多人在 kubeadm 初始化集群时,会看到--pod-network-cidr这个参数。它定义了 Pod 网段,必须和 CNI 插件预期的网段一致,否则插件分配的 IP 会处于错误网段,导致网络不通。

常见组合是:

# Flannel 对应的经典网段 kubeadm init --pod-network-cidr=10.244.0.0/16 # Calico 的示例网段 kubeadm init --pod-network-cidr=192.168.0.0/16

选网段时,一定要确认 Pod 网段不会和节点所在的物理网段冲突。比如节点本身所在的局域网是 192.168.0.0/16,你又把 Pod 网段定成 192.168.0.0/16,路由会乱套。生产环境里我习惯单独留一段大网段,比如 172.16.0.0/16,并且和节点网段、容器的内部服务网段(Service CIDR)分得清清楚楚。

安装顺序也有讲究。集群初始化完成后,建议先安装 CNI 插件,再部署业务。如果安装 CNI 后 Pod 一直处于 ContainerCreating 或 Pending 状态,先去查看 CNI 相关 Pod 的状态和日志,不要一上来就重启节点。常见的坑包括:镜像拉不下来、插件二进制版本和集群版本不兼容、节点上的 openvswitch/内核模块缺失。

4. NetworkPolicy 安全策略:从零到生产

4.1 为什么默认是“全通”,策略又是怎么生效的

K8s 集群默认的网络策略是“海纳百川”——任何一个 Pod 都可以访问另一个 Pod,任何 Pod 也都可以访问任意外部地址。这在多数业务场景里并不是我们想要的,比如订单服务的 Pod 被攻击者控制后,如果网络全通,攻击者可以直接横向访问数据库 Pod、缓存 Pod,甚至调用管理接口。安全架构里有个零信任的理念,K8s 里落地的关键手段就是 NetworkPolicy。

NetworkPolicy 是一个 API 对象,它通过标签选择器决定哪些 Pod 受策略控制,并允许哪些来源访问这些 Pod(Ingress),允许这些 Pod 访问哪些目的地(Egress)。策略不是 K8s 核心组件直接实现的,而是由 CNI 插件配合实现。默认情况下,Flannel 并不支持 NetworkPolicy,Calico、Cilium 等插件会把这些策略转换成 iptables 规则或 eBPF 程序。

一个容易误解的点:如果没有匹配到任何 NetworkPolicy,Pod 的流量全部放行;但只要有一个 NetworkPolicy 选中了某个 Pod,默认行为就变成“只放行策略中明确允许的流量”。也就是说,一旦开始用策略,就得把规则写全。

4.2 一份最常用的 NetworkPolicy 配置解读

下面这个例子,模拟了一个比较常见的场景:app: web的 Pod 只允许app: api的 Pod 访问它的 8080 端口;它自己只允许访问 DNS 服务对应的 UDP 53 端口。

apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: web-allow-api-only spec: podSelector: matchLabels: app: web policyTypes: - Ingress - Egress ingress: - from: - podSelector: matchLabels: app: api ports: - protocol: TCP port: 8080 egress: - to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system ports: - protocol: UDP port: 53

拆开看几个关键字段:

podSelector选中要控制的 Pod,这里是所有带app: web标签的 Pod。

policyTypes声明策略作用方向。如果只在ingress里写了规则,但policyTypes忘了写 Egress,那么 egress 规则不会生效。反过来,一旦policyTypes里包含 Egress,但 egress 下没有写任何规则,那么这个 Pod 的所有出流量都会被拒绝。这是一个非常容易踩的坑。

ingress.from里可以放多个来源,多个来源之间是“或”的关系;如果同一个来源里同时写了podSelector和namespaceSelector,那这两个条件是“且”的关系。很多人想表达“来自命名空间 nm 且标签为 app:api 的 Pod”,会错误地把两个 selector 直接并列,结果变成并集。正确的写法是把 namespaceSelector 和 podSelector 都放在同一个from条目下,或者用子选择器嵌套表示。

生产环境里推荐的策略思路是:先分别建 default-deny 策略(将命名空间内所有 Pod 纳入,且不添加任何 ingress/egress 规则),再逐步放行白名单。这样即使后续新部署的 Pod 忘了加策略,默认也是拒绝状态,安全性更高。

4.3 生产实践中遇到的几个典型问题

NetworkPolicy 在落地过程中,最常见的问题不是策略写不出来,而是“写了之后反而把正常流量给打挂了”。

第一个高频故障:Egress 策略只允许了访问数据库端口,却忘了放行 DNS 的 UDP 53,结果应用启动时域名解析全部超时。DNS 是隐形的依赖,但凡策略涉及 Egress,一定要把 DNS 这条规则带上。

第二个高频故障:策略里写了namespaceSelector,但不知道在更早的 K8s 版本中,命名空间级别默认没有kubernetes.io/metadata.name这个标签。你可以手动给命名空间打标签,或者确认版本是否支持自动注入。

第三个问题是性能。NetworkPolicy 数量多、规则复杂时,底层的 iptables 规则数会非常庞大,每次流量的匹配开销上升,并发量高时会明显感受到延迟抖动。Calico 和 Cilium 在高并发下优势更明显,尤其 Cilium 的 eBPF 实现本身就在减少 netfilter 层的开销。

第四个问题是排障困难。被 NetworkPolicy 丢弃的流量通常不会出现在业务日志里,看起来就像后端服务假死。排查时除了看 Pod 日志,还要在节点上用tcpdump抓包,或者在支持可观测性的 CNI 里查看 drop 事件。如果策略下发到每个节点,还得确认策略已经在所有节点生效,而不是只更新了一部分节点。

5. 生产环境网络故障排查实战记录

5.1 常见故障现象速查表

在网络问题排障上,经验丰富确实能少走弯路。我整理了一张速查表,遇到问题时先按表格对上号,再深入排查。

现象可能原因排查起点
Pod IP ping 不通CNI 插件异常、节点路由缺失节点路由表、CNI Pod 日志
Service ClusterIP 不通kube-proxy 未运行、iptables 规则丢失、Endpoints 为空kubectl get endpoints、查看 kube-proxy 日志
DNS 解析超时CoreDNS 故障、conntrack 表满、ndots 太多CoreDNS 日志、kubectl get pod -n kube-system
跨节点 Pod 通信失败overlay 隧道故障、MTU 不匹配节点路由表、VXLAN 网卡状态
外部访问 NodePort 失败防火墙未放行、安全组未配置、kube-proxy 异常节点上 curl、tcpdump 抓包
业务偶发超时Pod 探针失败未摘除、conntrack 冲突、后端容量不足Endpoints 状态、conntrack -L

这张表覆盖了大部分线上问题。注意“现象”不等于“根因”,同一个现象背后可能对应完全不同的问题。排障最重要的是按“数据面 → 控制面 → 外部链路”的顺序排查。

5.2 排障三板斧:容器内、节点上、抓包看

实际排障时,我会从三个层面递进操作。

第一步,先在 Pod 内部验证。使用类似这样的命令:

# 进入 Pod 后,用 curl 测本机和外部 kubectl exec -it <pod-name> -- bash curl -v http://<目标IP>:<端口> # 查看 Pod 自己的网卡和路由 kubectl exec -it <pod-name> -- ip addr kubectl exec -it <pod-name> -- ip route

如果 Pod 内部无法安装额外的工具,也可以用nsenter的方式进入 Pod 的网络命名空间。先获取 Pod 的 PID,再进入:

# 获取 Pod 对应进程的 PID(在节点上执行) kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[0].containerID}' # 找到 pause 容器的 PID # 使用 nsenter 进入它的网络命名空间 nsenter -t <PID> -n bash

第二步,在节点上检查数据面。这里重点看 kube-proxy 的规则是否正确。

# 查看 iptables 模式下的规则 iptables -t nat -L -n | grep <ClusterIP> # 查看 ipvs 模式下的规则 ipvsadm -Ln | grep <ClusterIP> # 查看 Service 的后端是否健康 kubectl get endpoints <service-name> -n <namespace> kubectl get pods -o wide -n <namespace>

第三步,用 tcpdump 抓包确认数据是否真正到达预期。抓包时不要一把抓所有流量,要尽量精确:

# 在节点上抓取发往某个 Pod IP 的流量 tcpdump -nn -i eth0 host <PodIP> -w /tmp/pod.cap # 抓取访问某个端口的流量 tcpdump -nn -i any port 8080

抓包文件可以用 Wireshark 分析,也可以直接在终端里看数据包方向。如果包已经到了节点但没到 Pod,问题大概率出在 CNI 或路由;如果包根本没到节点,就要去查上游负载均衡和安全组。

5.3 真实案例复盘:一个 Service 时而通时而不通的根因

之前线上有一个核心服务,客户端反馈偶尔超时,重启 Pod 后立即变好,但过一段时间又复发。最典型的就是“间歇性故障”。

我先照常看了一遍:Service 存在,Endpoints 列表里有两个 Pod,都是 Running 状态。直接 curl 两个 Pod IP,发现一个通一个不通,不通的那个虽然状态是 Running,但最近一次 Probe 已经失败了。也就是说,K8s 的控制面还没把不可用的 Pod 从 Endpoint 中摘除,或者摘除动作有延迟,新连接有一小部分会被打进已经“假死”的 Pod。

根因清楚了:治理策略配置不当。应用本身的 liveness 探针设置得太宽松,导致进程已经无法正常处理新连接,却仍然被认为存活。然后每次故障都表现为“Service 偶发超时”。

这个案例提醒我:Service 的稳定性不光是 kube-proxy 的活儿,探针配置、Endpoints 摘除机制、优雅停机(preStop)这些环节全都参与其中。排障时不要只盯着网络层,控制面的同步延迟往往才是幕后黑手。

类似的还有 conntrack 表满导致的丢包问题。业务量大时,连接跟踪表会被占满,新连接被内核拒绝,现象就是 Service 能连上但随即又断。此时在节点上查看dmesg或/proc/sys/net/netfilter/nf_conntrack_count可以确认。解决办法是提升 conntrack 上限,或者排查是否存在大量短暂连接被异常堆积。

5.4 降低网络故障的长期手段

故障排完不是结束,更重要的是提前预防。这里分享几个我在团队里一直推行的做法。

一是统一网络基线。节点网卡的 MTU、容器网卡的 MTU、隧道网卡的 MTU必须保持合理匹配。MTU 不一致会导致大包被丢弃,最常见表现就是小文件传输正常、大文件传输卡死。

二是建立节点网络巡检。每台节点看网卡丢包率、错误计数、软中断分布,特别是大量使用 overlay 隧道时,软中断打满会直接拉高请求延迟。

三是定期做故障演练。有条件的团队可以用tc命令人为制造丢包或者延迟,看看业务在单条链路劣化时能不能自愈。不要等真正出故障了才第一次思考这个场景。

四是关注 CNI 与内核的兼容矩阵。每次升级集群前,先确认 CNI 版本是否支持新内核,避免升级后出现诡异的网络问题。

6. 学习路线与常见面试题复盘

6.1 面试中必问的 K8s 网络题

这些年我面试别人和被别人问,K8s 网络的问题绕不开这几道。我把问题和核心回答思路一起列出来。

第一题:ClusterIP、NodePort、LoadBalancer 有什么区别?核心思路是讲清楚访问范围和数据路径:ClusterIP 只在集群内可达;NodePort 在所有节点上暴露端口,面向集群外;LoadBalancer 依赖云平台的负载均衡器,最终流量还是会进到节点或 Pod。要能画出流量路径。

第二题:Pod A 访问同一个命名空间里的 Service B,完整链路是什么?回答时需要提到 DNS 解析、ClusterIP、kube-proxy 规则、Endpoints、Pod IP、CNI 数据面。能把这条链路讲明白,说明对网络体系有系统性理解。

第三题:Flannel 和 Calico 的核心区别?回答要落到“封装 vs 路由”上。Flannel 默认用 VXLAN 隧道封装,Calico 用 BGP 通告路由,数据链路更直接。另外要提 Calico 支持 NetworkPolicy,Flannel 默认不支持。

第四题:iptables 模式和 IPVS 模式的差异?核心是性能和调度能力。iptables 的链式匹配复杂度高,IPVS 使用哈希表,性能更好,且支持更丰富的负载均衡算法。

第五题:NetworkPolicy 不生效可能是什么原因?常见答案有:CNI 不支持、podSelector 标签写错、policyTypes 未声明、策略没覆盖到实际生效的命名空间、节点上的规则未同步。回答“先查插件、再查标签、再抓包”会显得很有实战经验。

第六题:externalIPs 的坑在哪里?核心是外部路由依赖。K8s 只负责生成转发规则,不负责把 externalIP 绑定到网卡,也不负责通知路由器。外部 IP 必须先能路由到集群节点,否则配置了也是白配。

6.2 学习路径:别只看文档,要动手摸一遍

很多人让我推荐 K8s 网络的学习资料。官方文档毫无疑问是第一优先级,尤其是概念部分,值得反复读。国内很多团队用的是《Kubernetes 权威指南》这本书,可以结合官方文档一起看。但无论看什么书,都建议搭配动手实验来消化。

实验路径我建议这样安排:

  1. 准备三台虚拟机或几台裸金属节点,用 kubeadm 搭一个集群,先装 Flannel,验证跨节点 Pod 通信。
  2. 把 Flannel 卸载,换成 Calico,观察路由表变化,对比两种模式下的ip route。
  3. 创建几个不同命名空间的 Service,测试 DNS 解析和 ClusterIP 访问。
  4. 写一个 NetworkPolicy 策略,观察被 drop 的流量表现。
  5. 有条件的话,再深入一下 eBPF 的基础概念,不必一开始就啃源码,先看 Cilium 的数据路径示意图。

网络知识本身也是一样的逻辑:先理解经典协议栈,再深入内核优化。不要一上来就研究 eBPF,先搞懂 VXLAN 的封包结构、iptables 的链和表、conntrack 的连接状态,这些东西才是基本功。

6.3 给新人的最后一个建议:学会画流量路径

说了这么多,如果只能留一条建议,我会说“画流量路径”。

每次遇到网络问题,我第一件事不是在键盘上敲命令,而是在纸上把从客户端到后端的完整路径画出来,标注清楚每一跳的源 IP、目标 IP、源端口、目标端口,以及经过的组件(Ingress、Service、kube-proxy、CNI、Pod)。这个习惯看起来简单,但能避免大量的无效排查。

很多“玄学网络故障”,最后顺着这条路径走一遍都能定位到具体环节:要么是 DNS 层在某个搜索域上卡了时间,要么是 iptables 规则被更新顺序冲掉,要么是后端 Pod 探针失败但 Endpoints 尚未摘除。网络这种东西,本质是把一条一条链路串起来。别怕绕,绕明白了,整个 K8s 网络体系也就真的拿下了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询