Docker容器网络模式

之前有另一篇文章介绍过,容器可以通过网络命名空间来和主机之间进行网络的隔离,这篇文章会继续介绍一下容器的几种组网模式。这个对于我们在使用容器进行服务部署的时候来理解不同服务之间的通信尤为重要。
首先对于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. 路由表

  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   
  • 第一条是默认路由,会通过eth0这个网口发往默认的网关,这里的网关是010011AC(172.17.0.1,大端终结)
  • 第二条是所有发往172.17.0.0(掩码是255.255.0.0)网段的数据包都由本机处理(网关是0.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
  • 默认路由是所有找不到路由信息的数据,都通过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地址或者端口,

  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。注意以下几点,

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

  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不同的容器

  • 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分支桥接例子

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


桥接模式
802.1q分支桥接

5. none模式

6. 容器模式

综合各种网络模式的优劣

参考

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容