之前有另一篇文章介绍过,容器可以通过网络命名空间来和主机之间进行网络的隔离,这篇文章会继续介绍一下容器的几种组网模式。这个对于我们在使用容器进行服务部署的时候来理解不同服务之间的通信尤为重要。
首先对于host系统上的物理网络设备,一个物理的网络设备最多存在于一个Network namespace中,所以通常我们不会给容器分配一个物理的网络设备,而是用虚拟网卡来替代,比如说使用veth pair进行不同的Network namespace之间的通信,
- 可以是veth pair的直连
- 也可以是借助bridge进行连接通信
当然容器也可以和主机不进行网络命名空间的隔离,使用host模式和主机共享网络设备和配置。
1. 桥接模式(Bridge)
Docker创建新的容器的时候默认的网络模式是桥接,如图所示
- docker0是虚拟网桥
- 一端通过veth pair和container相连;
- 另一端通过SNAT/DNAT连接外部网络
1.1. veth pair
另一篇文章中有介绍,通过如下命令可以手动添加一个veth对到linux系统中,
$ ip link add veth0 type veth peer name veth01
如果像docker这样,已经默认构建好了内部网络,在host系统中我们只可以查看到veth01这个虚拟网口,
$ ip -d link show
5: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
....
5: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 02:42:4a:90:6c:62 brd ff:ff:ff:ff:ff:ff
...
7: vethf0f03d4@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default
link/ether ae:58:d8:8e:46:72 brd ff:ff:ff:ff:ff:ff link-netnsid 0
依据打印出来的信息可以看出来,
- index 5的网口就是docker默认的虚拟网关
- index 7对应的网口vethf0f03d4就是和container相连的虚拟网卡,后面还跟了
@if6
代表它和index 6的网卡互为veth pair;
如果这个时候打开docker容器查看,可以看到容器中的网卡的index就是6。
除此之外还可以通过如下命令来查看Linux系统中的veth pair
# ethtool -S vethf0f03d4
NIC statistics:
peer_ifindex: 6
1.2. docker0
可以使用如下命令来查看现在系统中创建的bridge
# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02424a906c62 no veth8ef89b4
vetha95b32d
当前这个例子中创建了两个容器,使用的都是默认网络;对应的host系统上也会有两个虚拟网卡veth8ef89b4和vetha95b32d
1.3. 路由表
- 首先查看一下容器中的ip路由表
# cat /proc/net/route
Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
eth0 00000000 010011AC 0003 0 0 0 00000000 0 0 0
eth0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0
- 第一条是默认路由,会通过eth0这个网口发往默认的网关,这里的网关是010011AC(172.17.0.1,大端终结)
- 第二条是所有发往172.17.0.0(掩码是255.255.0.0)网段的数据包都由本机处理(网关是0.0.0.0);
- 然后我们看一下host的路由表
$ ip route show
default via 10.0.2.2 dev enp0s3 proto static metric 100
...
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
- 默认路由是所有找不到路由信息的数据,都通过ip地址10.0.2.2以及网络接口enp0s3发送出去;
- docker0这条路由的意思是,所有docker0接口上收到的发往172.17.0.0/16网段的数据包都由本机处理;
1.4. iptables
1.4.1. 首先在host上查看filter表的链
iptables -L -n
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:53
ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:53
ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:67
ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:67
Chain FORWARD (policy ACCEPT)
target prot opt source destination
DOCKER-ISOLATION all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED
DOCKER all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
ACCEPT all -- 0.0.0.0/0 0.0.0.0/0
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain DOCKER (1 references)
target prot opt source destination
ACCEPT tcp -- 0.0.0.0/0 172.17.0.2 tcp dpt:443
ACCEPT tcp -- 0.0.0.0/0 172.17.0.2 tcp dpt:80
ACCEPT tcp -- 0.0.0.0/0 172.17.0.4 tcp dpt:80
Chain DOCKER-ISOLATION (1 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
这里我们可以看到有两个和docker有关的链
- Docker-isolation chain:包含限制不同容器网络之间的访问规则;
- Docker chain:包含容器的访问规则,例如这里就允许HTTP(port:80)和HTTPs(port: 443)两种访问;
Chain FORWAR中配置了转发策略,所有的数据包都会转发到DOCKER这个chain上;然后DOCKER这个CHAIN上会设置过滤规则。
1.4.2 然后查看nat表链
# iptables -t nat -L -n
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
MASQUERADE all -- 10.0.3.0/24 !10.0.3.0/24
MASQUERADE tcp -- 172.17.0.2 172.17.0.2 tcp dpt:443
MASQUERADE tcp -- 172.17.0.2 172.17.0.2 tcp dpt:80
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:443 to:172.17.0.2:443
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.4:80
NAT允许主机修改数据包的IP地址或者端口,
- SNAT
也就是这里Chain POSTROUTING中设置的MASQUERADE规则,看第一条,所有的源地址是172.17.0.0/16网段的数据包的源IP都会修改成这个数据要发送的网络接口上的IP地址;比如说之前route中发现这个数据包适用默认路由,要经过enp0s3 (IP:10.0.2.2)发送到默认网关,这个时候源IP地址就会修改成10.0.2.2;
Note: SNAT和MASQUERAGE的区别,SNAT需要指明目的地址要修改成具体的哪个或者哪几个IP地址;MASQUERAGE只要指定输出的网络口是哪个,系统会自动获取这个网络口的IP地址并进行伪装。后者对于动态分配IP的场景很有效。
- DNAT
Chain DOCKER设置了DNAT规则。例如第一条表示,所有的目的端口为443的数据包的目的地址都会修改成172.17.0.2:443,然后数据包才会去匹配路由规则,看下一跳要发送到哪里。
NOTE:所以这里可以看出来,如果你通过端口映射8080:80把主机的端口8080暴露给外网了,也就相当于主机上所有网络接口的8080端口都暴露给外网访问了,这个会破坏原有的iptables防火墙规则,可能会带来危险。
1.5. 连起来就是如下这么一个数据收发流程
2. 主机模式(Host)
使用主机的network namespace以及网络配置。
缺点:容器可以修改主机的网络配置,没有做网络的隔离。
还是来看个实际的例子,启动一个nginx的容器,
docker run --rm -dit --network host --name my_nginx nginx
- 查看一下当前host上的网络接口
ip addr show
可以看到这个时候没有添加新的veth口,这个和bridge的模式不一样。
- 再来看看端口80绑定到哪个进程了
$ sudo netstat -tulpn | grep :80
这里必须要使用sudo,因为nginx进程是属于Docker daemon用户的,否则没法儿获取进程名和PID。
可以看到这个例子里会输出如下信息,80端口直接绑定到nginx这个进程上了,
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 9815/nginx: master
如果配置成了桥接模式,会打印这样的信息,80端口没有直接绑定到nginx,而是绑定到了docker-proxy。
tcp6 0 0 :::80 :::* LISTEN 2972/docker-proxy
3. Overlay网络模式
overlay这种网络模式下,会在多个Docker Daemon主机的基础上创建一个分布式网络,这个分布式网络允许容器连接上去并且互相通信。这种部署方式相对复杂,后面单独找个时间去研究一下。
4. Macvlan网络模式
一些应用程序,尤其是一些遗留的程序或者是那些监控网络流量的程序,需要直接连接到物理网络上。这种情况下,你可以使用macvlan网络驱动来给每个容器的虚拟网络接口都分配一个MAC地址,让这些网络接口看上去像是一个物理网卡,并且能直接连到物理网络上。这种情况下你需要指定host上的一个物理网卡用于Macvlan,以及子网,和Macvlan的网关。你甚至可以通过使用不同的物理网卡来隔离Macvlan。注意以下几点,
- 由于IP地址的耗尽或者由于VLAN spread而很容易导致对网络的损害,通常出现在你的网络中有着大量的独立的MAC地址的情况下
- 你的网络设备需要可以处理混杂模式,这种情况下一个物理接口可以分配多个MAC地址
- 应用程序依然可以使用桥接(单一的Docker主机)或者overlay模式(跨多个docker主机间通信)
4.1. 创建一个macvlan网络
当你创建一个macvlan网络的时候,可以有两种模式
- 桥接模式,这种模式下Macvlan的数据通过主机上的物理设备传递;
- 802.1q分支桥接模式,数据通过一个802.1q 子接口来传输,这种模式允许用户控制路由和过滤规则
4.1.1 桥接模式
创建一个Macvlan网络
$ docker network create -d macvlan \
--subnet=172.16.86.0/24 \
--gateway=172.16.86.1 \
-o parent=eth0 pub_net
这里指定了子网和网关,parent这个选项指定所有的数据会通过哪个主机上的物理网卡来传输。
4.1.2 802.1q分支桥接模式
parent选项在指定网口的时候,如果后面加了小数点,就会默认为这是一个eth0的子网口,并且会自动创建这个子网口。
$ docker network create -d macvlan \
--subnet=192.168.50.0/24 \
--gateway=192.168.50.1 \
-o parent=eth0.50 macvlan50
4.1.3 使用ipvlan来替代macvlan
上述的例子里面依旧是个L3桥接(层三交换?),我们可以使用ipvlan来进行替换,并且指定ipvlan的模式是l2
$ docker network create -d ipvlan \
--subnet=192.168.210.0/24 \
--subnet=192.168.212.0/24 \
--gateway=192.168.210.254 \
--gateway=192.168.212.254 \
-o ipvlan_mode=l2 ipvlan210
4.2. 实例
4.2.1. 桥接的例子
这个例子里面,容器所有的流量都经过eth0发出去,Docker利用容器的MAC地址来路由消息到容器;从网络上来看,docker容器的网络接口就像是直接连在网络上的物理网卡
- 这个例子里面有两个虚拟主机,主机用于实验的网卡都必须开启混杂模式,否则一台主机上的容器无法访问另一台主机的容器
$ ip link set enp0s8 promisc on
这里enp0s8是主机上的网卡,IP地址是192.168.56.101,网关是192.168.56.1;
- 创建一个名为my-macvlan-net的网络
$ docker network create -d macvlan \
--subnet=192.168.56.0/24 \
--gateway=192.168.56.1 \
-o parent=enp0s8 \
my-macvlan-net
- 启动一个容器加入到这个网络中
$ docker run --rm -itd \
--network my-macvlan-net \
--ip 192.168.56.220 \
--name c1 \
alpine:latest \
ash
- 这个时候可以查看这个container的MAC地址了
$ docker container inspect my-macvlan-alpine
...
"Networks": {
"my-macvlan-net": {
"IPAMConfig": null,
"Links": null,
"Aliases": [
"6dd64f910242"
],
"NetworkID": "59d92c55e8b4f56535f8097881e9e35d5a2f363f9c53703ab228c64007afa256",
"EndpointID": "5bb8843f2990d7c0b80b068f8413a8f4170c90e86436a82b98eefa62e3ad1eb5",
"Gateway": "192.168.56.1",
"IPAddress": "192.168.56.220",
"IPPrefixLen": 24,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:10:56:02"
}
- 查看一下container中的网络端口配置,
$ docker exec my-macvlan-alpine ip addr show
9: eth0@if3: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:10:56:02 brd ff:ff:ff:ff:ff:ff
inet 192.168.56.220/24 scope global eth0
valid_lft forever preferred_lft forever
可以看到eth0配置了ip为192.168.56.220/24,同时eth0和if3互为veth对,回到host上查看,if3就是host上的物理网卡;
- 重复上述的步骤,在一个虚拟机中创建两个容器c1/c2,另一个虚拟机中创建容器c3/c4
拓扑结构如下
这个时候可以尝试一下ping不同的容器
- c1 ping c2(同一主机内的不同容器间) -> ok;
- c1 ping c3(不同主机的容器间) ->ok;
- master ping c1(主机到本机的容器) ->nok;
- master ping c3(主机到另一主机的容器) -> ok
Note:
这里的交换都是二层基于MAC地址的交换。
每个容器内的eth0都有一个虚拟的mac地址,对于网关来说,都像是一个主机上的实际网卡;这里设置不同容器内的网卡的时候,要相应设置对应的网关和交换机。
4.2.2. 802.1q分支桥接例子
用两张图来描述和普通桥接例子的区别
5. none模式
6. 容器模式
综合各种网络模式的优劣
参考
Docker网络深度解读
最新实践 | 将Docker网络方案进行到底
通过MacVLAN实现Docker跨宿主机互联
Macvlan and IPvlan basics