本次分享主要介绍乐视云两代PaaS平台的变迁过程,着重介绍第二代PaaS平台LeEngine的架构设计和遇到的问题。
背景2014年乐视云开始尝试Docker的推广和使用,我们的团队开始开发第一代容器云平台Harbor (分享网址:http://dockone.io/article/1091 )。(在这里提醒一下,这与VMware公司中国团队为企业用户设计的Docker Registry erver开源项目Harbor 重名)。第一代容器云平台可以认为是一个开放的托管平台。开发者可以将自己从公司申请的虚拟机或者物理机添加到Harbor系统中进行托管管理,平台基本包含:镜像自动构建(CI),应用快速扩容、缩容、灰度升级,资源权限管理,多集群主机管理等功能。 由于那时容器才刚刚兴起,刚开始在公司内部推动也有一定的阻力,刚开始给业务线推广的时候,需要首先介绍Docker,介绍容器和虚拟机的区别,然后再介绍平台本身和使用方法,推广成本以及业务线学习成本也比较高。接入Harbor上的业务的大多是业务线自己提供虚拟机,而物理机占少数。不过鉴于Harbor的易用程度,吸引了很多新业务接入。到现在为止Harbor已经完全实现开发自助化。业务自己添加主机,自主管理应用和容器,对应用进行升级、回滚,弹性伸缩,镜像构建,现在已经稳定运行2年多。 第一代容器云平台不足之处
随着Kubernetes在越来越多公司开始使用,乐视内部更多的团队和业务线开始接受或者主动了解Docker,同时为了解决第一代平台的存在问题和基于乐视现有服务部署情况,到2015年底,我们团队计划替换掉之前自己写的调度方案,着手尝试使用Kubernetes作为容器的调度引擎,在对比多套网络方案(Calico,Flannel等等)后,结合乐视现有状况,采用Bridge模式,容器大二层网络方案。负载均衡使用Nginx,计算资源全部使用物理机,计算资源完全对业务透明。经过半年多的调研和开发,在2016年10月第二代PaaS平台LeEngine在美国上线,半个月后,北京地区上线。LeEngine现在保持着一月一版本的开发迭代速度,到现在为止已经开发了3个版本。 LeEngine 采用全新的架构,主要面向于无状态或者RPC应用。现在已经承接了乐视云计算,乐视体育章鱼TV,风云直播,乐视网乐看搜索,云相册等近100多个重要业务,使用的客户普遍反映一旦掌握了LeEngine的使用流程,从开发到上线部署,弹性伸缩,升级的效率可成倍增长,极大简化了运维成本。LeEngine拥有极强的用户粘性,吸引了很多业务线主动申请试用LeEngine,现阶段已经不需要再增加额外的精力在公司内部推广。 简介Kubernetes:Google开源的的容器编排工具,在整个2016年,越来越多的公司开始在线上使用Kubernetes,同时Kubernetes具备我们迫切需要的容器自动迁移和高可用功能。关于Kubernetes 的架构在这里我就不多介绍了,虽然架构偏重,但是我们最终决定使用它,并且尽量只使用它的Pod,Replicationtroller和Service功能。这里首先解释一下几个概念: 用户:乐视各个产品线下的产品、开发、测试、运维等人员。 Region:偏向于地理概念,例如北京和洛杉矶是两个Region。 同一个Region内要求内网可达,网络可靠,低延迟。同一个Region共用一套镜像Registry,镜像构建系统,负载均衡系统和监控报警系统,不同Region 共享全局唯一的SDNS和GitLab代码仓库。 Cell:我们现在使用的Kubernetes 1.2.0版本,理论上能控制1000个计算节点,为谨慎使用,规定一个Kubernetes集群最大计算节点会控制在600个左右。 Cell 概念的引入是为了扩充单个Region下计算节点的规模,偏向于机房的概念,一个Cell 即一个Kubernetes集群,每个Region下可以搭建多个Cell。所有Cell共享本Region下的镜像Registry,镜像构建系统,负载均衡系统和监控系统。为同一个Cell下的容器配置一个或者多个网段,每个网段划分单独的VLAN。同一Cell下的计算节点不会跨机房部署。 LeEngine Registry:基于Docker Registry 2.0做的部分修改,后端支持乐视云的Ceph存储。并仿照Docker Hub增加权限和认证机制,只有拥有相应权限的用户才能对特定的镜像进行Push和Pull操作。也可以设置镜像公开,公开的镜像任何用户都可以Pull。 计算节点: 物理机,Kubernetes的Node概念。 应用: 定义提供相同业务逻辑的一组容器为一个应用,可以认为应用是一个微服务。这类应用要求是无状态Web服务或者RPC类的服务。应用可以部署在多个Cell中。上文提到过,一个Cell可以认为是一个机房。LeEngine在一个Region下会至少部署2个Cell,部署应用时候,我们要求应用至少部署在2个Cell中,这样即使一个机房出现网络故障时,另一个机房的应用容器还能继续对外提供服务。一个应用下可以部署多个版本的容器,因此可以支持应用的灰度升级。访问web类应用时候,我们强制要求这类应用(如果是线上应用)前面必须使用负载均衡,由我们的服务发现系统告诉负载均衡当前应用下有哪些容器IP。从Kubernetes层面讲,我们规定一个应用对应Kubernetes下的一个Namespace,因此在应用的数据库表中会存在一个Namespace的字段,并需要全局唯一,而应用的多个版本对应了在这个Namespace下创建的多个Replicationtroller。 Region、Cell 和kubernetes的关系: 架构平台设计容器直接运行在物理机上,计算节点全部由我们提供,业务线不需要关心,LeEngine可以作为一个企业解决方案对外全套输出,平台架构如下:业务层: 乐视使用容器的各个业务线,是LeEngine的最终用户。 PaaS 层: LeEngine提供的各种服务,主要是完成对应用的弹性伸缩,灰度升级,自动接入负载均衡,监控,报警,快速部署,代码构建等服务。 宿主机资源层:主要指Docker 物理机集群,并包含IP池的管理。 用户访问部署在LeEngine上的应用时,首先通过SDNS智能解析到对应的Nginx负载均衡集群,然后由Nginx将请求打到对应的容器中。数据库,缓存等有状态的服务并没有在LeEngine体系之内,因为采用大二层网络,容器可以直接连接公司其他团队提供的数据库或者缓存等服务。 下图是为了更好的说明支持多地域,多kubernetes集群的部署。 单一Region下单Cell部署图: 我们将计算节点的管理网络和容器网络划分开并给容器网络划分单独的VLAN。 成员、权限管理LeEngine下面定义了四大资源,应用、镜像、镜像分组和代码构建。为了团队协同操作,这4大资源都增加了成员和权限管理。成员和权限仿照了GitLab进行了设计,成员角色分为:Owner、Master、Developer、Reporter、Guest。 不同的角色针对不同的资源系统都定义了不同的权限。比如应用,只有Owner和Master有权限对应用部署新版本,弹性伸缩等等。 假如一个用户A创建了一个应用A1,那么A就默认是应用A1的Owner,拥有对应用A1所有操作权限,包括部署新版本,弹性伸缩,修改应用,删除应用等所有操作。而用户B此时对应用A1不可见,若想可见,必须由A对这个应用A1执行添加成员的操作,把B添加到A1中,并赋为除Owner以外的任何一个角色,若此时B被赋为Master角色,那B拥有了对应用A1部署新版本,弹性伸缩等权限,反之则没有。根据上面的权限设计,通过LeEngine的Console界面,不同的用户登录后看到的仅仅是跟自己相关的资源,如下图,在应用中,能看到我创建的以及我参与的应用: 在镜像页面,能够看到我创建的以及我参与的镜像,如下图: 帮助文档会提供给用户不同资源的权限说明: 用户端和管理端LeEngine具有面向用户端的Console界面和面向运维管理员的boss界面,在用户端用户可以看到自己创建和参与的4种不同的资源。管理端则主要是对整个LeEngine平台资源进行管理,包括用户可使用最大资源的限制,负载均衡特殊配置,Cell集群下的资源使用情况,操作频率统计等等。下图是LeEngine测试环境boss系统关于操作频率统计: 操作频率包括每天所用应用的部署次数,代码的构建次数,镜像的Push次数,弹性伸缩次数,在一定程度上能展示出业务线对LeEngine平台本身的使用频率。 LeEngine-coreLeEngine-core是LeEngine最终对外提供服务的API接口层(beego实现),所有4大资源的操作,包括权限控制,都是通过这一层控制的。LeEngine只提供最原子的API接口,特殊业务线要想有特殊的需求,完全可以在现有API基础上进行二次开发。容器网络容器采用大二层网络,因此可以保证外部服务可以直接连通容器,容器也可以直接连通外部服务,比如数据库,缓存等等。采用此种方案可以保证容器横向可连接,纵向可访问。外部想连接容器可以通过容器IP地址直接连接,也可以通过负载均衡方式进行访问。而容器也可以直接访问LeEngine体系外的虚拟,物理机资源,以及MySQL等组件服务。我们自己写了CNI插件和CNICTL管理工具,支持添加多个IP段,用来防止IP资源不够的情况。IP段的信息存在了当前Kubernetes集群里的etcd中。我们会为每个Cell即每个Kubernetes集群下都添加至少一个IP段,一般1024个IP地址22子网掩码,单独vlan防止广播风暴,这需要提前跟网络部门规划好IP段。如果这个IP段已经使用完,我们会使用CNICTL工具,重新增加一个新的IP段。 为了进一步保证业务容器在网络方面的稳定性,我们所有的计算节点都是4个网卡,2千兆,2万兆,双双做bond,千兆bond1用来做管理网卡,万兆bond1用来跑业务容器,每个计算节点在交付时候会创建一个OVS网桥,并将bond1挂载上去,上联交换机做堆叠,计算节点尽量打散在多个不同机柜中。 计算节点物理机上的Kubulet在创建Pod的PAUSE容器后,会调用我们自己CNI插件,CNI会创建一个veth pair, 一端扔到这个容器的Namespace中,并命名eth0,一端挂载到OVS网桥上,之后从etcd中大的IP段中找出一个连续16个IP地址的小段给这个计算节点,然后再从这个子段中找一个空闲的IP给这个容器,配置好容器IP,以及路由信息,同时会根据配置来确定是否发送免费ARP信息,之后遵守CNI规范把相应的信息返回给kubelet。当这个计算节点再次创建新的Pod时,会优先从这个子段中选择空间的IP,若没有空闲的IP地址,会重新计算一个子段给这个计算节点用。 现在CNI不能保证Pod删掉重新创建时候IP保持不变,因此应用在每次升级操作后,容器IP地址会变,这就需要我们的服务发现与负载均衡做对接。 不过现在的这套方案也会存在一些问题:比如物理主机突然down掉,或者Docker进程死掉,导致本主机上所有容器挂掉,等kubelet重新启动后,原来死掉的容器所占用的IP并不会释放。我们现在的解决方案是通过我们开发CNICTL命令来进行定期检测。CNICTL提供一个check命令,会检索etcd中所有分配的IP和对应的POD信息,之后调用apiserver获得所有Pod信息,取差值则为没释放的IP地址。收到报警后,人工调用CNICTL的释放IP功能,手动释放IP。 服务发现我们充分利用了Kubernetes的Service概念,前面已经提过,一个应用对应一个Namespace,一个版本对应一个RC,在用户通过API请求创建应用时候,LeEngine核心API层:LeEngin-core会默认在对应的Kubernetes集群中创建相关联的Namespace,同时默认在这个Namespace下创建一个Service,并创建一个唯一的标签属性,用户在部署新版本(RC)时候,LeEngine会给这个RC添加这个Service的唯一标签。这样就可以通过Service来发现后端的Endpoint。我们用Go写了一个服务发现服务,通过watch api-server的API接口,自动归类发现哪个应用下有IP变动,之后调用我们负载均衡的API接口,动态更改Nginx的后端upstream serverip。在我们没使用Kubernetes的健康探测功能之前,会有一定的几率出现容器创建完成,服务没有完全启动,这时候容器IP已经加载到负载均衡的情况,如果这时候如果刚好有请求过来,会出现请求失败的现象。之后我们在最新一版中,加入了健康探测功能,用户在给应用部署新版本时,允许用户指定自己服务的监控探测HTTP接口,当容器内服务探测成功后,才会加入到负载均衡中去。而删除容器则不会出现这种情况,执行RC缩容命令后,需要删除的容器首先会立马从负载均衡中删除,之后才会执行容器的删除操作。 负载均衡我们并没有使用Kubernetes的Proxy作为负载均衡,而是使用Nginx集群作为负载均衡。Nginx我们原则上会部署在同一个Region下的多个机房中,防止因为机房网络故障导致全部的Nginx不可用,支持Nginx横向可扩展,当负载均衡压力过大时候,可以快速横向增加Nginx物理机。为防止单一Nginx集群下代理的Domain数目过多,以及区分不同的业务逻辑,比如公网和内网负载均衡,我们支持创建多个Nginx负载集群。下图为用户浏览请求路径。 关于如何能够通知Nginx集群自动更新Upstream下的Server IP问题, 我们在Nginx集群外面用beego框架设计了一层API层:slb-core, 专门对外提供API接口,具体结构如下: etcd里面存放每个domain的配置信息。具体key结构如下: |
2016-12-30
2019-09-16
2016-12-23
2019-09-20
2019-12-26
回答
回答
回答
回答
回答
评论