Linux bridge实现容器通信
1.容器使用自定义网桥
要将Docker容器通过veth pair连接到手动创建的网络桥接上,可以按照以下步骤操作:
- 创建网络桥接:
sudo ip link add name mybridge type bridge
# 下面的命令也可以创建网桥
sudo brctl addbr mybridge
sudo ip link set mybridge up
# 查看linux网桥的命令
sudo brctl show
- 创建veth pair:
sudo ip link add veth1 type veth peer name veth2
- 将veth pair的一端连接到桥接:
sudo ip link set veth1 master mybridge
sudo ip link set veth1 up
- 将veth pair的另一端连接到容器:
这一步涉及到将veth pair的另一端(veth2)移动到容器的网络命名空间中。首先,您需要知道容器的网络命名空间标识符(PID):
PID=$(docker inspect -f '{{.State.Pid}}' <container_name_or_id>)
然后,将veth pair的一端移动到该命名空间,并启用:
sudo ip link set veth2 netns $PID
sudo nsenter -t $PID -n ip link set veth2 up
- 为容器端的veth分配IP地址(可选):
如果您的网络桥接没有运行DHCP服务,您可能需要手动为veth分配IP地址:
sudo nsenter -t $PID -n ip addr add <ip_address>/<subnet_mask> dev veth2
- 更新容器的路由表(可选):
为了确保容器可以通过新的网络桥接通信,您可能需要更新容器的路由表:
sudo nsenter -t $PID -n ip route add default via <bridge_ip_address>
下面是我在自己电脑上面操作的步骤:
- **创建镜像:**首先找一个可以使用
ping
,ip addr
命令的镜像。(可以随便拉出来一个镜像创建容器,然后在这个容器里面下载对应的工具,比如:apt install net-tools
、apt install iproute2
,下载对应的工具之后再把这个容器提交为镜像,后面使用这个镜像进行操作) - **创建容器:**使用上面的镜像,指定网络模式为–net=none
# 创建容器
docker run -itd --name debian_test1 --net=none debian_test bash
创建好容器进去查看网卡信息发现只有一个本地地址:
root@6a8952c68c52:/# ifconfig
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
- 创建网络桥接:
sudo ip link add name myBridge3 type bridge
# 下面的命令也可以创建网桥
sudo brctl addbr myBridge3
sudo ip link set myBridge3 up
# 查看linux网桥的命令
sudo brctl show
查看网桥:
moran778@moran:/mnt/c/Users/J-Yong$ sudo brctl show
[sudo] password for moran778:
bridge name bridge id STP enabled interfaces
br-aafa9d020ab7 8000.0242c19229af no veth553f647
docker0 8000.02420952916d no veth9aab6cd
myBridge2 8000.9e9142f3b613 no veth3
myBridge3 8000.8e30260c6961 no veth5
mybridge 8000.82c2133e1f75 no veth1
virbr0 8000.5254000872cb yes virbr0-nic
- 创建veth pair:
sudo ip link add veth5 type veth peer name veth6
- 将veth pair的一端连接到桥接:
sudo ip link set veth5 master myBridge3
sudo ip link set veth5 up
- 将veth pair的另一端连接到容器:
这一步涉及到将veth pair的另一端(veth6)移动到容器的网络命名空间中。首先,您需要知道容器的网络命名空间标识符(PID):
PID=$(docker inspect -f '{{.State.Pid}}' <container_name_or_id>)
PID=$(docker inspect -f '{{.State.Pid}}' debian_test1)
然后,将veth pair的一端移动到该命名空间,并启用:
sudo ip link set veth6 netns $PID
sudo nsenter -t $PID -n ip link set veth6 up
- 为容器端的veth分配IP地址(可选):
如果您的网络桥接没有运行DHCP服务,您可能需要手动为veth分配IP地址:
sudo nsenter -t $PID -n ip addr add 172.29.0.2/16 dev veth6
- 更新容器的路由表(可选):
为了确保容器可以通过新的网络桥接通信,您可能需要更新容器的路由表:
# 首先给myBridge3分配一个ip地址
sudo ip addr add 172.29.0.1/16 dev myBridge3
sudo nsenter -t $PID -n ip route add default via 172.29.0.1
查看宿主机的地址信息:
# 只列出了myBridge3和veth5的信息
moran778@moran:/mnt/c/Users/J-Yong$ ip addr
...
33: myBridge3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 8e:30:26:0c:69:61 brd ff:ff:ff:ff:ff:ff
inet 172.29.0.1/16 scope global myBridge3
valid_lft forever preferred_lft forever
inet6 fe80::d444:1dff:fe2d:330e/64 scope link
valid_lft forever preferred_lft forever
35: veth5@if34: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master myBridge3 state UP group default qlen 1000
link/ether 8e:30:26:0c:69:61 brd ff:ff:ff:ff:ff:ff link-netnsid 3
inet6 fe80::8c30:26ff:fe0c:6961/64 scope link
valid_lft forever preferred_lft forever
...
查看宿主机的arp、路由信息:
moran778@moran:/mnt/c/Users/J-Yong$ arp
Address HWtype HWaddress Flags Mask Iface
moran.mshome.net ether 00:15:5d:2d:4d:29 C eth0
172.25.0.2 ether 96:10:91:7c:1c:55 C mybridge
172.29.0.2 ether ce:08:36:1e:d9:1e C myBridge3
172.26.0.2 ether 2a:40:de:36:a9:c8 C myBridge2
172.27.0.2 ether 02:42:ac:1b:00:02 C br-aafa9d020ab7
moran778@moran:/mnt/c/Users/J-Yong$ route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default moran.mshome.ne 0.0.0.0 UG 0 0 0 eth0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
172.25.0.0 0.0.0.0 255.255.0.0 U 0 0 0 mybridge
172.26.0.0 0.0.0.0 255.255.0.0 U 0 0 0 myBridge2
172.27.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-aafa9d020ab7
172.28.0.0 0.0.0.0 255.255.240.0 U 0 0 0 eth0
172.29.0.0 0.0.0.0 255.255.0.0 U 0 0 0 myBridge3
192.168.122.0 0.0.0.0 255.255.255.0 U 0 0 0 virbr0
查看容器的地址信息:
root@6a8952c68c52:/# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
34: veth6@if35: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether ce:08:36:1e:d9:1e brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.29.0.2/32 scope global veth6
valid_lft forever preferred_lft forever
inet 172.29.0.2/16 scope global veth6
valid_lft forever preferred_lft forever
查看容器的arp、路由信息:
root@6a8952c68c52:/# arp
Address HWtype HWaddress Flags Mask Iface
172.29.0.1 ether 8e:30:26:0c:69:61 C veth6
root@6a8952c68c52:/# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default 172.29.0.1 0.0.0.0 UG 0 0 0 veth6
172.29.0.0 0.0.0.0 255.255.0.0 U 0 0 0 veth6
- 测试两层通信:
宿主机ping容器:
moran778@moran:/mnt/c/Users/J-Yong$ ping 172.29.0.2
PING 172.29.0.2 (172.29.0.2) 56(84) bytes of data.
64 bytes from 172.29.0.2: icmp_seq=1 ttl=64 time=0.119 ms
64 bytes from 172.29.0.2: icmp_seq=2 ttl=64 time=0.077 ms
64 bytes from 172.29.0.2: icmp_seq=3 ttl=64 time=0.065 ms
...
容器ping宿主机:
root@6a8952c68c52:/# ping 172.29.0.1
PING 172.29.0.1 (172.29.0.1) 56(84) bytes of data.
64 bytes from 172.29.0.1: icmp_seq=1 ttl=64 time=0.141 ms
64 bytes from 172.29.0.1: icmp_seq=2 ttl=64 time=0.061 ms
64 bytes from 172.29.0.1: icmp_seq=3 ttl=64 time=0.075 ms
...
目前只能进行两层通信,不能进行三层通信。如果想进行三层通信那么就借鉴docker0做法,使用nat规则。
2.网桥配置NAT
要为您创建的网桥配置NAT,您可以使用 iptables
设置MASQUERADE规则。这里是一个示例命令,假设您的网桥名为 br0
,宿主机连接互联网的接口名为 eth0
:
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
sudo iptables -A FORWARD -i br0 -o eth0 -j ACCEPT
sudo iptables -A FORWARD -i eth0 -o br0 -m state --state RELATED,ESTABLISHED -j ACCEPT
这些命令会设置NAT规则,允许从 br0
出去的流量通过 eth0
接口访问互联网,并允许回应的流量返回。请根据实际情况调整接口名称。
3.容器通过网桥实现不同vlan不同网段通信
现在测试的是,创建veth pair连接容器和根据网桥创建的vlan接口,发现不可行
下面全面地解释如何使用Linux Bridge创建网桥、在网桥上创建VLAN子接口并分配地址,以及使用none模式创建Docker容器并通过veth pair将它们连接到网桥。
1. 创建网桥
首先,您需要创建一个网桥,这相当于一个虚拟的交换机,可以连接多个网络接口。
sudo ip link add name br0 type bridge
这个命令创建了一个名为br0
的网桥。
sudo ip link set br0 up
这个命令启动了网桥,使其能够传输数据包。
查看Linux桥的状态,包括已附加的VLAN接口:
brctl show
这将显示Linux桥的状态,包括桥接的接口列表以及每个接口的状态和VLAN信息。
2. 创建VLAN子接口
VLAN(虚拟局域网)允许您在同一物理网络上创建隔离的逻辑网络。在网桥上创建VLAN子接口,可以让您在不同的VLAN间进行通信。
sudo ip link add link br0 name br0.10 type vlan id 10
sudo ip link add link br0 name br0.20 type vlan id 20
这些命令在网桥br0
上创建了两个VLAN子接口,分别是br0.10
和br0.20
,VLAN ID分别为10和20。
查看所有的VLAN接口:
ip -d link show type vlan
3. 分配地址给VLAN子接口
为了使VLAN子接口能够进行网络通信,您需要为它们分配IP地址。
sudo ip addr add 192.168.10.1/24 dev br0.10
sudo ip addr add 192.168.20.1/24 dev br0.20
这些命令为VLAN子接口分配了IP地址,分别是192.168.10.1/24
和192.168.20.1/24
。
sudo ip link set br0.10 up
sudo ip link set br0.20 up
这些命令启动了VLAN子接口,使其能够传输数据包。
4. 创建Docker容器
您可以使用Docker命令创建容器,--net=none
选项意味着容器将不会被自动分配到任何网络,这让您可以手动控制容器的网络配置。
docker run -d --net=none --name container1 <image>
docker run -d --net=none --name container2 <image>
将<image>
替换为您想要运行的Docker镜像名称。-d
选项使容器在后台运行。
5. 连接Docker容器到网桥
要将容器连接到网桥,您需要使用veth pair(一对虚拟以太网设备)。一端附加到容器,另一端附加到网桥。
对于每个容器,请执行以下步骤:
# 创建veth pair
sudo ip link add veth1 type veth peer name veth1-br
这个命令创建了一对veth设备,veth1
和veth1-br
。veth1
将会连接到容器,而veth1-br
将会连接到网桥。
# 将veth一端连接到容器
sudo ip link set veth1 netns $(docker inspect --format='{{.State.Pid}}' container1)
这个命令将veth1
端连接到容器container1
的网络命名空间。$(docker inspect --format='{{.State.Pid}}' container1)
是一个命令替换,用于获取容器container1
的进程ID,这是将网络接口添加到容器中所必需的。
# 将veth另一端连接到网桥
sudo ip link set veth1-br master br0
sudo ip link set veth1-br up
这些命令将veth1-br
端连接到网桥br0
,并启动它。
配置容器内网络接口:
sudo nsenter -t $PID -n ip link set veth1 name eth0
sudo nsenter -t $PID -n ip link set eth0 up
sudo nsenter -t $PID -n ip addr add 172.20.0.2/16 dev eth0
sudo nsenter -t $PID -n ip route add default via 172.20.0.1
这些命令在容器内部将veth1
接口重命名为eth0
,为它分配一个IP地址,并启动它。
对于第二个容器,您需要重复上述步骤,但要确保使用不同的接口名称(例如veth2
和veth2-br
)和IP地址。
请注意,执行这些步骤需要root权限,可能还需要安装一些工具,如iproute2
、bridge-utils
和docker
。如果您的系统没有这些工具,您需要先安装它们。此外,这些命令可能因Linux发行版和Docker版本的不同而有所差异,因此请根据需要进行调整。
4.使用Macvlan方式实现跨主机容器通信
Macvlan工作原理
Macvlan是Linux内核支持的网络接口。通过为物理网卡创建Macvlan子接口,允许一块物理网卡拥有多个独立的MAC地址和IP地址。虚拟出来的子接口将直接暴露在相邻物理网络中。从外部看来,就像是把网线隔开多股,分别接到了不同的主机上一样;物理网卡收到包后,会根据收到包的目的MAC地址判断这个包需要交给其中虚拟网卡。
当容器需要直连入物理网络时,可以使用Macvlan。Macvlan本身不创建网络,本质上首先使宿主机物理网卡工作在‘混杂模式’,这样物理网卡的MAC地址将会失效,所有二层网络中的流量物理网卡都能收到。接下来就是在这张物理网卡上创建虚拟网卡,并为虚拟网卡指定MAC地址,实现一卡多用,在物理网络看来,每张虚拟网卡都是一个单独的接口。
使用Macvlan需要注意以下几点:
- 容器直接连接物理网络,由物理网络负责分配IP地址,可能的结果是物理网络IP地址被耗尽,另一个后果是网络性能问题,物理网络中接入的主机变多,广播包占比快速升高而引起的网络性能下降问题;
- 宿主机上的某张网上需要工作在‘混杂模式’下;
- 工作在混杂模式下的物理网卡,其MAC地址会失效,所以,此模式中运行的容器并不能与外网进行通信,但是不会影响宿主机与外网通信;
实现步骤
创建macvlan网络: 在每台主机上,你需要创建一个macvlan网络。这里,–subnet 和 –gateway 应该与你的物理网络设置相匹配。-o parent 是你想要绑定的物理网络接口(在这个例子中是 eth0),my_macvlan_net 是创建的macvlan网络的名字。
混杂模式打开
在macvlan网络上运行容器: 在创建了macvlan网络之后,在该网络上启动容器。(手动设置容器ip,主机1:192.168.43.66,主机2:192.168.43.69)
分别在主机1和主机2上ping对方
macvlan网络结构分析
没有新建linux bridge
容器的接口直接与主机网卡连接,无需NAT或端口映射。
macvlan会独占主机网卡,但可以使用vlan子接口实现多macvlan网络
vlan可以将物理二层网络划分为4094个逻辑网络,彼此隔离,vlan id取值为1~4094
具体步骤:
ip link set enx54 promisc on
,开启混杂模式nano /etc/netplan/01-netcfg.yaml
,新建配置文件
network:
version: 2
ethernets:
enx5414a7283d98:
addresses: [ ]
dhcp4: no
optional: true
vlans:
enx5.10:
id: 10
link: enx5414a7283d98
addresses: [192.168.10.66/24]
sudo netplan apply
,应用配置文件生成虚拟子接口enx5.10:docker network create -d macvlan --subnet 192.168.10.0/24 --gateway 192.168.10.1 -o parent=enx5.10 my-macvlan-net10
,创建macvlan网络docker run -it --name demo10 --network my-macvlan-net10 --ip 192.168.10.67 busybox
,创建容器连接到macvlan网络并启动;
总结
容器通过网桥实现不同vlan不同网段通信,结果发现并不行,ping不通,目前这种方法还是行不通。在docker里面,主要有bridge,host,overlay,macvlan这几种模式,macvlan是主要依靠于物理网卡的,overlay主要应用于集群。
目前的环境想用linux bridge方式进行通信,自定义的docker bridge本质上使用的是linux bridge,都是通过veth pair进行连接通信的,但是docker bridge不支持创建vlan子接口,linux bridge支持创建vlan子接口(但是根据上面测试,就算创建出来了vlan子接口,但是也不能通信,不知道是自己操作问题还是本来就不支持这种方式。但是还是支持二层通信的。)