如果你管过几台跑容器服务的机器,多半会遇到过这种事情:容器明明起来了,业务却不通。ping外网超时,跨节点访问卡住,甚至同一台宿主机上两个容器都互相联系不上。很多人第一反应是查防火墙,折腾半天发现啥也没配错。问题其实出在容器网络这一层。今天我不讲太虚的理论,就用最直白的话,把containerd上的网络配置管理掰开揉碎聊一遍,再把我平时在生产环境里排查容器网络故障的套路分享出来,希望能给你一些实在的启发。
一、容器网络到底在折腾啥?
先把底层的概念用大白话过一遍,不然看后面的命令容易犯迷糊。
1.1 网络命名空间就是隔出来的小房间
每个容器都有一块独立的小天地,叫做网络命名空间。你可以把它想象成一个单间公寓,里面的网卡、路由表、防火墙规则都是自己那一份,跟外面完全隔开。宿主机是整栋楼,有自己的公共区域;容器是住户,关起门来谁也看不见谁。要给容器配网络,就等于给这个房间拉一根网线,让它能和外界说话。
1.2 veth pair就是一根两头都带插头的虚拟网线
你想让两个房间通电话,光有电话机没用,得有电话线。veth pair就是那根线,它永远成对出现,一头插在容器的命名空间里,另一头插在宿主机上。数据从任意一头进来,都会原封不动地从另一头出去。容器里看到的网卡叫eth0,宿主机上看到的对应网卡通常叫vethxxx,它们俩其实就是一根线的两端。
1.3 bridge就是一个家用交换机
如果房间多了,每根线都直接插到路由器上,口不够用。bridge网桥就是一个小型交换机,宿主机上创建一个Linux网桥,比如cni0,所有容器的veth线都插到这个网桥上,就能互相通信了。再给网桥配个IP地址,容器就能通过网桥这个“大门”访问宿主机外面的世界。
containerd本身不是干网络这个活儿的,它更像个管家,只负责启动容器、管理生命周期。真要给容器拉网线,得靠一个叫CNI的插件。containerd启动容器时,会对CNI插件喊一声:“帮我把这个容器的网线接好!”CNI插件就按照配置文件里的定义,创建veth、网桥,然后设置路由和规则。
二、containerd网络配置管理实战
光知道概念不行,咱们上手把网络配出来看看。
2.1 先让containerd认识CNI
containerd要调用CNI,得有两样东西:一是CNI插件二进制文件,二是配置文件目录。插件二进制通常放在/opt/cni/bin里,配置文件放在/etc/cni/net.d下。咱们先看看系统里有没有这些。
# 技术栈:Shell(Bash)
# 检查CNI插件目录是否存在,并且列出里面的插件
ls -l /opt/cni/bin/
# 检查CNI配置文件目录
ls -l /etc/cni/net.d/
# 如果目录不存在,手动创建,权限设成root可读写
sudo mkdir -p /opt/cni/bin
sudo mkdir -p /etc/cni/net.d
如果ls命令输出了很多名字像bridge、host-local、portmap这样的二进制文件,说明插件已经装上了。没有的话可以下载cni-plugins的压缩包,解压到目录里。这一步是地基,地基不稳,后面全白搭。
2.2 用CNI配置文件定义网络规则
CNI插件需要看配置才知道怎么干活。咱们用一个最简单的bridge网络举例,配置内容是JSON格式。这里我通过bash命令把配置写进文件,顺便解释里面的关键项。
# 技术栈:Shell(Bash)
# 创建一个基础CNI网络配置,文件名叫10-mynet.conf
# 前面的数字10用来决定配置的加载顺序,越小优先级越高
sudo tee /etc/cni/net.d/10-mynet.conf > /dev/null <<'EOF'
{
"cniVersion": "0.4.0",
"name": "mynet",
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.88.0.0/24",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
}
EOF
# 查看一下刚写的文件,确认内容没问题
cat /etc/cni/net.d/10-mynet.conf
这个配置里的type告诉CNI用哪个插件,这里用的是bridge插件。ipam是IP地址管理模块,这里用host-local,表示从本地子网里挑一个IP给容器。isGateway让网桥本身作为网关,ipMasq开启地址伪装,这样容器访问外网时看起来像是宿主机在访问,省去复杂的路由配置。
2.3 真正启动一个接好网的容器
配置写好了,咱们用containerd的命令行工具ctr来跑一个容器。注意要加--with-network参数,这个参数的意思是让CNI接管网络配置。
# 技术栈:Shell(Bash)
# 先拉取一个nginx镜像,作为示例
sudo ctr image pull docker.io/library/nginx:1.27
# 启动容器,--with-network 表示使用CNI网络
# --rm 表示容器退出后自动删除,方便测试
sudo ctr run --with-network --rm docker.io/library/nginx:1.27 web-test
这个时候容器已经启动,并且拿到了一个10.88.0.x的地址。我们可以另开一个终端,用ctr task ls查看容器进程,然后测试网络通不通。
# 技术栈:Shell(Bash)
# 查看正在运行的容器任务
sudo ctr task ls
# 从宿主机上ping一下刚才那个容器的IP,假设是10.88.0.2
ping -c 3 10.88.0.2
如果ping通了,说明容器网络基本可用。但这只是“能通”,生产环境要复杂得多,我们后面再聊故障排查。
2.4 自己动手模拟一下veth和bridge
如果你还觉得抽象,咱们完全可以用ip命令手动搭建一个最简单的容器网络模型。这不算重复造轮子,而是为了让你看清CNI到底动了哪些东西。
# 技术栈:Shell(Bash)
# 创建一个网桥,模拟交换机
sudo ip link add br0 type bridge
sudo ip link set br0 up
# 创建一对veth,模拟网线,一头叫veth0,另一头叫veth1
sudo ip link add veth0 type veth peer name veth1
sudo ip link set veth0 up
# 把网线的一头插到网桥上
sudo ip link set veth0 master br0
# 假设某个容器进程的PID是12345,把另一头塞进它的网络命名空间
# 实际使用时要将12345替换成真实PID,可以通过 ctr task ls 获取
sudo ip link set veth1 netns 12345
# 进入那个容器的命名空间,把veth1改名为eth0并启动
sudo nsenter -t 12345 -n ip link set veth1 name eth0
sudo nsenter -t 12345 -n ip link set eth0 up
这样你就手动给容器插了一根网线。虽然生产环境不会这么原始地操作,但理解这个过程,后面看CNI配置文件就不会晕。
三、生产环境容器网络故障快速定位方法
网络出问题的时候,别急着猜。按照一套固定的流程去查,一多半问题能在几分钟内找到线索。我平时习惯从“容器内部—宿主机—网桥—防火墙—抓包”这个顺序来走。
3.1 先看容器网卡有没有活着
第一步,进到容器里看看它自己有没有拿到IP。容器都起不来,后面全是空谈。
# 技术栈:Shell(Bash)
# 拿运行中的容器ID,这里假设容器ID是abcdefg
# 用crictl exec来执行容器内的命令,注意crictl是CRI客户端,containerd生产环境常用
sudo crictl exec abcdefg ip addr
# 如果crictl还没配置好,可以用ctr直接查进程PID,再用nsenter进命名空间
# 先列出所有任务,找到nginx那一行对应的PID
sudo ctr task ls
# 假设得到的PID是12345,直接用nsenter查看这个命名空间里的网卡
sudo nsenter -t 12345 -n ip addr
如果看到的eth0没有IP,或者根本没有eth0,那就是CNI没执行成功。这时候要回到宿主机看看CNI日志,containerd的日志里一般会有报错。
3.2 从容器里面往外ping,判断堵在哪
如果容器内有IP,我们再测试容器到网关、到外网的通路。注意ping的顺序:先ping自己的IP,再ping网桥的IP,再ping宿主机地址,最后ping外网。这个顺序能帮我们判断哪一段断了。
# 技术栈:Shell(Bash)
# 进入容器的网络命名空间
sudo nsenter -t 12345 -n sh
# 先ping自己的IP,检查回环和协议栈
ping -c 2 10.88.0.2
# 再ping网桥cni0的IP,通常情况下是10.88.0.1
ping -c 2 10.88.0.1
# 然后ping宿主机物理机IP,比如192.168.1.10
ping -c 2 192.168.1.10
# 最后ping外网IP,比如114.114.114.114
ping -c 2 114.114.114.114
如果自己的IP能通,网关不通,说明veth或bridge配置有问题。如果网关通了,宿主机不通,大概率是路由表或者网桥IP设置问题。如果宿主机通了,外网不通,那就要查iptables的NAT规则和转发开关。
3.3 检查宿主机上的桥接设备和veth
在宿主机上看看网桥里有没有连着该连的网线。
# 技术栈:Shell(Bash)
# 查看所有网桥设备的转发数据库,可以看到上面挂的MAC地址
sudo bridge fdb show br cni0
# 或者用传统命令brctl(如果没有,先安装bridge-utils)
brctl show cni0
# 查看宿主机上所有veth网卡
ip link show type veth
如果发现容器对应的veth不在网桥里,说明线没插紧。这时候可以手动把它加进网桥再测试。
3.4 检查iptables和转发开关
Linux默认不太喜欢转发,容器的流量从一根veth进来,要转发到另一根veth或物理网卡,必须打开ip_forward开关。
# 技术栈:Shell(Bash)
# 查看是否开启了IPv4转发,1表示开,0表示关
sysctl net.ipv4.ip_forward
# 如果是0,临时打开,生产环境建议写入/etc/sysctl.conf永久生效
sudo sysctl -w net.ipv4.ip_forward=1
# 查看iptables的FORWARD链,确认容器网段有没有被丢弃
sudo iptables -L FORWARD -n -v
# 查看NAT规则,确认有没有MASQUERADE这条伪装规则
sudo iptables -t nat -L POSTROUTING -n -v
如果FORWARD链默认策略是DROP,而且没有容器网段的放行规则,那跨容器和访问外网都会失败。很多生产环境故障就是因为有人清理防火墙时,不小心把FORWARD链的默认策略改成了DROP。
3.5 用tcpdump抓包,看流量到底走到哪一步
命令查不出问题时,抓包是最直观的。我们可以在宿主机网桥上抓包,也可以进容器的网络命名空间里抓。
# 技术栈:Shell(Bash)
# 在网桥cni0上抓包,只看来自容器IP的流量
sudo tcpdump -i cni0 -n host 10.88.0.2 -c 20
# 如果看不到任何包,说明流量根本没到网桥,问题在容器内部或veth
# 如果看到包发了但没回应,说明请求出去了但响应没回来,要查上游路由或防火墙
抓包结果能帮你把问题定位得很精确。比如容器ping网关时,网桥上能看到ICMP请求,但看不到回应,那八成是网桥IP或路由有问题,而不是容器的问题。
3.6 一个完整的排查流程示例
我用一个综合例子把上面这些技巧串起来。假设一台宿主机上有个nginx容器,用户反馈访问不了它的服务。我们按步骤来。
# 技术栈:Shell(Bash)
# 1. 查看容器状态和IP
sudo crictl ps -a | grep nginx
sudo crictl inspect <容器ID> | grep -A 5 "ipAddress"
# 2. 在宿主机上ping容器IP,测宿主机到容器的链路
ping -c 2 10.88.0.2
# 3. 进入容器命名空间,从容器内测到宿主机
PID=$(sudo crictl inspect <容器ID> | grep -oP '"pid":\s*\K[0-9]+')
sudo nsenter -t $PID -n ping -c 2 192.168.1.10
# 4. 检查网桥上有没有容器MAC
sudo bridge fdb show br cni0 | grep 10.88.0.2
# 检查网桥是否UP
ip link show cni0
# 5. 查看FORWARD链,确认有没有丢包
sudo iptables -L FORWARD -n -v | grep 10.88.0.2
# 6. 在网桥上抓包,模拟一次访问
sudo tcpdump -i cni0 -n port 80 -c 10 &
curl http://10.88.0.2 -m 3
sudo kill %1
这个流程走下来,九成的问题能定位出来。剩下那成,可能是CNI插件版本兼容性、自定义路由或者网络策略,那就需要结合日志和具体环境再深挖了。
四、应用场景、技术优缺点以及注意事项
4.1 适用场景
containerd配合CNI这套方案,最经典的场景就是Kubernetes。每个Pod分配独立IP,服务通过Service跨节点转发,靠的就是CNI插件实现对网络生命周期的管理。另外,一些基于containerd的容器平台,如果不想依赖Kubernetes,也可以直接用ctr run --with-network来快速给容器配网,适合测试环境和小规模部署。
4.2 技术优缺点
优点是灵活。想换网络模式,只要换一套CNI插件就行,不用改containerd本身。无论是桥接网络、VXLAN叠加网络还是直接路由网络,都有对应的现成插件。而且配置是文本文件,版本化、审阅都很方便,出了问题也好追溯。
缺点是学习曲线有点陡。CNI插件种类多,不同插件对底层网络的要求不同,出了问题需要同时懂容器、网络和系统,不然很容易摸不着头脑。另外,CNI本身不负责网络策略,像Kubernetes的NetworkPolicy,还得额外搭配Calico或Cilium才能实现,等于又多一层复杂度。
4.3 注意事项
第一,CNI配置文件里的“name”字段要唯一,多个配置文件混在一起时容易冲突。第二,IPAM的网段不要和宿主机物理网络重叠,否则路由会互相打架。第三,修改CNI配置后,一定要重建容器才生效,因为容器启动时的网络是即插即用的。第四,生产环境不要用host-local这类本地IPAM管理大规模集群,因为节点间无法感知IP分配状态,容易撞地址。第五,防火墙的FORWARD链一定要愿意放行容器流量,很多半夜加班排查的惨案,最后都发现是这条链子上被加了一条DROP规则。
五、总结
容器网络看着神秘,拆开看无非就是命名空间、veth、bridge、iptables这几条线。containerd把创建网络的活交给了CNI,而我们要做的,就是理解这套规则,然后学会用最基础的命令去验证每一段链路。以后再遇到容器网络不通,别急着重启容器,按着“容器内—宿主机—网桥—防火墙—抓包”的流程走一遍,往往比瞎猜管用得多。希望这篇文章能让你对containerd网络配置管理有一个比较踏实的认识,下次再遇到类似问题,能少挠头几分钟。
评论
围绕“Containerd容器的网络配置管理详解及生产环境容器网络故障的快速定位方法”参与讨论