很多同学接触 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 插件。CNIContainer Network Interface插件负责干实事在 Pod 创建时分配 IP、创建虚拟网卡、设置路由在 Pod 删除时回收资源。你在集群里部署的 Flannel、Calico、Cilium就是这一层的具体实现。1.3 先建立一条流量主线学习 K8s 网络我建议不要一上来就背名词而是先把一整条流量路径画出来。画出下面这条主线之后所有细节都是在给这条路添砖加瓦用户请求 ↓ Ingress Controller域名/路径路由TLS 终止 ↓ ServiceClusterIP虚拟 IP ↓ Endpoints一个或多个 Pod IP ↓ Pod容器网络命名空间 ↓ 容器进程监听端口在这条主线上每一层都有自己的规则和组件Ingress Controller 负责 L7 路由Service 负责 L4 负载均衡Endpoints 维护后端列表Pod 网络由 CNI 保证连通性。NetworkPolicy 则像一个闸门横在流量路径上决定哪些流量允许经过。有了这条主线后面再遇到任何网络问题都可以按图索骥先看流量到哪一步断了再针对性排查对应组件。这是我在生产环境里效率最高的排障方式。2. 核心网络对象逐个拆解Pod、Service、DNS 与 Ingress2.1 Pod 网络为什么每个 Pod 都必须要有一个独立 IP在 K8s 中Pod 是网络资源的最小单位。一个 Pod 里可以跑多个容器但这些容器共享同一个网络命名空间。K8s 是怎么做到的呢秘密在“pause 容器”上。每个 Pod 创建时kubelet 会先启动一个基础设施容器也就是 pause 容器。pause 容器在创建后便持有整个 Pod 的网络命名空间、IP 地址和端口监听权。后面的业务容器在启动时会通过--networkcontainer: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-modeipvs。在查看 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 上手动指定一个或多个 IPapiVersion: 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。它没有虚拟 IPDNS 会直接返回后端所有 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: 8080Ingress 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/下面对应的插件二进制文件。插件收到调用后会做三件事在网络命名空间里创建虚拟网卡veth pair 的一端。分配 IP 地址一般从节点或集群的 Pod CIDR 中分配。配置路由让 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 就能跑到不错的速度如果网络设备支持 BGPCalico 是更好的中大型集群选择如果追求极致性能又有一批合适的内核版本节点Cilium 值得投入精力。3.4 安装 CNI 时最容易出错的地方很多人在 kubeadm 初始化集群时会看到--pod-network-cidr这个参数。它定义了 Pod 网段必须和 CNI 插件预期的网段一致否则插件分配的 IP 会处于错误网段导致网络不通。常见组合是# Flannel 对应的经典网段 kubeadm init --pod-network-cidr10.244.0.0/16 # Calico 的示例网段 kubeadm init --pod-network-cidr192.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 受策略控制并允许哪些来源访问这些 PodIngress允许这些 Pod 访问哪些目的地Egress。策略不是 K8s 核心组件直接实现的而是由 CNI 插件配合实现。默认情况下Flannel 并不支持 NetworkPolicyCalico、Cilium 等插件会把这些策略转换成 iptables 规则或 eBPF 程序。一个容易误解的点如果没有匹配到任何 NetworkPolicyPod 的流量全部放行但只要有一个 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 支持 NetworkPolicyFlannel 默认不支持。第四题iptables 模式和 IPVS 模式的差异核心是性能和调度能力。iptables 的链式匹配复杂度高IPVS 使用哈希表性能更好且支持更丰富的负载均衡算法。第五题NetworkPolicy 不生效可能是什么原因常见答案有CNI 不支持、podSelector 标签写错、policyTypes 未声明、策略没覆盖到实际生效的命名空间、节点上的规则未同步。回答“先查插件、再查标签、再抓包”会显得很有实战经验。第六题externalIPs 的坑在哪里核心是外部路由依赖。K8s 只负责生成转发规则不负责把 externalIP 绑定到网卡也不负责通知路由器。外部 IP 必须先能路由到集群节点否则配置了也是白配。6.2 学习路径别只看文档要动手摸一遍很多人让我推荐 K8s 网络的学习资料。官方文档毫无疑问是第一优先级尤其是概念部分值得反复读。国内很多团队用的是《Kubernetes 权威指南》这本书可以结合官方文档一起看。但无论看什么书都建议搭配动手实验来消化。实验路径我建议这样安排准备三台虚拟机或几台裸金属节点用 kubeadm 搭一个集群先装 Flannel验证跨节点 Pod 通信。把 Flannel 卸载换成 Calico观察路由表变化对比两种模式下的ip route。创建几个不同命名空间的 Service测试 DNS 解析和 ClusterIP 访问。写一个 NetworkPolicy 策略观察被 drop 的流量表现。有条件的话再深入一下 eBPF 的基础概念不必一开始就啃源码先看 Cilium 的数据路径示意图。网络知识本身也是一样的逻辑先理解经典协议栈再深入内核优化。不要一上来就研究 eBPF先搞懂 VXLAN 的封包结构、iptables 的链和表、conntrack 的连接状态这些东西才是基本功。6.3 给新人的最后一个建议学会画流量路径说了这么多如果只能留一条建议我会说“画流量路径”。每次遇到网络问题我第一件事不是在键盘上敲命令而是在纸上把从客户端到后端的完整路径画出来标注清楚每一跳的源 IP、目标 IP、源端口、目标端口以及经过的组件Ingress、Service、kube-proxy、CNI、Pod。这个习惯看起来简单但能避免大量的无效排查。很多“玄学网络故障”最后顺着这条路径走一遍都能定位到具体环节要么是 DNS 层在某个搜索域上卡了时间要么是 iptables 规则被更新顺序冲掉要么是后端 Pod 探针失败但 Endpoints 尚未摘除。网络这种东西本质是把一条一条链路串起来。别怕绕绕明白了整个 K8s 网络体系也就真的拿下了。