容器技术通过轻量化的资源隔离机制,极大地简化了应用部署的复杂性。而容器网络作为容器生态的核心组件,直接影响着容器间通信、服务发现及跨主机协同的能力。本文将从容器网络的基础组件出发,逐步剖析其工作原理及关键实现。
所谓网络栈,就包括了:网卡(Network Interface)、回环设备(Loopback Device)、路由表(Routing Table)和 iptables 规则。对于一个进程来说,这些要素,其实就构成了它发起和响应网络请求的基本环境。
使用宿主机的网络栈(–net=host),即:不开启 Network Namespace,比如:
docker run -d --net=host --name nginx-host nginx:demo每个容器启动时都会创建独立的网络命名空间(Network Namespace),形成完全隔离的网络栈,包含以下核心组件:
veth)与宿主机通信。例如,容器内的eth0 网卡通过 veth 对连接到宿主机的虚拟网桥(如 docker0)。这种设计使得容器流量能够通过宿主机网络栈转发。lo 设备用于本机进程间通信(如访问 localhost 服务),与宿主机回环设备完全隔离。eth0,将出站流量导向宿主机的网桥;特定服务的路由规则可实现精细的流量控制。MASQUERADE 规则将容器出站流量伪装为宿主机 IP,实现外网访问;FORWARD 链规则控制容器间的通信权限。eth0、lo 等设备,与其他容器不可见。-p 8080:80)暴露服务。容器间的网络通信本质上是通过虚拟化技术模拟多机通信场景实现的。我们可以将容器视为独立主机,通过虚拟交换机(网桥)和虚拟网线(Veth Pair)构建通信链路。以下通过一个具体场景解析其完整流程:
宿主机上运行两个容器 ContainerA 和 ContainerB,二者通过 docker0 网桥互联
+-------------------------------------------------+
| 宿主机 |
| +------------+ +------------+ |
| | ContainerA | | ContainerB | |
| | eth0@vethA | | eth0@vethB | |
| +-----↑------+ +-----↑------+ |
| ↓ ↓ |
| +-----↓---------------------------↓------+ |
| | docker0网桥 (172.17.0.1) | |
| +----------------------------------------+ |
+-------------------------------------------------+当容器启动时,Docker 会创建一对 Veth Pair(如 vethA 和 vethA-peer)
# 宿主机查看Veth Pair(vethA-peer在容器内被重命名为eth0)
# ip link show | grep -n2 veth
12: veth6f43f82@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
link/ether 3e:30:83:c4:92:54 brd ff:ff:ff:ff:ff:ff link-netnsid 0
13: veth2cb4daf@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
link/ether 36:e5:74:e0:27:80 brd ff:ff:ff:ff:ff:ff link-netnsid 1vethA-peer 被放入容器的 Network Namespace,重命名为 eth0vethA 保留在宿主机,连接到 docker0 网桥当 ContainerA 向 ContainerB 发送数据包:
eth0(即 vethA-peer)发出数据包vethAdocker0 网桥接收并转发数据包到 vethBbrctl showmacs docker0 查看),记录端口与 MAC 的映射关系:MAC地址 | 端口 |
|---|---|
3e:30:83:c4:92:54 | vethA |
36:e5:74:e0:27:80 | vethB |
# brctl showmacs docker0
port no mac addr is local? ageing timer
2 26:12:cf:e4:da:80 no 297.59
2 36:e5:74:e0:27:80 yes 0.00
2 36:e5:74:e0:27:80 yes 0.00
1 3e:30:83:c4:92:54 yes 0.00
1 3e:30:83:c4:92:54 yes 0.00
字段说明
port no: 网桥端口编号,对应连接到网桥的接口
mac addr: 学习到的 MAC 地址
is local?:
yes: 表示该 MAC 地址属于宿主机本地接口(如网桥自身或直接连接的 Veth 宿主机端)
no: 表示该 MAC 地址是外部学习到的(如容器内部的 Veth 设备)
ageing timer: 条目剩余存活时间(秒),归零后删除。0.00 表示该条目被标记为永久或刚被更新转发决策
以 ContainerA(172.17.0.2) 访问 ContainerB(172.17.0.3) 为例:
ContainerA 通过广播查询 172.17.0.3 的 MAC 地址。ContainerB 响应其 MAC。# 创建两个Network Namespace
ip netns add ns1
ip netns add ns2
# 创建Veth Pair并分配到Namespace
ip link add veth1 type veth peer name veth1-peer
ip link set veth1-peer netns ns1
ip netns exec ns1 ip link set veth1-peer name eth0
ip link add veth2 type veth peer name veth2-peer
ip link set veth2-peer netns ns2
ip netns exec ns2 ip link set veth2-peer name eth0
# 创建网桥并连接Veth
brctl addbr mybridge
brctl addif mybridge veth1
brctl addif mybridge veth2
# 启动设备
ip link set mybridge up
ip link set veth1 up
ip link set veth2 up
# 配置容器IP
ip netns exec ns1 ip addr add 10.0.0.1/24 dev eth0
ip netns exec ns1 ip link set eth0 up
ip netns exec ns2 ip addr add 10.0.0.2/24 dev eth0
ip netns exec ns2 ip link set eth0 up
# 测试连通性
ip netns exec ns1 ping 10.0.0.2# 查看docker0网桥信息
# brctl show docker0
bridge name bridge id STP enabled interfaces
docker0 8000.7a2a35e08d9f no veth2cb4daf
veth6f43f82
定位Veth Pair对应的容器:
1.获取Veth接口索引号
# ip link show veth2cb4daf
13: veth2cb4daf@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
link/ether 36:e5:74:e0:27:80 brd ff:ff:ff:ff:ff:ff link-netnsid 1
说明:
13::宿主机上 veth2cb4daf 接口的索引号为 13
@if2 表示其对端接口在另一个网络命名空间的索引为2
link-netnsid 1 表示该Veth对端所在的网络命名空间ID(需结合其他命令解析)
2.列出所有容器的网络命名空间
# ls -l /var/run/docker/netns/
total 0
-r--r--r-- 1 root root 0 Mar 31 23:32 426133a3ab9c
-r--r--r-- 1 root root 0 Mar 31 22:45 default
-r--r--r-- 1 root root 0 Mar 31 23:32 e1f62106d430
3.进入容器网络命名空间验证
# nsenter --net=/var/run/docker/netns/426133a3ab9c ip link show eth0
2: eth0@if13: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 26:12:cf:e4:da:80 brd ff:ff:ff:ff:ff:ff link-netnsid 0
@if13 对应宿主机的 veth2cb4daf 接口索引号13,确认Veth Pair关系。# 确认两个容器在同一子网
docker inspect <容器ID> | grep IPAddress
# 确认docker0网桥的子网
ip addr show docker0# 进入容器Network Namespace检查路由
nsenter --net=/var/run/docker/netns/<容器netns> ip route--network 参数# 容器A中查询容器B的ARP记录
nsenter --net=/var/run/docker/netns/<容器A-netns> arp -n | grep 172.17.x.x
# 容器B中查询容器A的ARP记录
nsenter --net=/var/run/docker/netns/<容器B-netns> arp -n | grep 172.17.x.xbrctl setageing docker0 0 会关闭学习)brctl showmacs docker0 | grep -E '3e:30:83:c4:92:54|26:12:cf:e4:da:80' # 示例MACis local?=no 条目。ip link set docker0 down && ip link set docker0 up# 宿主机抓取veth接口流量(容器A的宿主机端Veth)
tcpdump -i veth123456 -nn -e icmp
# 容器内抓包(进入容器Network Namespace)
nsenter --net=/var/run/docker/netns/<容器B-netns> tcpdump -i eth0 -nn -e icmpip link show veth123456)ethtool -S vethXXX 查看数据包计数,确认是否有丢包 iptables -t nat -L -n -v在这篇文章中,主要为你介绍了在本地环境下,单机容器网络的实现原理和 docker0 网桥的作用。
这里的关键在于,容器要想跟外界进行通信,它发出的 IP 包就必须从它的 Network Namespace 里出来,来到宿主机上。
而解决这个问题的方法就是:为容器创建一个一端在容器里充当默认网卡、另一端在宿主机上的 Veth Pair 设备。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。