在部署大规模Docker集群时,网络成为了最大挑战。

纯粹的Docker原生网络功能无法满足广大云计算厂商的需要,于是一大批第三方的SDN解决方案如雨后春笋般涌现出来,如Pipework, Weave, Flannel, SocketPlane等。

2015年3月,Docker宣布收购SocketPlane,一个新的Docker子项目"Libnetwork"开始酝酿。一个月后,Libnetwork在github上正式与开发者见面,预示着Docker开始在网络方面发力。


Libnetwork

https://github.com/docker/libnetwork


libnetwork提出了新的容器网络模型(CNM, Container Network Model),定义了标准的API用于为容器配置网络,其底层可以适配各种网络驱动。如下图所示:


原生部署和docker部署优缺点 docker的原生网络_原生部署和docker部署优缺点


CNM有三个重要的组件

沙盒:

沙盒是一个隔离的网络运行环境,保存了容器网络栈的配置,包括了对网络接口,路由表和DNS配置的管理。在Linux上,是用Network Namespace实现的,在其他平台上可能有不同的实现,比如FreeBSD Jail。一个沙盒可以包含来自多个网络的多个Endpoint。


Endpoint:

一个Endpoint用于将沙盒加入一个网络。Endpoint的实现可以是veth pair或者OVS内部端口。当前libnetwork的实现是veth pair.一个Endpoint只能属于一个沙盒及一个网络。通过给沙盒增加多个Endpoint可以将一个沙盒加入多个网络。


网络:

网络由一组能够相互通信的Endpoint组成。网络的实现可以是linux bridge,vlan等。

Libnetwork的出现使得Docker具备了跨主机多子网的能力,同一个子网内的不同容器可以运行在不同的主机上。


Libnetwork目前已经实现了5种驱动:

bridge:

Docker默认的容器网络驱动。Container通过一对veth pair连接到docker0网桥上,由Docker为容器动态分配IP及配置路由,防火墙规则等。

host:

容器与主机共享同一Network Namespace,共享同一套网络协议栈,路由表及iptables规则等。容器与主机看到的是相同的网络视图。

null:

容器内网络配置为空,需要用户手动为容器配置网络接口及路由。

remote:

Docker网络插件的实现。Remote driver使得Libnetwork可以通过HTTP RESTFUL API对接第三方的网络方案,类似SocketPlane的SDN方案,只要实现了约定的HTTP URL处理函数及底层的网络接口配置方法,就可以替换Docker原生的网络实现。

overlay:

Docker原生的跨主机多子网网络方案。主要通过使用Linux bridge和vxlan隧道实现,底层通过类似于etcd和consul的KV存储系统实现多机的信息同步。


基本网络配置

Docker网络初探

  • none:不为容器配置任何网络
$ docker run --net=none -it --name d1 debian ip addr show
 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
     inet 127.0.0.1/8 scope host lo
        valid_lft forever preferred_lft forever
     inet6 ::1/128 scope host 
        valid_lft forever preferred_lft forever
 zhangxa@ubuntu:~$



可以看到容器仅有一个lo环回接口,使用--net=none启动容器之后,仍然可以手动为容器配置网络。


  • container:与另一个运行中的容器共享Network namespace,共享相同的网络视图。

首先以默认网络配置(birdge模式)启动一个容器,设置hostname为dockeNet,dns为8.8.4.4

$ docker run -h dockerNet --dns 8.8.4.4 -itd debian bash
 b8e47afe77af504ca8c46d52b1a7e709aa9abae8532df74ace8887d87887dd24
 zhangxa@ubuntu:~$ docker exec -it b8e4 bash
 root@dockerNet:/# ip addr show
 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
     inet 127.0.0.1/8 scope host lo
        valid_lft forever preferred_lft forever
     inet6 ::1/128 scope host 
        valid_lft forever preferred_lft forever
 25: eth0@if26: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
     link/ether 02:42:ac:11:00:0c brd ff:ff:ff:ff:ff:ff
     inet 172.17.0.12/16 scope global eth0
        valid_lft forever preferred_lft forever
     inet6 fe80::42:acff:fe11:c/64 scope link 
        valid_lft forever preferred_lft forever
 root@dockerNet:/#

然后以--net=container:b8e4方式启动另一个容器:

$ docker run --net=container:b8e4 -it debian bash
 root@dockerNet:/# ip addr show
 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
     inet 127.0.0.1/8 scope host lo
        valid_lft forever preferred_lft forever
     inet6 ::1/128 scope host 
        valid_lft forever preferred_lft forever
 25: eth0@if26: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
     link/ether 02:42:ac:11:00:0c brd ff:ff:ff:ff:ff:ff
     inet 172.17.0.12/16 scope global eth0
        valid_lft forever preferred_lft forever
     inet6 fe80::42:acff:fe11:c/64 scope link 
        valid_lft forever preferred_lft forever
 root@dockerNet:/#



可以看到,使用--net=container:b8e4参数启动容器,其IP地址,DNS,hostname都继承了容器b8e4。实质上两个容器是共享同一个Network Namespace的,自然网络配置也是完全相同的。


  • host:与主机共享Root Network Namespace,容器有完整的权限可以操纵主机的协议栈,路由表和防火墙等,所以是不安全的。

相应的,host模式启动时需要指定--net=host参数。

$ docker run -it --net=host debian bash
 root@ubuntu:/# ip addr
 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
     inet 127.0.0.1/8 scope host lo
        valid_lft forever preferred_lft forever
     inet6 ::1/128 scope host 
        valid_lft forever preferred_lft forever
 2: ens34: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 1000
     link/ether 00:0c:29:30:e2:e6 brd ff:ff:ff:ff:ff:ff
     inet 192.168.124.6/24 brd 192.168.124.255 scope global dynamic ens34
        valid_lft 74081sec preferred_lft 74081sec
     inet6 fe80::3b06:154f:8b60:76e5/64 scope link 
        valid_lft forever preferred_lft forever
 3: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
     link/ether 00:0c:29:30:e2:dc brd ff:ff:ff:ff:ff:ff
     inet 192.168.17.140/24 brd 192.168.17.255 scope global dynamic ens33
        valid_lft 1339sec preferred_lft 1339sec
     inet6 fe80::d1e2:6e4d:a046:a2ed/64 scope link 
        valid_lft forever preferred_lft forever
 4: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
     link/ether 02:42:4d:eb:09:d0 brd ff:ff:ff:ff:ff:ff
     inet 172.17.0.1/16 scope global docker0
        valid_lft forever preferred_lft forever
     inet6 fe80::42:4dff:feeb:9d0/64 scope link 
        valid_lft forever preferred_lft forever
 root@ubuntu:/#

host模式下,容器可以操纵主机的网络配置,这是很危险的,除非万不得已,应该尽可能避免使用host模式。


  • bridge:Docker设计的NAT网络模型。

Docker daemon启动时会在主机创建一个Linux网桥(默认为docker0,可通过-b参数手动指定)。容器启动时,Docker会创建一对veth pair设备,veth设备的特点是成对存在,从一端进入的数据会同时出现在另一端。Docker会将一端挂载到docker0网桥上,另一端放入容器的Network Namespace内,从而实现容器与主机通信的目的。

bridge模式下的风格拓扑如下图所示:


原生部署和docker部署优缺点 docker的原生网络_Network_02

在桥接模式下,Docker容器与Internet的通信,以及不同容器之间的通信,都是通过iptables控制的。

Docker网络的初始化动作包括:创建docker0网桥,为docker0网桥新建子网及路由,创建相应的iptables规则等。


我们可以查看主机的路由表:

$ route -n
 Kernel IP routing table
 Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
 0.0.0.0         192.168.17.2    0.0.0.0         UG    100    0        0 ens33
 0.0.0.0         192.168.124.1   0.0.0.0         UG    101    0        0 ens34
 169.254.0.0     0.0.0.0         255.255.0.0     U     1000   0        0 ens33
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0

查看iptables:

# iptables -vnL 
 Chain INPUT (policy ACCEPT 93127 packets, 32M bytes)
  pkts bytes target     prot opt in     out     source               destination         

 Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
  pkts bytes target     prot opt in     out     source               destination         
   678  109K DOCKER-ISOLATION  all  --  *      *       0.0.0.0/0            0.0.0.0/0           
   326 76320 DOCKER     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0           
   326 76320 ACCEPT     all  --  *      docker0  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
   352 32742 ACCEPT     all  --  docker0 !docker0  0.0.0.0/0            0.0.0.0/0           
     0     0 ACCEPT     all  --  docker0 docker0  0.0.0.0/0            0.0.0.0/0           

 Chain OUTPUT (policy ACCEPT 58576 packets, 27M bytes)
  pkts bytes target     prot opt in     out     source               destination         

 Chain DOCKER (1 references)
  pkts bytes target     prot opt in     out     source               destination         
     0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.2           tcp dpt:12384
     0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.2           tcp dpt:12383
     0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.4           tcp dpt:12382
     0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.9           tcp dpt:12380
     0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.9           tcp dpt:2379
     0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.11          tcp dpt:8080
     0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.5           tcp dpt:12381
     0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.6           tcp dpt:4443
     0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.7           tcp dpt:2375
     0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.3           tcp dpt:2376
     0     0 ACCEPT     tcp  --  !docker0 docker0  0.0.0.0/0            172.17.0.8           tcp dpt:4443

 Chain DOCKER-ISOLATION (1 references)
  pkts bytes target     prot opt in     out     source               destination         
   678  109K RETURN     all  --  *      *       0.0.0.0/0            0.0.0.0/0           
 root@ubuntu:~#




查看docker0网桥:

# brctl show
 bridge name    bridge id        STP enabled    interfaces
 docker0        8000.02424deb09d0    no        veth0f652b1
                             veth11e2546
                             veth3d102a8
                             veth4c5b172
                             veth9b3638d
                             vethab542f0
                             vethae532c8
                             vethcd8f58a
                             vethd23a8f0
                             vethf32305b
                             vethf8df181


每个bridge模式启动的容器都会在docker0上创建一个veth.


  • overlay:Docker原生的跨主机多子网模型

overlay网络模型比较复杂,底层需要类似consul或etcd的KV存储系统进行消息同步,核心是通过linux bridge与vxlan隧道实现跨主机划分子网。

如下图所示:每创建一个网络,Docker会在主机上创建一个单独的沙盒,沙盒的实现实质上是一个Network Namespace。在沙盒中,Docker会创建名为br0的网桥,并在网桥上增加一个vxlan接口,每个网络占用一个vxlan ID,当前Docker创建vlxan隧道的ID范围为256~1000,因而最多可以创建745个网络。当添加一个容器到某一个网络上时,Docker会创建一对veth网卡设备,一端连接到此网络相关沙盒内的br0网桥上,别一端放入容器沙盒内,并设置br0的IP地址作为容器内路由默认的网关地址,从而实现容器加入网络的目的。

原生部署和docker部署优缺点 docker的原生网络_Docker_03


容器1和容器4属于一个网络,容器1需要通过256号vxlan隧道访问另一台主机的容器4.Docker通过vxlan和linux网桥实现了跨主机的虚拟子网功能。