cover_image

Docker网络模型及相关开源简介

秦春华 云开源 2016年01月20日 08:41

随着容器技术的兴起,网络愈发体现其重要性,由于容器本身的特性,导致尤其需要软件定义网络来解决其问题。

简单来说,容器技术会带来两个重要变化,一是相比于虚拟机技术,其资源密度变得更大,一个主机上会部署上百,甚至成千个逻辑节点,这些节点内部互通,并且也需要和外部互通;另外一个是,容器技术代码服务部署的变化,容器天生是为服务而生的,当某些数据处理类型集中的服务部署在某个物理节点上,其周围部署的服务数据流量就会相应增加,类似于数据引力。这需要可编程,自动化的虚拟化网络来改变网络拓扑和流量导向,适应这种变化。

本文主要介绍Docker的基本网络模型,及相关引出的开源项目的网络发展情况。


Docker基本网络模型


在使用dockerrun创建Docker容器时,可以用--net选项指定容器的网络模式,Docker有以下4种网络模式:

  • host模式,使用--net=host指定。

  • container模式,使用--net=container:NAME_or_ID指定。

  • none模式,使用--net=none指定。

  • bridge模式,使用--net=bridge指定,默认设置。


下面分别介绍一下Docker的各个网络模式。

host模式

一个Docker容器一般会分配一个独立的Network Namespace。但如果启动容器的时候使用host模式,那么这个容器将不会获得一个独立的Network Namespace,而是和宿主机共用一个NetworkNamespace。容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。

container模式

这个模式指定新创建的容器和已经存在的一个容器共享一个Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过lo网卡设备通信。

none模式

这个模式和前两个不同。在这种模式下,Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置。也就是说,这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。

bridge模式

bridge模式是Docker默认的网络设置,此模式会为每一个容器分配Network Namespace、设置IP等,并将一个主机上的Docker容器连接到一个虚拟网桥上。

当Docker server启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。

连接到docker0的容器就从docker0所在子网中选择一个未占用的IP使用。如一般Docker会使用172.17.0.0/16这个网段,并将172.17.42.1/16分配给docker0网桥(在主机上使用ifconfig命令是可以看到docker0的,可以认为它是网桥的管理接口,在宿主机上作为一块虚拟网卡使用)。单机环境下的网络拓扑如下,主机地址为10.10.101.105/24。

图片

在目前Docker默认的网络环境下,单台主机上的Docker容器可以通过docker0网桥直接通信,而不同主机上的Docker容器之间只能通过在主机上做端口映射进行通信。这种端口映射方式对很多集群应用来说极不方便。如果能让Docker容器之间直接使用自己的IP地址进行通信,会解决很多问题。

目前使Docker容器跨主机通信最常见的方法是:overlay网络。隧道被广泛用于连接那些因使用其他网络而被隔离的主机和网络,因为它有效地覆盖在基础网络之上,这个模型就可以很好地解决跨网络使Docker容器实现二层或三层通信的需求。
使用gre隧道连接的多主机容器间通信,如下图所示。(OVS + gre)

图片


Docker最新版本Libnetwork插件技术


Libnetwork是Docker官方近一个月来推出的新项目,旨在将Docker的网络功能从Docker核心代码中分离出去,形成一个单独的库。 Libnetwork通过插件的形式为Docker提供网络功能,使得用户可以根据自己的需求实现自己的Driver来提供不同的网络功能。

Libnetwork所要实现的网络模型基本是这样的:用户可以创建一个或多个网络(一个网络就是一个网桥或者一个VLAN ),一个容器可以加入一个或多个网络。 同一个网络中容器可以通信,不同网络中的容器隔离。

Libnetwork实现了一个叫做Container Network Model (CNM)的东西,也就是说希望成为容器的标准网络模型、框架。其包含了下面几个概念:

图片

  • Sandbox。对于Sandbox可以认为是一个namespace。联系我们前面Kubernetes中说的Pod,Sandbox其实就是传统意义上的虚拟机的意思。

  •  EndpointNeutron中和Endpoint相对的概念应该是VNIC,也就是虚拟机的虚拟网卡(也可以看成是VIF)。当Sandbox要和外界通信的时候就是通过Endpoint连接到外界的,最简单的情况就是连接到一个Bridge上。

  • Network。libnetwork中的Network可以认为是Neutron中的一个拥有一个subnet的network。一个network就是一个唯一的、可识别的endpoint组,组内endpoint可以相互通讯。你可以创建一个『Frontend』和『Backend』network,然后这两个network是完全隔离的。

 目前已经实现了如下Driver

  • Bridge : 这个Driver就是Docker现有网络Bridge模式的实现。

  • Null : Driver的空实现,类似于Docker 容器的None模式。

  • Overlay : 隧道模式(VXLAN)实现多主机通信的方案。

  • remote:提供了融合第三方驱动的接口。

     

Kubernetes网络


Kubernetes采用扁平化的网络模型,每个Pod都有一个全局唯一的IPPod之间可以跨主机通信,相比于Docker原生的NAT方式来说,这样使得容器在网络层面更像虚拟机或者物理机,复杂度整体降低,更加容易实现服务发现,迁移,负载均衡等功能。

POD内容器间通信

KubernetesPod是最基本操作单元,把相关的一个或多个容器构成一个PodPod包含的容器运行在同一个Host上,看作一个统一管理单元。

Pod中指定容器运行时,会默认启动一个容器镜像是gcr.io/google_containers/pause,它是Networkcontainer,它不做任何事情,只是用来接管Pod的网络。

通过docker inspect查看着几个容器的信息,可以看出Pod中定义的容器的网络设置都集中配置在了Netowrk Container上,其它容器网络模型设置为“container”模式,和该Network Container共享网络配置。这样的好处是避免服务容器之间产生依赖,用一个简单的容器来统一管理网络。

 Pod间通信

Pod间通信使用一个内部IP,这个IP即使Netowrk ContainerIP。对应用来说,这个IP是应用能看到,并且是可以对外宣称的(服务注册,服务发现);而NAT方式,应用能看到的IP是不能对外宣称的(必须要使用主机IP+port方式),端口本身就是稀缺资源,并且ip+port的方式,无疑增加了复杂度,这就是IP-per-pod的优势所在。

如何保证PodIP是全局唯一的?因为PodIPdocker bridge分配的,不同Node之间docker bridge配置成不同的网段。

同一个Node上的Pod原生能通信,但是不同Node之间的Pod如何通信的,这就需要对Docker进行增强,现有的方案有Flannel,OpenVSwitch,Weave等。

Kubernetes OpenVSwitch  GRE/VxLAN networking

图片

Flannel

Flannel是CoreOS团队针对Kubernetes设计的一个网络规划服务,简单来说,它的功能是让集群中的不同节点主机创建的Docker容器都具有全集群唯一的虚拟IP地址。 
在Kubernetes的网络模型中,假设了每个物理节点应该具备一段“属于同一个内网IP段内”的“专用的子网IP”。但在默认的Docker配置中,每个节点上的Docker服务会分别负责所在节点容器的IP分配。这样导致的一个问题是,不同节点上容器可能获得相同的内外IP地址。

Flannel的设计目的就是为集群中的所有节点重新规划IP地址的使用规则,从而使得不同节点上的容器能够获得“同属一个内网”且”不重复的”IP地址,并让属于不同节点上的容器能够直接通过内网IP通信。 

工作原理:

它们的控制平面如下图所示:

图片

每个机器上面的Flannel进程会监听ETCD,向ETCD申请每个节点可用的IP地址段,并且从ETCD拿到其他所有宿主机的网段信息,这样它就可以做一些路由。

转发平面:默认的节点间数据通信方式是UDP转发,原理图如下:

图片
数据从源容器中发出后,经由所在主机的docker0虚拟网卡转发到flannel0虚拟网卡,这是个P2P的虚拟网卡,flanneld服务监听在网卡的另外一端。 Flannel通过Etcd服务维护了一张节点间的路由表。 

源主机的flanneld服务将原本的数据内容UDP封装后根据自己的路由表投递给目的节点的flanneld服务,数据到达以后被解包,然后直接进入目的节点的flannel0虚拟网卡,然后被转发到目的主机的docker0虚拟网卡, docker0路由到达目标容器。 

总结一下Flannel方案,可以看出它并没有实现隔离,并且它也是按照网段做IP分配,即一个容器从一台主机迁到另外一台主机的时候,它的地址一定会变化。

 Calico

图片
Calico的方案如上图所示。它把每个操作系统的协议栈认为是一个路由器,然后把所有的容器认为是连在这个路由器上的网络终端,在路由器之间跑标准的路由协议——BGP的协议,然后让它们自己去学习这个网络拓扑该如何转发。所以Calico方案其实是一个纯三层的方案,也就是说让每台机器的协议栈的三层去确保两个容器,跨主机容器之间的三层连通性。

对于控制平面(图6),它每个节点上会运行两个主要的程序,一个是它自己的叫Felix,左边那个,它会监听ECTD中心的存储,从它获取事件,比如说用户在这台机器上加了一个IP,或者是分配了一个容器等。接着会在这台机器上创建出一个容器,并将其网卡、IPMAC都设置好,然后在内核的路由表里面写一条,注明这个IP应该到这张网卡。绿色部分是一个标准的路由程序,它会从内核里面获取哪一些IP的路由发生了变化,然后通过标准BGP的路由协议扩散到整个其他的宿主机上,让外界都知道这个IP在这里,你们路由的时候得到这里来。

图片

Calico由于跑在了一个三层网关的物理网络上时,它需要把所有机器上的路由协议和整个物理网络里面的路由器的三层路全部用BGP打通。这其实会带来一个问题,这里的容器数量可能是成千上万的,然后你让所有物理的路由学习到这些知识,其实会给物理集群里的BGP路由带来一定的压力。

转发平面是Calico的优点。因为它是纯三层的转发,中间没有任何的NAT,没有任何的overlay,所以它的转发效率可能是所有方案中最高的,因为它的包直接走原生TCP/IP的协议栈,它的隔离也因为这个栈而变得好做。因为TCP/IP的协议栈提供了一整套的防火墙的规则,所以它可以通过IPTABLES的规则达到比较复杂的隔离逻辑。

图片

 

继续滑动看下一个
云开源
向上滑动看下一个