容器中的网络-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虚拟设备。
| Bash |
|---|
| [root@ubuntu]:[~][tty:0]# ip link add name br0 type bridge
|
如上添加了bridge,名称为br0,使用如下命令,可以查看所有的bridge虚拟设备。
| Bash |
|---|
| [root@ubuntu]:[~][tty:0]# brctl show
bridge name bridge id STP enabled interfaces
br0 8000.72cfbe81fc62 no
[root@ubuntu]:[~][tty:0]#
|
创建veth
首先在宿主机上创建3条veth,命令如下:
| Bash |
|---|
| [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的即可。
| Bash |
|---|
| [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麻烦。
创建第一个容器。
| Bash |
|---|
| [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。
创建第二个容器。
| Bash |
|---|
| [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。
创建第三个容器。
| Bash |
|---|
| [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设备移动到第一个容器中。
| Bash |
|---|
| [root@ubuntu]:[~][tty:0]# ip link set veth0 netns 1349
[root@ubuntu]:[~][tty:0]#
|
查看第一个容器中的相关网卡。
| Bash |
|---|
| (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中
| Bash |
|---|
| [root@ubuntu]:[~][tty:0]# ip link set veth1 master br0
[root@ubuntu]:[~][tty:0]#
|
查看br0连接的信息。
| Bash |
|---|
| [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设备移动到第一个容器中。
| Bash |
|---|
| [root@ubuntu]:[~][tty:0]# ip link set veth2 netns 1357
[root@ubuntu]:[~][tty:0]#
|
查看第二个容器中的相关网卡。
| Bash |
|---|
| (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中
| Bash |
|---|
| [root@ubuntu]:[~][tty:0]# ip link set veth3 master br0
[root@ubuntu]:[~][tty:0]#
|
查看br0连接的信息。
| Bash |
|---|
| [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设备移动到第一个容器中。
| Bash |
|---|
| [root@ubuntu]:[~][tty:0]# ip link set veth4 netns 1365
[root@ubuntu]:[~][tty:0]#
|
查看第三个容器中的网卡信息。
| Bash |
|---|
| (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中
| Text Only |
|---|
| [root@ubuntu]:[~][tty:0]# ip link set veth5 master br0
[root@ubuntu]:[~][tty:0]#
|
查看br0连接信息。
| Bash |
|---|
| [root@ubuntu]:[~][tty:0]# brctl show
bridge name bridge id STP enabled interfaces
br0 8000.72cfbe81fc62 no veth1
veth3
veth5
[root@ubuntu]:[~][tty:0]#
|
查看宿主机现有的网络信息
| Bash |
|---|
| [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在宿主机上,所以需要在宿主机执行。
| Bash |
|---|
| [root@ubuntu]:[~][tty:0]# ip addr add 192.168.1.250/24 dev br0
[root@ubuntu]:[~][tty:0]#
|
查看br0地址
| Bash |
|---|
| [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已经移动到第一个容器中去了,所以需要到该容器中执行。
| Bash |
|---|
| (unshare) [root@ubuntu]:[~][tty:1]# ip addr add 192.168.1.245/24 dev veth0
(unshare) [root@ubuntu]:[~][tty:1]#
|
查看veth0地址。
| Bash |
|---|
| (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已经移动到第二个容器中去了,所以需要到该容器中执行。
| Bash |
|---|
| (unshare) [root@ubuntu]:[~][tty:2]# ip addr add 192.168.1.246/24 dev veth2
(unshare) [root@ubuntu]:[~][tty:2]#
|
查看veth2地址。
| Bash |
|---|
| (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已经移动到第三个容器中去了,所以需要到该容器中执行。
| Bash |
|---|
| (unshare) [root@ubuntu]:[~][tty:3]# ip addr add 192.168.1.247/24 dev veth4
(unshare) [root@ubuntu]:[~][tty:3]#
|
查看veth4地址。
| Bash |
|---|
| (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在宿主机,所以需要在宿主机操作。
| Bash |
|---|
| [root@ubuntu]:[~][tty:0]# ip link set br0 up
[root@ubuntu]:[~][tty:0]#
|
再查看br0的ip地址。
| Bash |
|---|
| [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通了。
| Bash |
|---|
| [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都在宿主机上,需要将其启动。
| Bash |
|---|
| [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),需要将其启动。
| Bash |
|---|
| (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),需要将其启动。
| Bash |
|---|
| (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),需要将其启动。
| Bash |
|---|
| (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通第一个容器和第二个容器。
| Bash |
|---|
| (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通第二个容器和第三个容器。
| Bash |
|---|
| (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。
| Bash |
|---|
| (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。
先查看其路由信息。
| Bash |
|---|
| (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。
| Bash |
|---|
| (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转发。
| Bash |
|---|
| [root@ubuntu]:[~][tty:0]# sysctl net.ipv4.conf.all.forwarding=1
net.ipv4.conf.all.forwarding = 1
[root@ubuntu]:[~][tty:0]#
|
而后再定一个nat规则,将容器上来的数据包,修改为宿主机吱声的,从而请求,报文回复后,再进行nat转发。
| Bash |
|---|
| [root@ubuntu]:[~][tty:0]# iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -j MASQUERADE
[root@ubuntu]:[~][tty:0]#
|
此时再在容器中,就可以连接上外网了。
| Bash |
|---|
| (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,即可上网。
首先操作第二个容器。
| Bash |
|---|
| (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]#
|
最后操作第三个容器。
| Bash |
|---|
| (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端口上,例如:
| Bash |
|---|
| [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端口。
| Bash |
|---|
| (unshare) [root@ubuntu]:[~][tty:1]# nc -lp 80
|
在外部机器,使用telnet进行请求。
| Bash |
|---|
| $ 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也有了相同的消息。
| Bash |
|---|
| (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写一个最简单的转发命令。
| 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:用于转发的容器地址(也可以是任何网络地址)。
先进行编译。
| Bash |
|---|
| [root@ubuntu]:[~][tty:4]# go build -o portForward
[root@ubuntu]:[~][tty:4]#
|
运行程序。
| Bash |
|---|
| [root@ubuntu]:[~][tty:4]# ./portForward --tcpPort 8081 --target 192.168.1.245:80
|
表示将宿主机的8081端口,映射到192.168.1.245的80端口上。
这样的话,现在本地尝试telnet下。
| Bash |
|---|
| [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日志。
| Bash |
|---|
| (unshare) [root@ubuntu]:[~][tty:1]# nc -lp 80
hello world ,test portForward
|
使用外网再测试下。
| Bash |
|---|
| 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日志。
| Bash |
|---|
| (unshare) [root@ubuntu]:[~][tty:1]# nc -lp 80
hello world port forward out
|
查看portForward程序的日志。
| Bash |
|---|
| [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转发规则即可。