Linux bridge实现容器通信

1.容器使用自定义网桥

要将Docker容器通过veth pair连接到手动创建的网络桥接上,可以按照以下步骤操作:

  1. 创建网络桥接
sudo ip link add name mybridge type bridge
# 下面的命令也可以创建网桥
sudo brctl addbr mybridge

sudo ip link set mybridge up

# 查看linux网桥的命令
sudo brctl show
  1. 创建veth pair
sudo ip link add veth1 type veth peer name veth2
  1. 将veth pair的一端连接到桥接
sudo ip link set veth1 master mybridge
sudo ip link set veth1 up
  1. 将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
  1. 为容器端的veth分配IP地址(可选):
    如果您的网络桥接没有运行DHCP服务,您可能需要手动为veth分配IP地址:
sudo nsenter -t $PID -n ip addr add <ip_address>/<subnet_mask> dev veth2
  1. 更新容器的路由表(可选):
    为了确保容器可以通过新的网络桥接通信,您可能需要更新容器的路由表:
sudo nsenter -t $PID -n ip route add default via <bridge_ip_address>

下面是我在自己电脑上面操作的步骤:

  1. **创建镜像:**首先找一个可以使用pingip addr命令的镜像。(可以随便拉出来一个镜像创建容器,然后在这个容器里面下载对应的工具,比如:apt install net-toolsapt install iproute2,下载对应的工具之后再把这个容器提交为镜像,后面使用这个镜像进行操作)
  2. **创建容器:**使用上面的镜像,指定网络模式为–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
  1. 创建网络桥接
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
  1. 创建veth pair
sudo ip link add veth5 type veth peer name veth6
  1. 将veth pair的一端连接到桥接
sudo ip link set veth5 master myBridge3
sudo ip link set veth5 up
  1. 将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
  1. 为容器端的veth分配IP地址(可选):
    如果您的网络桥接没有运行DHCP服务,您可能需要手动为veth分配IP地址:
sudo nsenter -t $PID -n ip addr add 172.29.0.2/16 dev veth6
  1. 更新容器的路由表(可选):
    为了确保容器可以通过新的网络桥接通信,您可能需要更新容器的路由表:
# 首先给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
  1. 测试两层通信:
    宿主机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.10br0.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/24192.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设备,veth1veth1-brveth1将会连接到容器,而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地址,并启动它。

对于第二个容器,您需要重复上述步骤,但要确保使用不同的接口名称(例如veth2veth2-br)和IP地址。

请注意,执行这些步骤需要root权限,可能还需要安装一些工具,如iproute2bridge-utilsdocker。如果您的系统没有这些工具,您需要先安装它们。此外,这些命令可能因Linux发行版和Docker版本的不同而有所差异,因此请根据需要进行调整。

4.使用Macvlan方式实现跨主机容器通信

Macvlan工作原理

Macvlan是Linux内核支持的网络接口。通过为物理网卡创建Macvlan子接口,允许一块物理网卡拥有多个独立的MAC地址和IP地址。虚拟出来的子接口将直接暴露在相邻物理网络中。从外部看来,就像是把网线隔开多股,分别接到了不同的主机上一样;物理网卡收到包后,会根据收到包的目的MAC地址判断这个包需要交给其中虚拟网卡。

当容器需要直连入物理网络时,可以使用Macvlan。Macvlan本身不创建网络,本质上首先使宿主机物理网卡工作在‘混杂模式’,这样物理网卡的MAC地址将会失效,所有二层网络中的流量物理网卡都能收到。接下来就是在这张物理网卡上创建虚拟网卡,并为虚拟网卡指定MAC地址,实现一卡多用,在物理网络看来,每张虚拟网卡都是一个单独的接口。

使用Macvlan需要注意以下几点:

  • 容器直接连接物理网络,由物理网络负责分配IP地址,可能的结果是物理网络IP地址被耗尽,另一个后果是网络性能问题,物理网络中接入的主机变多,广播包占比快速升高而引起的网络性能下降问题;
  • 宿主机上的某张网上需要工作在‘混杂模式’下;
  • 工作在混杂模式下的物理网卡,其MAC地址会失效,所以,此模式中运行的容器并不能与外网进行通信,但是不会影响宿主机与外网通信;

容器网络不通宿主机网络_服务器

实现步骤

容器网络不通宿主机网络_linux_02

创建macvlan网络: 在每台主机上,你需要创建一个macvlan网络。这里,–subnet–gateway 应该与你的物理网络设置相匹配。-o parent 是你想要绑定的物理网络接口(在这个例子中是 eth0),my_macvlan_net 是创建的macvlan网络的名字。

容器网络不通宿主机网络_linux_03

混杂模式打开

容器网络不通宿主机网络_服务器_04


在macvlan网络上运行容器: 在创建了macvlan网络之后,在该网络上启动容器。(手动设置容器ip,主机1:192.168.43.66,主机2:192.168.43.69)

容器网络不通宿主机网络_docker_05

容器网络不通宿主机网络_容器网络不通宿主机网络_06

分别在主机1和主机2上ping对方

macvlan网络结构分析

没有新建linux bridge

容器的接口直接与主机网卡连接,无需NAT或端口映射。

macvlan会独占主机网卡,但可以使用vlan子接口实现多macvlan网络

vlan可以将物理二层网络划分为4094个逻辑网络,彼此隔离,vlan id取值为1~4094

容器网络不通宿主机网络_docker_07

具体步骤:

  1. ip link set enx54 promisc on,开启混杂模式
  2. 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]
  1. sudo netplan apply,应用配置文件生成虚拟子接口enx5.10:
  2. docker network create -d macvlan --subnet 192.168.10.0/24 --gateway 192.168.10.1 -o parent=enx5.10 my-macvlan-net10,创建macvlan网络
  3. docker run -it --name demo10 --network my-macvlan-net10 --ip 192.168.10.67 busybox,创建容器连接到macvlan网络并启动;

总结

容器通过网桥实现不同vlan不同网段通信,结果发现并不行,ping不通,目前这种方法还是行不通。在docker里面,主要有bridgehostoverlaymacvlan这几种模式,macvlan是主要依靠于物理网卡的,overlay主要应用于集群。

目前的环境想用linux bridge方式进行通信,自定义的docker bridge本质上使用的是linux bridge,都是通过veth pair进行连接通信的,但是docker bridge不支持创建vlan子接口,linux bridge支持创建vlan子接口(但是根据上面测试,就算创建出来了vlan子接口,但是也不能通信,不知道是自己操作问题还是本来就不支持这种方式。但是还是支持二层通信的。)