zoukankan      html  css  js  c++  java
  • 七牛容器SDN技术与微服务架构实践

     

    Docker的横空出世很大程度上推动了容器技术的热度和发展。容器技术和传统的虚拟化技术有很大的不同,具体包括:首先是相对于传统的虚拟机,以前一个虚拟机里做的事情,要打散成很多个容器去做,它们各自的职能会更少;第二点是会造成以前一个虚机的IP会变成很多个容器的多个IP,容器之间的关系会变得更加复杂;第三点是整个网络中的网络端点数量呈现一个上升的趋势;第四点是容器的生命周期其实会更短。此外,容器由于其轻量级的优势,可能会被不停地调度,从一台机器调度到另外一台机器,根据资源的负载均衡,容器的生命周期其实是比虚机要短的。以上几点其实都是给传统的虚拟化网络提出的一些新的挑战。

    本文是对七牛资深架构师徐兆魁在2015全球架构师峰会上所做的同题目演讲的整理,主要分为三个部分。第一部分简单介绍SDN的内涵以及它和容器之间的关系。第二部分介绍一些现有围绕容器的一些开源的SDN的解决方案,包括Flannel、Calico和Weave。第三部分是七牛在这部分的一些实践和案例的分享。

    关于SDN和容器

    作为近年来比较热的一个概念,众所周知SDN是Software Defined Network的缩写,即软件定义网络。但不同的人对SDN有不同的理解。在广义上,只要是你通过软件实现了一个东西,然后那个东西能够灵活地去达到网络上面的部署和伸缩,这就可以被认为是SDN。在后文中会对Flannel、Calico、Weave这三个解决方案进行分析,并从控制层和转发层来着重探讨它们的技术实现,虽然它们并没有宣称自己是SDN的解决方案。

    由于容器技术给传统的虚拟化网络提出了一些新的挑战,所以围绕Docker产生了很多不同的网络解决方案,以弥补Docker在这些方面的不足。

    围绕容器的开源的SDN解决方案

    Docker自己的网络方案比较简单,就是每个宿主机上会跑一个非常纯粹的Linux Bridge,这个Bridge可以认为是一个二层的交换机,但它的能力有限,只能做一些简单的学习和转发。然后出来的流量,出网桥的流量会走IPTABLES,做NAT的地址转换,然后靠路由转发去做一个宿主之间的通信。但是当真正用它的网络模型部署一个比较复杂的业务时,会存在很多问题,比如容器重启之后IP就变了;或者是由于每台宿主机会分配固定的网段,因此同一个容器迁到不同宿主机时,它的IP可能会发生变化,因为它是在不同的网段;同时,NAT的存在会造成两端在通讯时看到对方的地址是不真实的,因为它被NAT过;并且NAT本身也是有性能损耗等。这些问题都对使用Docker自己的网络方案造成了障碍。

    Flannel

    图1

    Flannel是CoreOS团队针对Kubernetes设计的一个覆盖网络(Overlay Network)工具,如图1所示。它们的控制平面其实很简单,如图2所示。

    图2

    每个机器上面的Flannel进程会监听ETCD,向ETCD申请每个节点可用的IP地址段,并且从ETCD拿到其他所有宿主机的网段信息,这样它就可以做一些路由。对于它的转发平面(图3)——转发平面主要表现数据流的流向——它在Docker进来的网桥基础之上,又创建了一个新的叫VXLAN的设备(图4),VXLAN是一个隧道的方案,它可以把一个二层的包,前面加一个包头,然后再把整个包作为物理网络的一个包,去物理网络里面去路由,流转。

    图3

    图4

    为什么会要有这个东西呢?因为通常虚拟网络的IP和MAC在物理的网络其实是不认识的。因为识别IP需要物理网络支持,这个其实是个隧道的方案。

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

    Calico

    图5

    Calico的思路比较新,如图5所示。它把每个操作系统的协议栈认为是一个路由器,然后把所有的容器认为是连在这个路由器上的网络终端,在路由器之间跑标准的路由协议——BGP的协议,然后让它们自己去学习这个网络拓扑该如何转发。所以Calico方案其实是一个纯三层的方案,也就是说让每台机器的协议栈的三层去确保两个容器,跨主机容器之间的三层连通性。对于控制平面(图6),它每个节点上会运行两个主要的程序,一个是它自己的叫Felix,左边那个,它会监听ECTD中心的存储,从它获取事件,比如说用户在这台机器上加了一个IP,或者是分配了一个容器等。接着会在这台机器上创建出一个容器,并将其网卡、IP、MAC都设置好,然后在内核的路由表里面写一条,注明这个IP应该到这张网卡。绿色部分是一个标准的路由程序,它会从内核里面获取哪一些IP的路由发生了变化,然后通过标准BGP的路由协议扩散到整个其他的宿主机上,让外界都知道这个IP在这里,你们路由的时候得到这里来。

    图6

    关于Calico这里讨论一个问题,因为它跑的是纯三层的协议,所以其实它对物理架构有一定的侵入性。Calico官方称,你可以跑在一个大二层的网络里面。所谓大二层就是没有任何三层的网关,所有的机器、宿主机、物理机在二层是可达的。这个方案其实有一定的弊端,事实上在很多有经验的网络工程师眼里,一个大二层其实是一个单一的故障率,也就是说任何一个都会有一定的硬件风险会让整个大二层瘫痪。

    另外,Calico跑在了一个三层网关的物理网络上时,它需要把所有机器上的路由协议和整个物理网络里面的路由器的三层路全部用BGP打通。这其实会带来一个问题,这里的容器数量可能是成千上万的,然后你让所有物理的路由学习到这些知识,其实会给物理集群里的BGP路由带来一定的压力,这个压力我虽然没有测过,但据专业的网络工程师告知,当网络端点数达到足够大的时候,它自我学习和发现拓扑以及收敛的过程是需要很多的资源和时间的。

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

    图7

    Weave

    图8

    Weave方案比较有趣,如图8所示。首先它会在每台机器上跑一个自己写的Router程序起到路由器的作用,然后在路由器之间建立一个全打通的PC连接,接着在这张TCP的连接网里面互相跑路由协议,形成一个控制平面(图9)。可以看出,它的控制平面和Calico一致,而转发平面(图10)则是走隧道的,这一点和Flannel一致,所以Weave被认为是结合了Flannel和Calico这两个方案的特点。

    图9

    图10

    图11所示是它的服务发现与负载均衡的一个简单的方案,它在每个容器会起两个网卡,一个网卡连着自己起的可以跟其他宿主机联通的网桥;另一个网卡绑在原生Docker的一个网桥上,并在这个网桥上监听一个DNS的服务,这个DNS实际上嵌在Router里面,即它可以从Router里学习到一些服务的后端的一些配置。所以这时容器如果发起DNS查询,实际上会被路由导到宿主机上,DNS Server上,然后DNS server做一些响应。它们官方负载均衡也是靠这个,但是这其实是一个短板,因为我们更偏向于四层或者是七层更精细的负载均衡。

    在隔离方面,Weave的方案比较粗糙,只是子网级的隔离(图12)。比如说有两个容器都处在10.0.1-24网段,那么它会在所有的容器里面加一条路由说该网段会走左边的网桥出去,但是所有非此网段的流量会走Docker0,这个时候Docker0和其他是不联通的,所以它就达到一个隔离的效果。

    图11

    图12

    三个方案总结

    总结一下:

    1. Flannel仅仅作为单租户的容器互联方案还是很不错的,但需要额外的组件去实现更高级的功能,例如服务发现与负载均衡。

    2. Calico有着良好的性能和隔离策略,但其基于三层转发的原理对物理架构可能会有一定的要求和侵入性。

    3. Weave自带DNS,一定程度上能解决服务发现,但因隔离功能有限,若作为多租户的联通方案还稍加欠缺。

    4. 另外,Calico和Weave都使用了路由协议作为控制面,而自主路由学习在大规模网络端点下的表现其实是未经验证的,曾咨询过相关的网络工程师,大规模端点的拓扑计算和收敛往往需要一定的时间和计算资源。

    七牛的具体实践

    业务需求

    七牛实际上一直在拥抱容器带来的变革,拥抱新型的微服务架构理念。所以构建了一套容器平台,这么做的目的,一方面想推进通过将已有业务容器化简化研发和上线流程,另一方面也想通过这个方式去满足用户的一些计算需求,毕竟计算和数据离得越近越好。

    所以我们业务上对网络的需求是:

    1. 首先一点,是能够运行在底层异构的基础网络上,这一点对于推进已有业务的容器化来说是很重要的,否则会涉及到基础网络的大规模变更,这是无法接受的。

    2. 我们试图构造一个对容器迁移友好的网络结构,允许容器在必要情况下发生调度。

    3. 我们认为服务发现和负载均衡对业务来说是个基础而普适的需求,尤其是在倡导微服务架构的今天,一个设计良好的组件应该是可水平伸缩的,因此对于组件的调用方,服务发现和负载均衡是非常必要的功能。当然有人会说这个功能和网络层无关,而应由应用层去实现,这个说法挺有道理,但后面我会讲到由网络层直接支持这两个功能的好处。

    4. 为了满足七牛本身已有的一些对隔离有要求的服务,并满足上层更丰富的权限模型和业务逻辑,我们试图将隔离性做的更加灵活。

    在这几个需求的驱动下,我们最终尝试跳出传统网络模型的束缚,尝试去构造一个更加扁平而受控的网络结构。

    转发平面

    首先,在转发层面,为了包容异构的基础网络,我们选择了使用Open vSwitch构造L2 overlay模型,通过在OVS之间联通vxlan隧道来实现虚拟网络的二层互通。如图13所示。但隧道通常是有计算成本的,隧道需要对虚拟二层帧进行频繁解封包动作,而通用的cpu其实并不擅长这些。我们通过将vxlan的计算量offload到硬件网卡上,从而将一张万兆网卡的带宽利用率从40%提升到95%左右。

    选择overlay的另一个理由是,据我们目前所了解到,当下硬件的设备厂商在对SDN的支持上通常更偏向于overlay模型。

    图13

    控制平面

    而在控制层面,我们思考了容器和传统虚机的一些不同:

    前面提到,微服务架构下,每个容器的职责相对虚机来说更加细化和固定,而这会造成容器与容器间的依赖关系也相对固定。那么每台宿主机上的容器可能产生的outbound其实也是可推演的。如果进一步想的话,其实推演出来的理论范围通常会远大于容器实际产生的 outbound。所以我们尝试使用被动的方式实现控制指令的注入。因此我们引入了OpenFlow作为控制面的协议。OpenFlow作为目前SDN 控制平面的协议标准,它有着很强的表达能力。从包匹配的角度看,它几乎可匹配包头中的任意字段,并支持多种流老化策略。此外,扩展性也很好,支持第三方的 Vendor 协议,可以实现标准协议中无法提供的功能OpenFlow可以按Table组织流表,并可在表间跳转(这一点其实和IPTABLES很像,但OpenFlow的语义会更加丰富)。配合OpenFlow的这种Table组织方式,可以实现相对复杂的处理逻辑。如图14所示。

    图14

    选择了OpenFlow,我们的控制平面会显得很中规中矩,也就是逻辑上的集中式控制,没有weave/calico的P2P那么炫酷。在这样的结构下,当ovs遇到未知报文时,会主动提交包信息给Controller,Controller会根据包信息判断后,给ovs下发合适的流表规则。为了实现负载均衡和高可用,我们给每组ovs配置多个Controller。如图15所示。

    例如:

    1. 对于非法流量Controller会让ovs简单丢弃,并在将来一段时间内不要再询问。

    2. 对于合法流量,Controller会告诉ovs如何路由这个包并最终到达正确的目的地。

    图15

    服务发现和负载均衡

    关于服务发现和负载均衡,我们提供了以下几个对象模型:

    1. Container,容器实例,多个Container构成一个Pod(实体)。

    2. Pod,每个Pod共享一个网络栈,IP地址和端口空间(实体)。

    3. Service,多个相同Pod副本构成一个Service,拥有一个Service IP(逻辑)。

    4. 安全组,多个Service构成一个安全组(逻辑)。

    其中,可动态伸缩的关系是一个Service与其后端Pod的映射,这一步是靠平台的自动服务发现来完成。只要发起对Service IP的访问,那么Service本身就会完成服务发现和负载均衡的功能。后端Pod如果发生变动,调用方完全无需感知。

    从实现上来说,我们将这个功能实现到了每个宿主机上,每个宿主机上的这个组件会直接代理本机产生的Service流量,这样可以避免额外的内网流量开销。

    功能上,我们实现了IP级的负载均衡,什么意思,就是每个Service IP的可访问端口与后端Pod实际监听的端口是一致的,比如后端Pod监听了12345,那么直接访问Service IP的12345端口,即可直接访问,而无需额外的端口配置。

    这里对比一下常见的几种负载均衡:

    1. 比DNS均衡更加精细。

    2. 比端口级的负载均衡器更容易使用,对业务入侵更小。

    另外,7层的负载均衡实际上有很大的想象空间,我们实现了大部分Nginx的常用配置,使用者可以灵活配置。业务甚至还可以指定后端进行访问。

    安全组

    在隔离层面,我们在逻辑上划分了安全组,多个service组成一个安全组,安全组之间可以实现灵活的访问控制。相同安全组内的容器可以互相不受限制的访问。其中最常见的一个功能是,将安全组A中的某些特定的Service Export给另一组安全组B。Export后,安全组B内的容器则可以访问这些导出的Service,而不能访问A中的其他Service。如图16所示。

    图16

    介绍完了我们网络的基础功能,这里通过分析两个七牛的实际案例来说明这样的结构是如何推动业务的架构演变的。

    案例分析1——七牛文件处理FOP架构演变

    第一个是七牛的文件处理架构(File OPeration),如图17所示。文件处理功能一直是七牛非常创新、也是很核心的一个功能,用户在上传了一个文件后,通过简单地在资源url中添加一些参数,就能直接下载到按参数处理后的文件,例如你可以在一个视频文件的url中添加一些参数,最终下载到一张在视频某一帧上打了水印并旋转90度并裁剪成40x40大小的图片。

    图17

    而支撑这样一个业务的架构,在早期是非常笨拙的。图17左侧是业务的入口,右侧是实际进行计算的各种worker集群,里面包含了图片处理,视频处理,文档处理等各种处理实例。

    1. 集群信息写死在入口配置中,后端配置变更不够灵活。

    2. 业务入口成为流量穿透的组件(业务的指令流与数据流混杂在一起)。

    3. 突发请求情况下,应对可能不及时。

    后面负责文件处理的同事将架构进化成了这样(如图18)。

    1. 增加Discovery组件,用于集群中worker信息的自动发现,每个worker被添加进集群都会主动注册自己。

    2. 业务入口从Discovery获取集群信息,完成对请求的负载均衡。

    3. 每个计算节点上新增Agent组件,用于向Discovery组件上报心跳和节点信息,并缓存处理后的结果数据(将数据流从入口分离),另外也负责节点内的请求负载均衡(实例可能会有多个)。

    4. 此时业务入口只需负责分发指令流,但仍然需要对请求做节点级别的负载均衡。

    图18

    图19描述的是文件处理架构迁移到容器平台后的早期结构,较迁移之前有如下变更。

    1. 每个Agent对应一个计算worker,并按工种独立成Service,比如Image Service,Video Service。

    2. 取消业务的Discovery服务,转由平台自身的服务发现功能。

    3. 每个Agent的功能退化:

    l 无需和Discovery维护心跳,也不在需要上报节点信息。

    l 由于后端只有一个worker,因此也不需要有节点内的负载均衡逻辑。

    4. 业务入口无需负载均衡,只需无脑地请求容器平台提供的入口地址即可。

    图19

    图20是迁移后发生的另一次演变,实际上上一个阶段中,每个Agent仍然和计算实例绑定在一起,而这么做其实只是为了方便业务的无痛迁移,因为Agent本身的代码会有一些逻辑上的假设。

    这张图中,我们进一步分离了Agent和worker,Agent独立成一个Service,所有的worker 按工种独立成Service,这么分离的目的在于,Agent是可能会有文件内容缓存、属于有状态的服务,而所有的worker是真正干活、无状态的服务。分离之后的好处在于,worker的数量可以随时调整和伸缩,而不影响Agent中携带的状态。

    好处:

    1. 可以看到,相比于最早的架构,业务方只需集中精力开发业务本身,而无需重复造轮子,实现各种复杂的服务发现和各种负载均衡的代码。

    2. 另外,自从部署到容器平台之后,平台的调度器会自动更具节点的资源消耗状况做实例的迁移,这样使得计算集群中每个节点的资源消耗更加均衡。

    案例分析2——用户自定义文件处理UFOP架构演变

    另一个案例是七牛的用户自定义文件处理。

    用户自定义文件处理(User-defined File OPeration,UFOP)是七牛提供的用于运行用户上传的文件处理程序的框架。他的作用实际上和前面介绍的是一致的,只是允许用户自定义他的计算实例。例如七牛现有的鉴黄服务,就是一个第三方的worker,可以用于识别出一个图片是否包含黄色内容。而正是由于引入了用户的程序,所以UFOP在架构上和官方的 FOP的不同在于,UFOP对隔离有要求。

    图20是原本UFOP的架构,事实上,这里已经使用了容器技术进行资源上的隔离,所有的容器通过Docker Expose将端口映射到物理机,然后通过一个集中式的注册服务,将地址和端口信息注册到一个中心服务,然后入口分发服务通过这个中心服务获取集群信息做请求的负载均衡。

    而在网络的隔离上,由于Docker自身的弱隔离性,这个架构中选择了禁止所有的容器间通信,而只允许入口过来的流量。这个隔离尺度一定程度上限制了用户自定义程序的灵活性。

    图20

    而在迁移到容器平台后,由于有灵活的安全组控制,不同用户上传的处理程序天然就是隔离的,而用户可以创建多种职责不同的Service来完成更复杂的处理逻辑。如图21所示。

    另外,迁移后的程序将拥有完整的端口空间,进一步放开了用户自定义处理程序的灵活性。

    图21

    以上内容是七牛首度公开关于多租户虚拟网络方面的探索和实践,并总结了我们对这一领域的观察和思考,还有很多更为细节的点值得探讨,望以后能与大家做更充分的交流。

  • 相关阅读:
    第二章 java内存区域与内存溢出异常
    TCP实现可靠传输
    Tomcat的类加载架构
    浅析Synchronized
    设计数据库
    http和https
    IOC容器的依赖注入
    IOC容器初始化
    深入理解Java内存模型
    单例应该这么写
  • 原文地址:https://www.cnblogs.com/liuhongru/p/11168269.html
Copyright © 2011-2022 走看看