
这个标题拆开看很有意思前半截“Gemini永久会员”跟技术本身没有一毛钱关系大概率是蹭热搜的标题党写法真正值得反复琢磨的是后半句——在Kubernetes里创建Pod系统一定是先创建pause容器然后才轮到CNI插件把网络配置到位。这句话听起来简单但它背后串起了kubelet、CRI、containerd、网络命名空间、CNI规范一整条链路。今天这篇文章就把这条链路从头到尾掰碎了讲。这篇文章适合两类人。第一类是刚入门Kubernetes想搞清楚Pod到底是怎么“攒”出来的新手第二类是集群已经跑起来了但遇到Pod网络时通时不通、DNS偶尔抽风、IP没法分配这类问题想系统搞懂底层原理的人。把“先有pause再有网络然后才是业务容器”这条顺序刻进脑子里你排查Pod网络问题的速度会比别人快一个量级。1. 一个Pod的诞生流程别把“先pause再CNI”当成一句话1.1 调用链从kubelet开始Kubernetes集群里所有对Pod的操作本质上都是对API Server的描述性请求。当你执行kubectl apply提交一个Deployment后经过控制器、调度器一系列流转最终会有一个Pod对象被分配到某个节点上。节点上真正干活的组件叫kubelet它通过Watch机制发现“这个Pod归我了”然后开启创建流程。kubelet本身不会直接创建容器。它通过CRIContainer Runtime Interface容器运行时接口和容器运行时通信。CRI是Kubernetes定义的一套gRPC接口协议把“拉镜像”“创建容器”“启停容器”这些底层操作全部抽象成标准方法。目前最常见的运行时是containerd也有部分环境还在用CRI-O。kubelet把Pod定义翻译成CRI请求发给containerdcontainerd再想办法把容器创建出来。这里有个容易混淆的点CRI有两个重要的服务一个是ImageService负责拉取和管理镜像另一个是RuntimeService负责容器和Sandbox的生命周期管理。创建Pod时kubelet调用的核心接口是RuntimeService.RunPodSandbox。这个接口的名字就暗示了创建Pod的第一步不是直接拉业务镜像而是先创建一个“沙箱”。1.2 RunPodSandbox这一步到底做了什么RunPodSandbox是理解整条链路的钥匙。在containerd里Sandbox这个词就是Pod的意思而Sandbox对应的实体容器正是pause容器。containerd收到RunPodSandbox请求后会依次做这么几件事第一准备Sandbox的配置包括Pod的ID、命名空间、资源限制等元数据第二通过CRI的ImageService调用确保pause镜像存在本机如果不存在就拉取第三基于pause镜像创建一个容器实例这个实例就是我们在节点上用crictl ps看到的那种POD形态的容器第四启动pause容器通过shim进程把pause跑起来第五在启动Sandbox的过程中调用CNI插件执行网络配置。整个过程用一句话概括pause容器先被创建并启动这一个容器就是整个Pod的网络基座随后CNI在这个基座上架桥铺路最后kubelet才逐个创建业务容器。你可以把Pod理解成一栋公寓楼。pause容器是这栋楼的地基和公共管廊CNI负责把水电网络接到楼里业务容器则是住进楼里的住户。没有地基住户不可能入住没有网络接入住户住进来也是断网状态。Kubernetes不允许业务容器先于pause容器出现这是由CRI接口的设计和kubelet的调用顺序双重保证的。1.3 网络配置为什么必须卡在pause之后、业务容器之前在containerd的实现里网络配置发生在Sandbox启动阶段。也就是说当RunPodSandbox返回成功时pause容器已经启动CNI已经执行完毕Pod的IP已经分配好整个网络命名空间已经处于可用状态。为什么必须在这个时间点做因为业务容器在创建时需要直接“加入”pause容器的网络命名空间。Kubernetes要求一个Pod内的所有容器共享同一个网络命名空间这意味着它们共用同一个IP、同一套端口空间、同一个路由表。如果CNI配置拖到业务容器创建之后再做业务容器启动时连IP都拿不到进程起来也不知道该监听的网络环境是什么样。另外CNI的配置结果还要通过CRI返回给kubeletkubelet会把这些信息如Pod IP更新到API Server的状态里。如果网络配置失败RunPodSandbox会直接报错kubelet不会继续执行后续的创建容器步骤Pod就会停留在ContainerCreating状态。这也是为什么排障时看到Pod卡在ContainerCreating第一反应应该去看Sandbox和CNI而不是盯着业务容器。2. pause容器整张网络拓扑的“地基”2.1 pause到底是什么为什么只有几百KBpause容器的全称叫“infrastructure container”中文圈子里常叫“基础容器”“沙箱容器”。它使用的镜像通常是registry.k8s.io/pause:3.9镜像体积只有几百KB里面只有一个静态编译的pause二进制文件。这个二进制做的事情简单到令人发指启动后先拉起PID 1进程然后不断处理信号支撑整个Pod的生命周期。它不承载任何业务逻辑不监听端口不写日志唯一的任务就是“以容器形态存在”。但恰恰是这个“空壳”成了整个Pod的基石。为什么不用业务容器来承担这个角色因为业务容器的镜像体积大、启动慢、生命周期不稳定。Pod需要的是一个能最早启动、最晚退出、绝对不会崩溃的“占位符”。pause镜像设计目标就是极小、极稳、启动极快这样才能保证在业务容器就绪之前整个网络拓扑已经有了一个可靠的锚点。2.2 三大职责拆解pause容器的职责可以拆成三块。第一持有网络命名空间。pause容器是整个Pod网络命名空间的“业主”业务容器都是“租客”。租客搬进来要开通水电业主必须先签好房子。CNI配置网络时操作的目标就是pause容器的网络命名空间。第二作为PID命名空间里的PID 1。Pod内所有容器共享PID命名空间时pause容器是第一个进程也是孤儿进程回收者。当某个业务容器的子进程因为父进程退出而变成孤儿时这些进程会被过继给PID 1也就是pause进程由它负责回收和清理避免僵尸进程堆积。第三信号管理的总闸。用户执行docker stop或kubectl delete pod时最终会向Pod发送信号。pause作为主进程接收这些信号然后决定整个Pod如何退出。业务容器各自退出时产生的信号也会统一由pause兜底处理。这个设计让Pod作为一个整体可以被优雅终止而不是各容器各跑各的。2.3 Pod内共享哪些命名空间一个Pod里的容器并非共享所有内核命名空间而是有选择地共享。最常见的共享目标是网络命名空间network namespace所有容器看到同一个网卡、同一个IP、同一个路由表。其次是UTS命名空间共享同一个主机名还有IPC命名空间共享System V IPC和POSIX消息队列这保证了容器间可以通过共享内存通信。PID命名空间默认在Kubernetes里也是共享的在Pod维度为true时这也是pause成为PID 1的条件。至于mount命名空间考虑到安全和隔离需求通常是各容器独立的不会共享文件系统挂载视图。理解这个表后你就会明白为什么Pod内两个容器可以用localhost互相访问——因为它们在同一个网络命名空间里。3. CNI交互组装Pod网络的幕后流程3.1 CRI与CNI怎么握手CRI和CNI是Kubernetes生态里两个容易混淆的接口。CRI管容器的生命周期CNI管容器网络的配置。两者在一条调用链上协作kubelet通过CRI请求运行时创建Sandbox运行时在Sandbox启动过程中调用CNI插件完成网络配置。containerd内部对CNI的接入非常成熟。它默认会在节点上的/etc/cni/net.d/读取网络配置文件在/opt/cni/bin/读取CNI插件二进制。kubelet启动时也可以通过--cni-bin-dir和--cni-conf-dir指定这两个目录。生产环境中Calico、Cilium、Flannel等网络方案安装后都会在这两个目录里写入自己的配置和插件。整个流程可以概括为containerd启动Sandbox时先从配置目录读取CNI网络配置列表conflist然后按配置里的插件链逐个调用插件二进制比如先调用calico插件再调用portmap插件。每个插件执行ADD操作把网卡、IP、路由这些网络要素装配到pause容器的网络命名空间里。3.2 CNI ADD的输入输出全解析CNI插件的执行方式很有意思它不是通过HTTP调用而是runtime直接执行插件二进制文件通过环境变量传入参数通过标准输入传入网络配置JSON通过标准输出返回执行结果。这种方式轻量、通明也方便调试。ADD操作必须传入的环境变量包括CNI_COMMAND操作类型ADD、DEL、CHECK、GC、VERSION等。CNI_CONTAINERID容器ID在Kubernetes场景下就是Sandbox的ID。CNI_NETNS网络命名空间的路径形如/proc/pid/ns/net其中pid是pause容器的进程ID。CNI_IFNAME要创建的网卡名称通常是eth0。CNI_PATH插件被调用的路径列表方便插件调用链。网络配置JSON则会从标准输入传入格式大致如下{ cniVersion: 0.3.1, name: k8s-pod-network, type: calico, ipam: { type: host-local, subnet: 10.244.0.0/16 } }插件执行ADD成功后会往标准输出返回一段JSON包含分配的IP、网卡MAC、网关、DNS等信息。例如{ cniVersion: 0.3.1, interfaces: [ {name: eth0, mac: aa:bb:cc:dd:ee:ff, sandbox: /proc/1234/ns/net} ], ips: [ {version: 4, address: 10.244.5.7/24, gateway: 10.244.5.1} ], dns: {nameservers: [10.96.0.10]} }这些信息会被containerd解析最终反映到Pod的Status里比如kubectl get pod -o wide看到的IP就是从这里来的。需要注意的是CNI插件并不是只能创建一个网卡链式插件会在同一命名空间内依次执行每个插件都可以修改网络配置。3.3 常见CNI插件如何接入生产环境里最常见的CNI方案有Flannel、Calico、Cilium。它们的网络模型不同但对接CNI的方式完全一致——都遵循CNI规范都通过插件二进制的方式被运行时调用。Flannel走的是VXLAN或host-gw模式偏向简单易用。它的CNI插件会创建veth对一端在pause容器的网络命名空间里另一端接到cni0网桥随后通过flanneld维护的路由表把数据送到对端节点。Calico走的是纯三层BGP路由模式性能更优。它的CNI插件同样创建veth对但不会依赖cni0网桥而是把宿主机这一端的veth作为BGP的参与接口通过BGP协议把路由分发到其他节点。配合IPPool的IPAM机制Calico能给Pod分配独立的IP块。Cilium则基于eBPF实现了更细粒度的网络策略和安全能力。它的CNI插件会创建veth对然后通过eBPF程序在网络路径上挂接钩子实现L3/L4甚至L7的策略控制。不管插件内部怎么实现对于containerd来说它们都只是标准输入输出协议下的一个二进制。4. 实操一步步复现“pause先起CNI后配”4.1 在节点上找到pause容器没有比亲手在节点上看到这条链路更能加深理解了。如果你的集群使用containerd节点上安装好crictl工具后可以先用crictl ps -a查看所有容器。你会注意到每个Pod都对应一个名字带Pod字样、镜像为pause的容器。它比同Pod的业务容器创建时间更早状态通常是Running。crictl ps -a | grep -i pause输出大致是CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID 2f8a3b6c1d9e registry.k8s.io/pause:3.9 5 minutes ago Running kube-flannel-ds-7lw2t 1 2f8a3b6c1d9e如果你想只看某个Pod的pause容器可以先拿到Pod的UID再用crictl pods按Pod名称过滤。实际操作中我经常用crictl inspect pause容器ID拿到完整的容器元数据包括PID、网络命名空间路径等。这一步能帮你确认Sandbox到底起了什么。4.2 crictl runp手动走一遍Sandbox创建如果集群里没有正在创建中的Pod你甚至可以手动模拟一次Sandbox创建。crictl提供了一个叫runp的命令专门用来跑一个裸的Pod Sandbox。先准备一个Sandbox配置# pod-sandbox.yaml metadata: name: my-sandbox log_directory: /tmp/logs linux: security_context: namespace_options: network: NODE然后执行crictl runp pod-sandbox.yaml这个命令会直接调用containerd的RunPodSandbox接口效果就是创建一个pause容器并且触发CNI插件执行网络配置。跑完之后再执行crictl ps | grep my-sandbox就能看到多了一个pause容器。你可以把它删掉crictl stoppcrictl rmp不影响集群正常使用。需要说明的是集群默认的CNI配置会接管这个Sandbox所以如果你在装有Calico的节点上跑这个命令这个裸Sandbox也会拿到一个IP。这正是CNI链路在独立Sandbox上工作的直观验证。4.3 手动执行CNI插件验证ADD逻辑理解CNI最直接的方式是自己动手跑一次插件。找一台测试节点进入/opt/cni/bin目录随便选一个简单的插件比如bridge模拟runtime调用。首先找到pause容器的PIDPAUSE_PID$(crictl inspect $(crictl ps --namemy-sandbox -q) | grep -o pid: [0-9]* | head -1 | awk {print $2})然后设置CNI环境变量并手动执行ADDexport CNI_COMMANDADD export CNI_CONTAINERID$(crictl ps --namemy-sandbox -q) export CNI_NETNS/proc/$PAUSE_PID/ns/net export CNI_IFNAMEeth0 export CNI_PATH/opt/cni/bin echo { cniVersion: 0.3.1, name: mynet, type: bridge, bridge: cni0, ipam: {type: host-local, subnet: 10.244.0.0/24} } | /opt/cni/bin/bridge如果一切正常插件会返回一段JSON里面包含分配的IP和网桥信息。执行完以后再进入pause容器的网络命名空间查看网卡是否已经存在nsenter -t $PAUSE_PID -n ip addr你会发现eth0已经存在而且带着刚分配的IP。这个手动过程完全还原了containerd在Sandbox启动时做的事情对理解CNI协议特别有帮助。4.4 进netns检查配置结果网络配置是否生效最终要看网络命名空间内部的状态。Pause容器的网络命名空间路径是/proc/pause_pid/ns/net。可以用nsenter直接进入这个命名空间执行任意网络命令。nsenter -t $PAUSE_PID -n ip route nsenter -t $PAUSE_PID -n curl ifconfig.me你会看到在pause的网络命名空间里不仅有分配的IP还有默认路由、网关等一整套网络配置。这套配置就是Pod内所有业务容器共享的“网络视图”。业务容器启动时不过是在同一命名空间里打开自己的文件描述符而已。5. 问题排查Pod网络异常时先查谁5.1 与排查速查表我在实际支持别人排查问题时发现绝大多数网络问题都可以沿着“pause状态 - CNI插件 - 网络命名空间内容 - 业务容器内网络”这条顺序定位。下面是几个高频问题的排查思路速查表。现象优先检查方向常用命令/方法Pod长时间ContainerCreatingSandbox创建失败或CNI执行失败crictl ps -acrictl logsjournalctl -u kubelet查看containerd日志pause容器起不来pause镜像不存在、运行时配置错误crictl pull registry.k8s.io/pause:3.9检查containerd配置Pod有IP但ping不同网关网络路由、节点防火墙、Calico状态异常nsenter -t pause_pid -n ip routecalicoctl node statusPod之间通但访问Service不稳定kube-proxy/iptables/IPVS规则异常或conntrack表刷新iptables -L -t natipvsadm -Ln重启kube-proxy后重测DNS解析失败或超时CoreDNS与Pod网络连通性、上游DNS配置进入Pod执行nslookupkubectl -n kube-system logs -l k8s-appkube-dnsCNI插件报错找不到网卡CNI插件版本与内核不兼容、Pod Sandbox被误删手动执行CNI ADD复现查看插件目录权限确认pause PID对应命名空间存在Pod IP一直变化或分配冲突IPAM池耗尽、host-local数据残留查看节点上/var/lib/cni/networks/下的残留记录calicoctl ipam show这里要给一个最关键的技巧排障第一步永远是确认pause容器的网络命名空间是否健康。如果pause根本没起来所有依赖网络的排查都是空中楼阁。5.2 两个典型案子的复盘第一个案子有一次同事反馈某个应用的Pod一直处于ContainerCreating状态业务容器日志完全没有输出。我先查crictl ps -a发现pause容器根本没出现。再翻containerd日志看到CNI网络配置加载失败原因是/etc/cni/net.d/目录下同时存在Calico和Flannel两份冲突配置。CNI规范要求节点上只能有一个网络配置多个配置会导致运行时不知道选哪个直接报错。解决办法是删掉不用的配置重新创建Pod后一切正常。第二个案子某个节点上的Pod能拿到IP但跨节点通信不通。进入pause容器的网络命名空间看路由发现默认路由的网关指向了一个不存在的下一跳。实际原因是Calico的BGP会话断掉路由没有被正确分发。通过calicoctl node status确认BGP peer状态异常重启Calico的calico-node组件后问题解决。这类问题如果不从pause的网络命名空间入手很容易在业务容器里绕圈子。需要注意的是很多网络排查工具和数据都来自pause容器的命名空间。业务容器里可能没有ip、route、ping这些命令的镜像但宿主机上的nsenter可以无视这些限制直接进入pause的命名空间操作。这是排查时最顺手的一个工具。最后分享一点个人体会我平时排查Pod网络问题有个习惯不管问题表象多复杂先到节点上看一眼pause容器状态再进它的网络命名空间看一眼IP和路由。只要这两步是好的业务容器基本不会出大问题只要这两步有问题业务容器再正常也没用。有一次在客户现场同事查了将近一小时反复看业务容器日志发现某个服务一直在重启。最后我过去一查——pause容器的Sandbox网络命名空间被手工操作误删了业务容器起来以后根本没有网络健康检查失败自然反复重启。把这个“先pause后CNI”的顺序刻进条件反射里很多看似诡异的网络问题都能快速落地不会在表象里打转。