容器中的网络-bridge
本文将简单了解Linux bridge
。
什么是Linux bridge
Linux bridge
也称为Linux
虚拟网桥,也可以理解为一台虚拟的网络交换机,它是一种链路层设备,允许根据MAC
地址在网络之间转发流量。所以在Linux
中可以用它来模拟网桥。bridge
不仅允许虚拟设备接入,还支持物理设备接入。
此前已经简单了解了veth
虚拟接口是如何通信的,这次来了解下bridge
和veth
如何配合使用。
实验准备
准备1个bridge
信息分别为:
bridge信息
bridge
:192.168.1.250/24
准备3个veth
,信息分别为:
第一对veth
veth0
、veth1
veth0
:192.168.1.245/24
veth1
: 接入br0
第二对veth
veth2
、veth3
veth2
:192.168.1.246/24
veth3
: 接入br0
第三对veth
veth4
、veth5
veth4
:192.168.1.247/24
veth5
: 接入br0
而后再将veth0
、veth2
、veth4
分别放入不同的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]#
该进程的pid
为1349
。
创建第二个容器。
[root@ubuntu]:[~][tty:2]# unshare -m -u -i -n -f /bin/bash
(unshare) [root@ubuntu]:[~][tty:2]# echo $$
1357
(unshare) [root@ubuntu]:[~][tty:2]#
该进程的pid
为1357
。
创建第三个容器。
[root@ubuntu]:[~][tty:3]# unshare -m -u -i -n -f /bin/bash
(unshare) [root@ubuntu]:[~][tty:3]# echo $$
1365
(unshare) [root@ubuntu]:[~][tty:3]#
该进程的pid
为1365
。
如上使用unshare
创建了3个“容器”,但是没有使用pid namespace
,作用是为了方便查看其进程真实的pid
。
将veth串联起来
下面将使用veth
串联起来bridge
设备和容器。
将veth0
移动到第一个容器中
通过上述创建容器的命令可得,第一个容器的pid
为1349
,下面命令将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
移动到第二个容器中
通过上述创建容器的命令可得,第二个容器的pid
为1357
,下面命令将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
移动到第三个容器中
通过上述创建容器的命令可得,第二个容器的pid
为1365
,下面命令将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]#
可以看到,在宿主机中就只能看到br0
和veth1
、veth3
、veth5
设备了,其他的veth0
、veth2
、veth4
已经被移动到相应的容器中去了。
给虚拟设备配置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]#
配置veth0
的ip
地址
由于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]#
配置veth2
的ip
地址
由于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]#
配置veth4
的ip
地址
由于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]#
再查看br0
的ip
地址。
[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
设备
veth1
、veth3
、veth5
都在宿主机上,需要将其启动。
[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
端口。
此类也是需要iptables
做nat
转发。
比如,第一个容器的ip
地址为192.168.1.245
,可以将宿主机的8080
端口,转发到192.168.1.245
的80
端口上,例如:
[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.245
的80
端口上。
这样的话,现在本地尝试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 协议。转载请注明出处,不得用于商业用途。