当前位置: 首页 > 科技观察

阿里巴巴集团八年容器化演进之路

时间:2023-03-20 10:19:36 科技观察

近日,阿里巴巴集团内部实现100%容器化镜像;PouchContainer开源不到一年,PouchContainerOpenSourceVersion1.0GA就发布了,已经完全达到生产级别。另外,作为最新的开源容器技术,PouchContainer已经被收录到大学教材《云计算导论》中。PouchContainer目前服务于阿里巴巴集团和蚂蚁金服集团的大部分BU,包括交易&中间件、B2B/CBU/ICBU、搜索广告数据库,以及收购或投资的部分公司,如优酷高德、UC等。其中,交易和电商平台规模最大。我们在2017年双十一支撑了一个破记录的峰值,它背后的应用都运行在PouchContainer中,整体容器实例达到了最大规模。使用PouchContainer的应用场景非常广泛。从这些场景的运行方式来看,既有标准的在线APP,也有购物车、广告、测试环境等特殊场景。不同的场景对PouchContainer的使用方法和要求是不同的。从编程语言的角度来看,用Java、C/C++、Nodejs、GoLang等语言编写的应用程序实际上是在运行的。从技术栈来看,包括电商、DB、流计算、大数据、专有云等场景。每个场景对容器的要求不同,使用的特性也不同。每个场景的PouchContainer需求在产品中都得到了支持。PouchContainer容器技术在阿里的演进,伴随着阿里技术架构本身的演进。阿里巴巴内部的技术架构经历了从集中式单体应用到分布式微服务的演进。淘宝最初是一个单体应用,一个包括商品、用户、订单等所有交易环节功能的应用。随着功能越来越完善,维护难度也越来越大。为了提高研发效率,从2008年开始,我们逐渐将这个应用拆分成多个分布式应用,商品、交易、用户、前端、后端;通过HSF远程调用框架,TDDL分布式数据层与Notify分布式消息中间件串联。其中每一个服务都有多个实例,可以独立开发演进,也可以进一步拆分。于是,一个庞大的分布式服务集群逐渐形成。从单体应用到多个单一功能的轻量级服务应用,应用实例总数增加,每个实例需要的系统资源更少。所以从最初每个实例直接使用物理机,自然过渡到使用Xen、KVM等虚拟化技术。VM使用一段时间后,发现物理机整体的利用率还是很低的。当时一台24核的物理机只能虚拟出4台4核的VM。加上当时虚拟化本身的高开销,每个应用程序实例仍然无法用完虚拟机中分配的资源。于是想能不能不使用虚拟机,使用更轻量级的进程级资源分割方式。这个时候,阿里内部的运维体系已经比较庞大了。从应用的构建、部署到分发,再到运行时的一些监控、告警等管控系统,都依赖于一个应用实例运行在独立机器上的假设。这个假设已经在不经意间渗透到研发、运维的各个环节,包括系统设计、运维习惯等,都严重依赖这个假设。我们不可能重建集群,把现有的业务停掉,用新的运维方式在新的集群上运行。这个业务和运维是不能接受的。电子商务交易的研发不可能停止。几个月来,系统停了几天来做这个。所以首先要兼容,新的资源使用要和原来的假设兼容。仔细分析这个假设的内涵后,我们发现每个应用实例都有以下四个需求:独立IP、SSH登录、独立隔离的文件系统资源隔离、使用和可见性隔离。有独立IP,可以SSH登录。其次,有独立的文件系统。当应用程序运行时,希望程序看到的整个文件系统都是专用的,因为现有的代码和配置中肯定有很多硬编码的路径,需要满足这种潜在的需求。还有,无论是通过工具还是代码,他都只能看到分配给他的资源。比如4个CPU,8G内存,他可以根据这些资源的使用情况做一些监控,对自己的资源使用情况做一些收集和告警。这四个特点总结起来就是新的资源使用方式要和物理机或者VM的使用体验一致。如果能做到这一点,原本运行在VM中的应用就可以顺利迁移,而不需要对现有的应用系统和运维系统做很大改动。为了实现这四点,一开始大神多龙手动hack了系统调用、glibc基础库等,实现了部分资源的隔离。比如有独立IP登录,使用虚拟网卡在每个容器中启动一个sshd进程;对于资源隔离和可见性,使用内核特性,例如Cgroup和命名空间;后来发现开源的LXC项目也在做同样的事情,而且比手动Hack更通用也更优雅。于是我们集成了LXC,在内核上加了一个自定义的资源可见性隔离补丁,让用户的实例只能看到分配给他的CPU和内存,还加了一个基于目录的磁盘空间隔离补丁,这样就形成了我们的第一代容器产品。这款产品当时代号为T4,寓意第四代淘宝技术,淘宝4.0;2011年推出T4容器技术灰度。与VM相比,T4完全没有虚拟化hypervisor层的开销。在资源分割和分配上更加灵活,可以支持不同程度的资源超卖。这样就很好地支撑了业务爆发式增长的需求,控制了物理机与业务增长的比例扩张。另外,由于T4完全兼容以往研发运维使用物理机和VM的习惯,大部分应用都可以在应用不感知的情况下透明切换。因为这些特点,在接下来的几年里,T4逐渐接管了交易和电商实体的在线应用。到2015年,Docker技术火了起来。写程序的都知道有一个著名的公式,程序=数据结构+算法。从程序交付和使用成为软件产品的角度,我们可以套用这个公式:软件=文件(集)+进程(组);从静态的角度来看,软件从构建到部署都是分布式的,最终的形式是一个依赖的层次文件集合。从动态的角度来看,这些文件集,包括二进制文件和配置文件,都是被操作系统载入内存后执行的,是一个交互进程组。我们之前的T4容器在进程(组),或者说运行时,基本上和Docker类似,比如使用了Cgroup,Namespace,linuxbridge等技术。有些是T4独有的,例如基于目录的磁盘空间隔离、资源可见性隔离以及与旧版本内核的兼容性。我们从最早的物理机进化到VM,再到现在的容器。内核升级周期比较长,迭代很慢。15年现有机器都是2.6.32内核,T4兼容2.6.32内核。.但另一方面,Docker在文件(集)的处理上做得更好,更系统。T4只是做了一层很薄的镜像,针对同一个业务域做了一个基本的运行和配置环境。这种镜像并没有深入到每个特定的应用程序中。另一方面,Docker将每个应用程序的整个依赖堆栈打包到一个镜像中。所以在2015年,我们引入了Docker镜像机制来完善我们自己的容器。Docker镜像集成后,原有基于T4的研发运维体系受到很大冲击。首先,交付方式发生了变化。在此之前,我们构建了一个应用的代码包,并将代码包交给我们的部署发布系统,它会创建一个空容器,根据业务所在的瘦模板运行一个空容器,然后在容器中安装一些依赖的IPM包,设置一些配置,按照每个应用的列表一个一个安装,然后解压启动应用包。此应用程序所依赖的软件和配置列表在内部称为应用程序基线。引入镜像并集成Docker镜像后,原有的交付方式发生了变化。之前,我们构建了一个应用的代码包,并将代码包交给我们的部署发布系统。后者创建一个空容器,根据这个业务对应的瘦模板运行一个空容器,然后返回到容器中安装一些依赖的RPM包,设置一些配置,根据各个应用的列表一个一个安装,然后将应用包解压到主目录即可启动。这个应用程序所依赖的软件和配置列表,内部称为应用程序基线。引入镜像后,我们应用的代码包和我们依赖的所有第三方软件、二方软件都会被镜像。以前是通过基线来维护应用依赖环境,现在是放在每个应用自己的Dockerfile中,大大简化了研发、构建、分发、运维的整个流程。这样做之后,研发和运维之间的职责和边界发生了变化。过去,研发只需要关注功能、性能、稳定性、可扩展性、可测试性等。引入镜像后,因为要自己写Dockerfile,所以需要了解这个技术依赖什么,运行环境是什么,应用才能跑起来。原来这些都是相应运维人员的职责。研发人员自己梳理和维护之后,就会知道这些依赖是否合理,是否可以优化等等。研发还需要格外关注应用的可操作性和运行成本,关注你的应用是有状态的还是无状态的。有状态运维的成本比较高。这种职责的转变,可以更好地让研发具备全栈能力。思考完涵盖运维领域的问题,会带来对如何设计更好的系统的更深刻理解。因此,引入Docker后,对研发提出了新的要求。我们总结了新时代和新运维模式下研发能力所需要的要素,可以归纳为几个原则:为了更好的构建自己的系统,我们必须从一开始就提倡研发来构建系统。运行时需要考虑最终的可操作性,比如参数是否可配置,是否可以随时重启。机器每天都有硬件故障。这些硬故障不是每天都可以手动处理的。它们必须尽可能自动处理。在自动处理的过程中,有些故障虽然只影响部分实例,有些是好的,但也可能需要一起处理,比如当需要迁移物理机上的所有服务来修复物理机时。因此,无论当时容器内的业务是好是坏,都必须满足随时重启和迁移的要求。以前是部分交付,现在你要考虑你有什么样的运行环境,你能运行什么样的运行环境,尽量做标准化的操作。比如启动,在Dockerfile中写入启动路径,不做任何特殊处理。如果有特殊处理,就无法统一调度和运维。统一的业务迁移不是靠搬机器来完成的。我们的目标其实是从一开始比较粗放的运维,到不断开发自动化的工具和系统,形成一个系统。通过前期的人工运维流程,对一些固定的故障处理流程进行建模,***提炼出一些能够自动处理故障、自动恢复的机制。我们的最终目标是无人驾驶。这一切的加入,其实就是我们引入镜像,向无人值守方向演进后,对研发和运维的新要求。以上是PouchContainer容器的Roadmap。T4于2011年上线,2015年3月T4覆盖了大部分交易应用。这个时候引入了Docker镜像机制,在里面做了大量的兼容工作。比如将原有的T4轻量级模板转化为对应的基础镜像,兼容以往的很多运维习惯和运维工具,比如账号推送、安全策略、系统检测等。我们在2016年初推出了第一个镜像应用程序,到5月,该团队决定将主站点上的所有应用程序容器化。在做镜像之前,阿里有一个一两百人的团队来做每个应用的部署、运维、稳定控制。后来这个团队没有了,都转向DevOps,转向开发工具和运维平台,通过代码,解决运维问题的工具。对于曾经的全日制运维同学来说,最大的负担就是线上环境的变化。研发向运维同学提交变更申请,运维同学进行线上操作。研发不知道代码运行环境依赖于哪些基础软件。做完镜像后,研发负责编写Dockerfile,运维通过Dockerfile机制将环境变更交给研发。运维和研发的界限非常清晰,这个界限是由Dockerfile定义的。研发负责在Dockerfile中定义他的代码依赖的环境,运维保证构建和分发的时候没有问题。2016年双十一期间,我们完成了交易核心应用的镜像PouchContainer改造。2017年双11期间,所有的交易应用都上了镜像。然后我们在2017年11月19日宣布正式开源PouchContainer,我们内部的PouchContainer已经规模化运营,支持各种业务场景,各种技术栈,不同的运营形式,积累了很多经验。这些经历此前都是与阿里内部环境高度耦合的。比如我们的网络模型其实是嵌入在阿里内部的网络管控平台,包括IP的分配,都是由内部一个独立的系统来完成的。比如什么时候启用IP,什么时候下发路由等等,这些都由统一的SDN网管系统管理。还有类似的内部存储系统,以及一些用于运维的指令推送系统。内部系统耦合比较大,不能直接开源。所以我们最好的策略是外部从头开始孵化一个全新的项目,内部的特性一点点迁移。在这个过程中,我们内部的版本也会进行重构,通过一些插件来解耦内部的依赖,让最新的全新项目能够很好的对外运行;在内部,也可以使用一些耦合内部环境的插件。你可以运行它,最终目标是对内对外使用一套开源版本。那么我们的PouchContainer容器和其他容器有什么区别呢?主要体现在隔离、镜像分发优化、富容器模式、大规模应用和内核兼容性等方面。传统的容器隔离维度是namespace和cgroup;在资源可见性方面,我们在过去几年对内核进行了修补。在查看容器内的内存、CPU利用率等数据时,我们将统计值与当前容器的Cgroup和Namespace进行关联,使得容器可以使用的资源和已经使用的资源都是容器自己的。2018年我们引入了社区的lxcfs,这样就不需要依赖特定的内核补丁了。低版本内核也打了磁盘空间限制,支持基于文件目录的磁盘空间隔离,可以限制每个容器的rootfs。在4.9以上的内核上,我们使用overlay2文件系统来完成同样的功能。我们还在研究基于管理程序的容器解决方案,以提高容器隔离和安全性。对于一些多租户场景,我们在PouchContainer中集成了RunV。阿里内部线下混合部门之所以能够得到提升,是因为线上业务和线下任务都可以在同一台机器上运行,彼此不会有太大的干扰。核心技术是PouchContaienr容器可以根据优先级,隔离不同服务的资源使用,保证在线服务优先使用资源。这个资源包括很多维度,比如CPU、内存、CPU缓存、磁盘、网络等等。这就是PouchContainer的镜像分发设计。我们内部有很多核心应用,体量比较大,实例会分布在几万台物理机上。一个新版本发布时,几万台机器同时拉取镜像,任何一个中央镜像仓库都应付不了。因此,我们设计了一个镜像分发的二级架构,在每个区域建立一个镜像,在同一个区域拉取镜像时使用P2P分发技术---我们内部的产品叫Dragonfly,已经开源了;镜像服务器之间可以相互分散拉取文件碎片,直接解决了中心镜像仓库的服务压力和网络压力。事实上,未来图像分发有更好的想法。我们正在尝试远程图像。通过存储计算分离技术,将镜像挂载到远程磁盘,直接跳过或异步分发镜像。目前,它正在内部环境中灰度运行。这是PouchContainer内部版本的架构。在第一层的宿主机层面,我们会做一些管理和运维,目的是保证容器运行的基础环境是健康的,包括宿主机的一些镜像清理,包括安全控制,权限管理等我们将低版本OS的内核适配到***2.6.32内核,包括容器中的进程管理。前面已经提到了资源隔离。其实网络模型的主体是Bridge,但是也支持其他各种场景。我们开发了许多插件。PouchContainer开源后,我们逐渐将这些插件标准化,以兼容和适配社区的CNI标准。最上层是丰富的容器模式支持,每个容器都会启动一些内部运维工具,一些与运维系统密切相关的组件,包括发布模式的一些优化。可见我们内部架构比较复杂,尤其是依赖其他内部系统。不能直接在外面运行,所以我们不能直接开源。所以我们的开源版本是从头开始构建的,这样会更清爽。我们引入contained来支持不同的runtime实现,包括我们自己封装lxc开发的RunLXCruntime,可以用来支持老版本的2.6.32内核。开源版本的PouchContainer兼容Docker所有接口,同时支持CRI协议,从而同时支持两种主流的集群管理系统。在网络方面,我们在libnetwork的基础上做了内部的增强,包括在不同场景下暴露出的一些问题,一些稳定性,以及在伸缩时对各种细节的一些优化。在存储方面,我们支持多盘、内存盘、远程盘等多种存储形式。PouchContainer可以无缝集成到上层编排工具中,包括Kubelet和Swarm。我们内部的Sigma调度系统使用不同版本的Docker协议和CRI协议。这是PouchContainer的开源地址:https://github.com/alibaba/pouch贡献方式:https://github.com/alibaba/pou...NG.md最近,PouchContainerGA的开源版本PouchContainer已经发布,PouchContainer能够在短时间内被用于这样的GA,离不开容器社区的支持。2300多个commit的背后,有80多个社区开发者的积极贡献,包括来自国内一线互联网公司和容器明星初创公司的贡献者的参与。在PouchContainer开源版本GA发布之前,这一开源容器引擎技术已经在阿里巴巴数据中心进行了大规模验证;GA之后,相信其一系列的突出特性也可以作为开箱即用的系统软件技术服务于行业,帮助行业服务率先推动云原生架构的变革。Q:你怎么让阿里巴巴集团,包括高德、菜鸟把这个技术推过来,因为大公司要跨部门甚至跨子公司推广你的某项技术?一个部门的研究成果,是一件比较难的事情。A:这是一个很好的问题,我们之前也确实遇到过这个问题。我们的方法是先向大家宣传这个理念,让大家在认知上接受镜像运维能够带来的好处,以及长期发展的好处。虽然很难有直接和即时的收益,但从长远来看,肯定会提高运维效率,降低资源使用成本。其实从这两年来看,我们确实降低了很多运维成本。Q:您好,请问容器中的持久化是怎么处理的?A:我们持久化在容器中的数据大致有两类。一是日志,二是应用本身会写入一些数据,比如搜索服务。或者放在本地磁盘上。如果是放在本地的话,迁移的时候就得自己处理数据的迁移了。每个不同的企业处理它的方式不同。另一种方法是使用远程,数据远程。我们有一个分布式存储系统“盘古”。创建容器时,在远程存储集群中构建远程磁盘。我们现在使用块设备并将其挂载到容器中。当容器用完或者迁移时,数据在远端,可以随意迁移到其他地方,然后再挂载数据盘回??来。搜索也可以放在远端。对于阿里的各种搜索场景,我的理解是如果副本多的话,使用远程存储是比较划算的。如果副本数只有1行或2行,远程性能不能满足要求某些场景的要求还不如本地多盘短时间内混合。一般来说,如果没有性能需求,放远端是一种趋势。Q:哪种方式会更多?A:宿主机直连容器。相对来说,最好的场景是在数据库中。大多数数据库位于本地,但有些数据库位于远程。在进化的过程中,还没有***。完成存储和计算的分离。有一天可能根本没有本地数据。问:这是一个云框架。当您听到云架构时,您会感到非常大。是什么样的海量运维、海量数据?对于中小型公司来说,规模可能没有那么大。如果要用这个框架,实现成本是多少,低于用户数,合适不合适?A:很难有一个明确的临界点,说什么时候该用云架构。从中小企业的角度来看,他们可以从第一天开始往这个方向走,或者用这个模式去实现。比如通过弹性混合云的方式在云上搭建一个小型的资源池和扩容也是一个很好的方式。如果第一天完全不考虑这些东西,怎么方便,怎么搭建,不考虑这些单点、容灾等灵活的东西,后面重建起来可能会比较痛苦。Q:对于部署成本,一个两人或者三人的研发团队使用你们的产品需要多长时间?它的难度,因为你需要了解整个框架,你必须部署这个东西才能了解这个东西,我认为学习曲线和部署的难度是什么?A:后一个系统正在做Sigma敏捷版,解决中小企业的问题。两个或三个开发人员不可能开发出当前规模的完整云架构。最好的是用云来支持这些场景的产品。云产品本身已经过很多用户的测试。有这么多云上操作的经验和一些技术积累,比自己开发靠谱多了。Q:请问PouchContainer容器,底层也会封装Docker之类的东西。这是我第一次接触到这个。另外,镜像库兼容Docker吗?A:首先镜像库是完全兼容Docker的。Docker分为很多层。底层的runV和containerd都是社区贡献的,都是开源的。我们在runV和containerd的基础上做了增强。总体来说兼容两个社区的两条主流技术路线,两个集群管理系统,Docker公司的Kubernetes和Swarm,都支持。