Skip to content

容器中的网络-bridge

本文将简单了解Linux bridge

什么是Linux bridge

Linux bridge也称为Linux虚拟网桥,也可以理解为一台虚拟的网络交换机,它是一种链路层设备,允许根据MAC地址在网络之间转发流量。所以在Linux中可以用它来模拟网桥。bridge不仅允许虚拟设备接入,还支持物理设备接入。

此前已经简单了解了veth虚拟接口是如何通信的,这次来了解下bridgeveth如何配合使用。

实验准备

准备1个bridge信息分别为:

bridge信息

bridge192.168.1.250/24

准备3个veth,信息分别为:

第一对veth

  • veth0veth1
  • veth0192.168.1.245/24
  • veth1: 接入br0

第二对veth

  • veth2veth3
  • veth2192.168.1.246/24
  • veth3: 接入br0

第三对veth

  • veth4veth5
  • veth4192.168.1.247/24
  • veth5: 接入br0

而后再将veth0veth2veth4分别放入不同的network namespace中。

虚拟设备创建

bridge创建

使用ip命令添加一个bridge虚拟设备。

[root@ubuntu]:[~][tty:0]# ip link add name br0 type bridge

如上添加了bridge,名称为br0,使用如下命令,可以查看所有的bridge虚拟设备。

[root@ubuntu]:[~][tty:0]# brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.72cfbe81fc62       no
[root@ubuntu]:[~][tty:0]#

创建veth

首先在宿主机上创建3条veth,命令如下:

[root@ubuntu]:[~][tty:0]# ip link add name veth0 type veth peer name veth1
[root@ubuntu]:[~][tty:0]# ip link add name veth2 type veth peer name veth3
[root@ubuntu]:[~][tty:0]# ip link add name veth4 type veth peer name veth5

创建完毕后,使用ip link可以查询到所有的网卡信息,只需要关注veth的即可。

[root@ubuntu]:[~][tty:0]# ip link
......
4: br0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 72:cf:be:81:fc:62 brd ff:ff:ff:ff:ff:ff
5: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether ae:dd:f7:9d:9b:f8 brd ff:ff:ff:ff:ff:ff
6: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 66:77:10:54:81:0e brd ff:ff:ff:ff:ff:ff
7: veth3@veth2: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 52:6c:7b:57:b2:7b brd ff:ff:ff:ff:ff:ff
8: veth2@veth3: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 9a:c0:ed:69:8c:4e brd ff:ff:ff:ff:ff:ff
9: veth5@veth4: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether a2:c7:ea:83:fc:91 brd ff:ff:ff:ff:ff:ff
10: veth4@veth5: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether d2:a0:6a:c1:fe:80 brd ff:ff:ff:ff:ff:ff
[root@ubuntu]:[~][tty:0]#

创建3个容器

使用unshare创建三个简单的容器,本次创建就不添加pid namespace了,免得获取进程自身的pid麻烦。

创建第一个容器。

[root@ubuntu]:[~][tty:1]# unshare -m -u -n -i -f /bin/bash
(unshare) [root@ubuntu]:[~][tty:1]# echo $$
1349
(unshare) [root@ubuntu]:[~][tty:1]#

该进程的pid1349

创建第二个容器。

[root@ubuntu]:[~][tty:2]# unshare -m -u -i -n -f /bin/bash
(unshare) [root@ubuntu]:[~][tty:2]# echo $$
1357
(unshare) [root@ubuntu]:[~][tty:2]#

该进程的pid1357

创建第三个容器。

[root@ubuntu]:[~][tty:3]# unshare -m -u -i -n -f /bin/bash
(unshare) [root@ubuntu]:[~][tty:3]# echo $$
1365
(unshare) [root@ubuntu]:[~][tty:3]#

该进程的pid1365

如上使用unshare创建了3个“容器”,但是没有使用pid namespace,作用是为了方便查看其进程真实的pid

将veth串联起来

下面将使用veth串联起来bridge设备和容器。

veth0移动到第一个容器中

通过上述创建容器的命令可得,第一个容器的pid1349,下面命令将veth0设备移动到第一个容器中。

[root@ubuntu]:[~][tty:0]# ip link set veth0 netns 1349
[root@ubuntu]:[~][tty:0]#

查看第一个容器中的相关网卡。

(unshare) [root@ubuntu]:[~][tty:1]# ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
6: veth0@if5: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 66:77:10:54:81:0e brd ff:ff:ff:ff:ff:ff link-netnsid 0
(unshare) [root@ubuntu]:[~][tty:1]#

veth1连接到br0

[root@ubuntu]:[~][tty:0]# ip link set veth1 master br0
[root@ubuntu]:[~][tty:0]#

查看br0连接的信息。

[root@ubuntu]:[~][tty:0]# brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.72cfbe81fc62       no              veth1
[root@ubuntu]:[~][tty:0]#

veth2移动到第二个容器中

通过上述创建容器的命令可得,第二个容器的pid1357,下面命令将veth2设备移动到第一个容器中。

[root@ubuntu]:[~][tty:0]# ip link set veth2 netns 1357
[root@ubuntu]:[~][tty:0]#

查看第二个容器中的相关网卡。

(unshare) [root@ubuntu]:[~][tty:2]# ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
8: veth2@if7: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 9a:c0:ed:69:8c:4e brd ff:ff:ff:ff:ff:ff link-netnsid 0
(unshare) [root@ubuntu]:[~][tty:2]#

veth3连接到br0

[root@ubuntu]:[~][tty:0]# ip link set veth3 master br0
[root@ubuntu]:[~][tty:0]#

查看br0连接的信息。

[root@ubuntu]:[~][tty:0]# brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.72cfbe81fc62       no              veth1
                                                        veth3
[root@ubuntu]:[~][tty:0]#

veth4移动到第三个容器中

通过上述创建容器的命令可得,第二个容器的pid1365,下面命令将veth4设备移动到第一个容器中。

[root@ubuntu]:[~][tty:0]# ip link set veth4 netns 1365
[root@ubuntu]:[~][tty:0]#

查看第三个容器中的网卡信息。

(unshare) [root@ubuntu]:[~][tty:3]# ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
10: veth4@if9: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether d2:a0:6a:c1:fe:80 brd ff:ff:ff:ff:ff:ff link-netnsid 0
(unshare) [root@ubuntu]:[~][tty:3]#

veth5连接到br0

[root@ubuntu]:[~][tty:0]# ip link set veth5 master br0
[root@ubuntu]:[~][tty:0]#

查看br0连接信息。

[root@ubuntu]:[~][tty:0]# brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.72cfbe81fc62       no              veth1
                                                        veth3
                                                        veth5
[root@ubuntu]:[~][tty:0]#

查看宿主机现有的网络信息

[root@ubuntu]:[~][tty:0]# ip link
......
4: br0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 72:cf:be:81:fc:62 brd ff:ff:ff:ff:ff:ff
5: veth1@if6: <BROADCAST,MULTICAST> mtu 1500 qdisc noop master br0 state DOWN mode DEFAULT group default qlen 1000
    link/ether ae:dd:f7:9d:9b:f8 brd ff:ff:ff:ff:ff:ff link-netnsid 1
7: veth3@if8: <BROADCAST,MULTICAST> mtu 1500 qdisc noop master br0 state DOWN mode DEFAULT group default qlen 1000
    link/ether 52:6c:7b:57:b2:7b brd ff:ff:ff:ff:ff:ff link-netnsid 0
9: veth5@if10: <BROADCAST,MULTICAST> mtu 1500 qdisc noop master br0 state DOWN mode DEFAULT group default qlen 1000
    link/ether a2:c7:ea:83:fc:91 brd ff:ff:ff:ff:ff:ff link-netnsid 2
[root@ubuntu]:[~][tty:0]#

可以看到,在宿主机中就只能看到br0veth1veth3veth5设备了,其他的veth0veth2veth4已经被移动到相应的容器中去了。

给虚拟设备配置IP地址

配置设备br0的地址

由于br0在宿主机上,所以需要在宿主机执行。

[root@ubuntu]:[~][tty:0]# ip addr add 192.168.1.250/24 dev br0
[root@ubuntu]:[~][tty:0]#

查看br0地址

[root@ubuntu]:[~][tty:0]# ip addr show br0
4: br0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 72:cf:be:81:fc:62 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.250/24 scope global br0
       valid_lft forever preferred_lft forever
[root@ubuntu]:[~][tty:0]#

配置veth0ip地址

由于veth0已经移动到第一个容器中去了,所以需要到该容器中执行。

(unshare) [root@ubuntu]:[~][tty:1]# ip addr add 192.168.1.245/24 dev veth0
(unshare) [root@ubuntu]:[~][tty:1]#

查看veth0地址。

(unshare) [root@ubuntu]:[~][tty:1]# ip addr show veth0
6: veth0@if5: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 66:77:10:54:81:0e brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.1.245/24 scope global veth0
       valid_lft forever preferred_lft forever
(unshare) [root@ubuntu]:[~][tty:1]#

配置veth2ip地址

由于veth2已经移动到第二个容器中去了,所以需要到该容器中执行。

(unshare) [root@ubuntu]:[~][tty:2]# ip addr add 192.168.1.246/24 dev veth2
(unshare) [root@ubuntu]:[~][tty:2]#

查看veth2地址。

(unshare) [root@ubuntu]:[~][tty:2]# ip addr show veth2
8: veth2@if7: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 9a:c0:ed:69:8c:4e brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.1.246/24 scope global veth2
       valid_lft forever preferred_lft forever
(unshare) [root@ubuntu]:[~][tty:2]#

配置veth4ip地址

由于veth4已经移动到第三个容器中去了,所以需要到该容器中执行。

(unshare) [root@ubuntu]:[~][tty:3]# ip addr add 192.168.1.247/24 dev veth4
(unshare) [root@ubuntu]:[~][tty:3]#

查看veth4地址。

(unshare) [root@ubuntu]:[~][tty:3]# ip addr show veth4
10: veth4@if9: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether d2:a0:6a:c1:fe:80 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.1.247/24 scope global veth4
       valid_lft forever preferred_lft forever
(unshare) [root@ubuntu]:[~][tty:3]#

启用虚拟设备

虚拟设备创建成功后,及时已经配置了ip地址,其网卡状态依然是关闭的(DOWN),需要手动启动才行。接下来,开始逐个启动网络设备。

启动br0

br0在宿主机,所以需要在宿主机操作。

[root@ubuntu]:[~][tty:0]# ip link set br0 up
[root@ubuntu]:[~][tty:0]#

再查看br0ip地址。

[root@ubuntu]:[~][tty:0]# ip addr show br0
4: br0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
    link/ether 72:cf:be:81:fc:62 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.250/24 scope global br0
       valid_lft forever preferred_lft forever
[root@ubuntu]:[~][tty:0]#

虽然此时的状态还是DOWN,但是也已经可以ping通了。

[root@ubuntu]:[~][tty:0]# ping -c 4 192.168.1.250
PING 192.168.1.250 (192.168.1.250) 56(84) bytes of data.
64 bytes from 192.168.1.250: icmp_seq=1 ttl=64 time=0.018 ms
64 bytes from 192.168.1.250: icmp_seq=2 ttl=64 time=0.027 ms
64 bytes from 192.168.1.250: icmp_seq=3 ttl=64 time=0.025 ms
64 bytes from 192.168.1.250: icmp_seq=4 ttl=64 time=0.026 ms

--- 192.168.1.250 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3110ms
rtt min/avg/max/mdev = 0.018/0.024/0.027/0.003 ms
[root@ubuntu]:[~][tty:0]#

启动宿主机上的veth设备

veth1veth3veth5都在宿主机上,需要将其启动。

[root@ubuntu]:[~][tty:0]# ip link set veth1 up
[root@ubuntu]:[~][tty:0]# ip link set veth3 up
[root@ubuntu]:[~][tty:0]# ip link set veth5 up
[root@ubuntu]:[~][tty:0]#

启动veth0设备

veth0在第一个容器上(pid:1349),需要将其启动。

(unshare) [root@ubuntu]:[~][tty:1]# ip link set veth0 up
(unshare) [root@ubuntu]:[~][tty:1]#
(unshare) [root@ubuntu]:[~][tty:1]# ip addr show veth0
6: veth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 66:77:10:54:81:0e brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.1.245/24 scope global veth0
       valid_lft forever preferred_lft forever
    inet6 fe80::6477:10ff:fe54:810e/64 scope link proto kernel_ll
       valid_lft forever preferred_lft forever
(unshare) [root@ubuntu]:[~][tty:1]#

启动veth2设备

veth2在第二个容器上(pid:1357),需要将其启动。

(unshare) [root@ubuntu]:[~][tty:2]# ip link set veth2 up
(unshare) [root@ubuntu]:[~][tty:2]#
(unshare) [root@ubuntu]:[~][tty:2]# ip addr show veth2
8: veth2@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 9a:c0:ed:69:8c:4e brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.1.246/24 scope global veth2
       valid_lft forever preferred_lft forever
    inet6 fe80::98c0:edff:fe69:8c4e/64 scope link proto kernel_ll
       valid_lft forever preferred_lft forever
(unshare) [root@ubuntu]:[~][tty:2]#

启动veth4设备

veth4在第三个容器上(pid:1365),需要将其启动。

(unshare) [root@ubuntu]:[~][tty:3]# ip link set veth4 up
(unshare) [root@ubuntu]:[~][tty:3]#
(unshare) [root@ubuntu]:[~][tty:3]# ip addr show veth4
10: veth4@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether d2:a0:6a:c1:fe:80 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.1.247/24 scope global veth4
       valid_lft forever preferred_lft forever
    inet6 fe80::d0a0:6aff:fec1:fe80/64 scope link proto kernel_ll
       valid_lft forever preferred_lft forever
(unshare) [root@ubuntu]:[~][tty:3]#

容器之间互相通信

有了bridge的加持,连接上的设备都可以相互通信,此时容器可以相互通信了。

比如在第三个容器上,可以ping通第一个容器和第二个容器。

(unshare) [root@ubuntu]:[~][tty:3]# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
10: veth4@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether d2:a0:6a:c1:fe:80 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.1.247/24 scope global veth4
       valid_lft forever preferred_lft forever
    inet6 fe80::d0a0:6aff:fec1:fe80/64 scope link proto kernel_ll
       valid_lft forever preferred_lft forever
(unshare) [root@ubuntu]:[~][tty:3]#
(unshare) [root@ubuntu]:[~][tty:3]# # ping 容器1
(unshare) [root@ubuntu]:[~][tty:3]# ping 192.168.1.245 -c 4
PING 192.168.1.245 (192.168.1.245) 56(84) bytes of data.
64 bytes from 192.168.1.245: icmp_seq=1 ttl=64 time=0.021 ms
64 bytes from 192.168.1.245: icmp_seq=2 ttl=64 time=0.035 ms
64 bytes from 192.168.1.245: icmp_seq=3 ttl=64 time=0.037 ms
64 bytes from 192.168.1.245: icmp_seq=4 ttl=64 time=0.039 ms

--- 192.168.1.245 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3101ms
rtt min/avg/max/mdev = 0.021/0.033/0.039/0.007 ms
(unshare) [root@ubuntu]:[~][tty:3]#
(unshare) [root@ubuntu]:[~][tty:3]#
(unshare) [root@ubuntu]:[~][tty:3]# # ping 容器2
(unshare) [root@ubuntu]:[~][tty:3]# ping 192.168.1.246 -c 4
PING 192.168.1.246 (192.168.1.246) 56(84) bytes of data.
64 bytes from 192.168.1.246: icmp_seq=1 ttl=64 time=0.049 ms
64 bytes from 192.168.1.246: icmp_seq=2 ttl=64 time=0.036 ms
64 bytes from 192.168.1.246: icmp_seq=3 ttl=64 time=0.035 ms
64 bytes from 192.168.1.246: icmp_seq=4 ttl=64 time=0.038 ms

--- 192.168.1.246 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3061ms
rtt min/avg/max/mdev = 0.035/0.039/0.049/0.005 ms
(unshare) [root@ubuntu]:[~][tty:3]#

同样的,在第一个容器上,也可以ping通第二个容器和第三个容器。

(unshare) [root@ubuntu]:[~][tty:1]# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
6: veth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 66:77:10:54:81:0e brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.1.245/24 scope global veth0
       valid_lft forever preferred_lft forever
    inet6 fe80::6477:10ff:fe54:810e/64 scope link proto kernel_ll
       valid_lft forever preferred_lft forever
(unshare) [root@ubuntu]:[~][tty:1]#
(unshare) [root@ubuntu]:[~][tty:1]# # ping 容器2
(unshare) [root@ubuntu]:[~][tty:1]# ping -c 4  192.168.1.246
PING 192.168.1.246 (192.168.1.246) 56(84) bytes of data.
64 bytes from 192.168.1.246: icmp_seq=1 ttl=64 time=0.045 ms
64 bytes from 192.168.1.246: icmp_seq=2 ttl=64 time=0.035 ms
64 bytes from 192.168.1.246: icmp_seq=3 ttl=64 time=0.061 ms
64 bytes from 192.168.1.246: icmp_seq=4 ttl=64 time=0.037 ms

--- 192.168.1.246 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3091ms
rtt min/avg/max/mdev = 0.035/0.044/0.061/0.010 ms
(unshare) [root@ubuntu]:[~][tty:1]# # ping 容器3
(unshare) [root@ubuntu]:[~][tty:1]# ping -c 4  192.168.1.247
PING 192.168.1.247 (192.168.1.247) 56(84) bytes of data.
64 bytes from 192.168.1.247: icmp_seq=1 ttl=64 time=0.022 ms
64 bytes from 192.168.1.247: icmp_seq=2 ttl=64 time=0.059 ms
64 bytes from 192.168.1.247: icmp_seq=3 ttl=64 time=0.037 ms
64 bytes from 192.168.1.247: icmp_seq=4 ttl=64 time=0.036 ms

--- 192.168.1.247 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3054ms
rtt min/avg/max/mdev = 0.022/0.038/0.059/0.013 ms
(unshare) [root@ubuntu]:[~][tty:1]#

让容器连上外网

现在的容器之间能够互相通信了,但是还是不能上外网,例如ping 8.8.8.8

(unshare) [root@ubuntu]:[~][tty:1]# ping 8.8.8.8
ping: connect: Network is unreachable
(unshare) [root@ubuntu]:[~][tty:1]#

显示的还是网络不可达,但是宿主机是可以上网的,可以将流量发送到宿主机上,再由宿主机转发出去即可,

首先要定义路由表,增加一条默认路由,将流量指向br0,即192.168.1.250

先查看其路由信息。

(unshare) [root@ubuntu]:[~][tty:1]# ip route
192.168.1.0/24 dev veth0 proto kernel scope link src 192.168.1.245
(unshare) [root@ubuntu]:[~][tty:1]#

添加一条默认路由,将流量都指向br0

(unshare) [root@ubuntu]:[~][tty:1]# ip route add default via 192.168.1.250
(unshare) [root@ubuntu]:[~][tty:1]#
(unshare) [root@ubuntu]:[~][tty:1]# ip route
default via 192.168.1.250 dev veth0
192.168.1.0/24 dev veth0 proto kernel scope link src 192.168.1.245
(unshare) [root@ubuntu]:[~][tty:1]#

定义完路由后,需要在宿主机上做转发。

首先需要允许内核进行ip转发。

[root@ubuntu]:[~][tty:0]# sysctl net.ipv4.conf.all.forwarding=1
net.ipv4.conf.all.forwarding = 1
[root@ubuntu]:[~][tty:0]#

而后再定一个nat规则,将容器上来的数据包,修改为宿主机吱声的,从而请求,报文回复后,再进行nat转发。

[root@ubuntu]:[~][tty:0]# iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -j MASQUERADE
[root@ubuntu]:[~][tty:0]#

此时再在容器中,就可以连接上外网了。

(unshare) [root@ubuntu]:[~][tty:1]# ping 8.8.8.8 -c 4
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=54 time=34.1 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=54 time=33.8 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=54 time=33.7 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=54 time=34.0 ms

--- 8.8.8.8 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3004ms
rtt min/avg/max/mdev = 33.705/33.907/34.094/0.165 ms
(unshare) [root@ubuntu]:[~][tty:1]#

其他2个容器,仅需添加默认路由为192.168.1.250,即可上网。

首先操作第二个容器。

(unshare) [root@ubuntu]:[~][tty:2]# ip route add default via 192.168.1.250
(unshare) [root@ubuntu]:[~][tty:2]# ping 8.8.8.8 -c 4
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=54 time=34.0 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=54 time=34.1 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=54 time=34.2 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=54 time=34.1 ms

--- 8.8.8.8 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3003ms
rtt min/avg/max/mdev = 34.047/34.110/34.172/0.044 ms
(unshare) [root@ubuntu]:[~][tty:2]#

最后操作第三个容器。

(unshare) [root@ubuntu]:[~][tty:3]# ip route add default via 192.168.1.250
(unshare) [root@ubuntu]:[~][tty:3]# ping 8.8.8.8 -c 4
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=54 time=35.5 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=54 time=34.0 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=54 time=34.3 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=54 time=33.7 ms

--- 8.8.8.8 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3003ms
rtt min/avg/max/mdev = 33.713/34.397/35.524/0.683 ms
(unshare) [root@ubuntu]:[~][tty:3]#

将容器的端口映射进出去

使用iptables进行端口映射

这个和bridge没有什么关系,只是让容器可以上网了,还有一个很常见的需求是,将容器的某个端口映射出去。比如,将宿主机的8080端口,映射到第一个容器的80端口上,即访问宿主机的8080端口,实际上是访问的第一个容器的80端口。

此类也是需要iptablesnat转发。

比如,第一个容器的ip地址为192.168.1.245,可以将宿主机的8080端口,转发到192.168.1.24580端口上,例如:

[root@ubuntu]:[~][tty:0]# iptables -t nat -A OUTPUT -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.245:80
[root@ubuntu]:[~][tty:0]# iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.245:80

添加了这2条规则,其中OUTPUT是指在本地发出的流量,而PREROUTING是指外部进入本机的流量。DNAT是修改流量的目标地址,将流量定向到192.168.1.245:80

添加了之后,在外部和内部都访问宿主机的8080即可。

首先,在第一个容器中(ip:192.168.1.245)使用nc监听80端口。

(unshare) [root@ubuntu]:[~][tty:1]# nc -lp 80

在外部机器,使用telnet进行请求。

$ telnet 192.168.100.102 8080
Trying 192.168.100.141...
Connected to 192.168.100.102.
Escape character is '^]'.
hello world namespace 1
^]

telnet> quit
Connection closed.
$

此时,在第一个容器中的nc也有了相同的消息。

(unshare) [root@ubuntu]:[~][tty:1]# nc -lp 80
hello world namespace 1
(unshare) [root@ubuntu]:[~][tty:1]#

在宿主机上同样的请求192.168.100.102 8080也是可以进行通信的。

使用程序进行端口映射

这样操作太过麻烦,且在iptables使用了端口转发,使用ss也查看不到,所以,最好还是直接使用程序的方法进行转发,例如,使用go写一个最简单的转发命令。

package main

import (
    "flag"
    "fmt"
    "io"
    "net"
)

var (
    tcpPort string
    target  string
)

func init() {
    flag.StringVar(&tcpPort, "tcpPort", "", "Specify TCP port")
    flag.StringVar(&target, "target", "", "Specify the target address")
}

func main() {

    flag.Parse()

    if tcpPort == "" && target == "" {
        fmt.Println("No specified tcpPort or destination address")
        return
    }

    l, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%s", tcpPort))
    if err != nil {
        fmt.Println("Listening to TCP error ", err)
        return
    }

    for {
        conn, err := l.Accept()
        if err != nil {
            fmt.Println("accept error", err)
            continue
        }
        go handleConn(conn, target)
    }
}

func handleConn(conn net.Conn, targetAddr string) {
    defer conn.Close()

    fmt.Println(conn.RemoteAddr().String(), "is connected.")

    remoteConn, err := net.Dial("tcp", targetAddr)
    if err != nil {
        fmt.Println("connet tcp ", targetAddr, " error", err)
        return
    }
    defer remoteConn.Close()

    go io.Copy(remoteConn, conn)
    io.Copy(conn, remoteConn)
}

上述命令是一个及其简单的tcp转发程序,需要输入的参数有2个。

  • --tcpPort:用于监听宿主机的端口。
  • --target:用于转发的容器地址(也可以是任何网络地址)。

先进行编译。

[root@ubuntu]:[~][tty:4]# go build -o portForward 
[root@ubuntu]:[~][tty:4]# 

运行程序。

[root@ubuntu]:[~][tty:4]# ./portForward --tcpPort 8081 --target 192.168.1.245:80

表示将宿主机的8081端口,映射到192.168.1.24580端口上。

这样的话,现在本地尝试telnet下。

[root@ubuntu]:[~][tty:5]# telnet 127.0.0.1 8081
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
hello world ,test portForward
^]
telnet>
^]
telnet> quit
Connection closed.
[root@ubuntu]:[~][tty:5]#

查看第一个容器的nc日志。

(unshare) [root@ubuntu]:[~][tty:1]# nc -lp 80
hello world ,test portForward

使用外网再测试下。

liwang@DESKTOP-CU66GL1:~$ telnet 192.168.100.102 8081
Trying 192.168.100.102...
Connected to 192.168.100.102.
Escape character is '^]'.
hello world port forward out
^]

telnet> quit
Connection closed.
liwang@DESKTOP-CU66GL1:~$

同样的查看nc日志。

(unshare) [root@ubuntu]:[~][tty:1]# nc -lp 80
hello world port forward out

查看portForward程序的日志。

[root@ubuntu]:[~][tty:4]# ./portForward --tcpPort 8081 --target 192.168.1.245:80
127.0.0.1:54988 is connected.
192.168.100.101:63441 is connected.

通过上述例子,可见,直接使用程序监听端口,比设置iptables更加容易的多。

总结

linux bridge是虚拟网桥,可以将多个网络接口连接在一起的虚拟设备,上述案例,创建多个容器,为每个容器都分配了veth,另一端则直接接入到了bridge中,这样可以实现容器间通信,容器间和宿主机的通信。想让容器能够上网的话,需要现将容器的默认路由指向bridge,再在宿主机上设置iptables转发规则即可。




容器中的网络-bridge

https://wangli2025.github.io/2024/11/19/docker_network_bridge.html

本站均为原创文章,采用 CC BY-NC-ND 4.0 协议。转载请注明出处,不得用于商业用途。

Comments