docker

Docker容器网络模式

2018-04-21  本文已影响17人  EVANMORE

之前有另一篇文章介绍过,容器可以通过网络命名空间来和主机之间进行网络的隔离,这篇文章会继续介绍一下容器的几种组网模式。这个对于我们在使用容器进行服务部署的时候来理解不同服务之间的通信尤为重要。
首先对于host系统上的物理网络设备,一个物理的网络设备最多存在于一个Network namespace中,所以通常我们不会给容器分配一个物理的网络设备,而是用虚拟网卡来替代,比如说使用veth pair进行不同的Network namespace之间的通信,

当然容器也可以和主机不进行网络命名空间的隔离,使用host模式和主机共享网络设备和配置。

1. 桥接模式(Bridge)


Docker创建新的容器的时候默认的网络模式是桥接,如图所示

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

依据打印出来的信息可以看出来,

# 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. 路由表

  1. 首先查看一下容器中的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   
  1. 然后我们看一下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

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有关的链

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地址或者端口,

  1. 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的场景很有效。

  1. 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
  1. 查看一下当前host上的网络接口
ip addr show

可以看到这个时候没有添加新的veth口,这个和bridge的模式不一样。

  1. 再来看看端口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。注意以下几点,

4.1. 创建一个macvlan网络

当你创建一个macvlan网络的时候,可以有两种模式

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容器的网络接口就像是直接连在网络上的物理网卡

  1. 这个例子里面有两个虚拟主机,主机用于实验的网卡都必须开启混杂模式,否则一台主机上的容器无法访问另一台主机的容器
$ ip link set enp0s8  promisc on

这里enp0s8是主机上的网卡,IP地址是192.168.56.101,网关是192.168.56.1;

  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
  1. 启动一个容器加入到这个网络中
$ docker run --rm -itd \
  --network my-macvlan-net \
  --ip 192.168.56.220 \
  --name c1 \
  alpine:latest \
  ash
  1. 这个时候可以查看这个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"
                }
  1. 查看一下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上的物理网卡;

  1. 重复上述的步骤,在一个虚拟机中创建两个容器c1/c2,另一个虚拟机中创建容器c3/c4
    拓扑结构如下

这个时候可以尝试一下ping不同的容器

Note:
这里的交换都是二层基于MAC地址的交换。
每个容器内的eth0都有一个虚拟的mac地址,对于网关来说,都像是一个主机上的实际网卡;这里设置不同容器内的网卡的时候,要相应设置对应的网关和交换机。

4.2.2. 802.1q分支桥接例子

用两张图来描述和普通桥接例子的区别


桥接模式 802.1q分支桥接

5. none模式

6. 容器模式

综合各种网络模式的优劣

参考

Docker网络深度解读
最新实践 | 将Docker网络方案进行到底
通过MacVLAN实现Docker跨宿主机互联
Macvlan and IPvlan basics

上一篇下一篇

猜你喜欢

热点阅读