近日,阿里集团内部已经实现 100% 容器化镜像化;距离 PouchContainer 开源不到一年时间,PouchContainer 开源版 1.0 GA 版本发布,已经完全达到生产级别。另外,作为百万级开源容器技术,PouchContainer 被收录进为高校教材《云计算导论》。
PouchContainer 现在服务于阿里巴巴集团和蚂蚁金服集团的绝大部分 BU, 包括交易&中间件,B2B/CBU/ICBU,搜索广告数据库,还有收购或入股的一些公司,比如优酷高德、UC等。其中体量最大的是交易和电商平台,在 2017 年双 11 的时候我们支撑了破纪录的峰值,背后的应用都是跑在 PouchContainer 里面,整体容器实例已经到了百万级规模。使用了 PouchContainer 的应用涵盖了各种各样的场景。这些场景从运行模式来看,有标准的在线 App,还有像购物车、广告、测试环境等比较特殊的场景。不同的场景对 PouchContainer 有不同的使用方式和需求。从编程语言看,实际运行着 Java、C/C++,Nodejs,GoLang 等语言编写的应用。从技术栈的角度看,包含了电商、DB、流计算、大数据、专有云等场景,每个场景对于容器各方面要求,所用到的特性都不太一样,PouchContainer 针对每个场景的需求都在产品上都做了支持。
PouchContainer 容器技术在阿里的演进过程伴随着阿里技术架构本身的演进。阿里内部技术架构经历了一个从集中式单体应用到分布式微服务化的演进。
淘宝最开始是一个巨石型的应用,一个应用里包含了商品、用户、下单等等所有交易链路的功能。随着功能越来越完善,维护起来也越来越困难。为了提高研发效率,从 2008 年开始我们逐渐把这个应用拆分成了多个分布式应用,商品的,交易的,用户的,前台的,后端的;通过 HSF 远程调用框架,TDDL 分布式数据层和 Notify 分布式消息中间件串联起来。其中每个服务都有多个实例,都可以独立研发演进,并可以进一步继续拆分。于是就逐渐形成了一个庞大的分布式服务集群。从巨石型应用到多个单一功能的轻量级服务型应用,总的应用实例数变多了,每个实例需要的系统资源变少了。于是从最初的每个实例直接使用物理机自然过渡到使用 Xen,KVM 等虚拟化技术。VM 使用了一段时间之后,发现整体物理机的利用率还是很低。当时一个 24 核的物理机只能虚出 4 台 4 核的 VM,除了当时虚拟化本身的开销不小外,每个应用实例在 VM 里仍然用不完分到的资源。于是就想能不能不用虚拟机,用更轻量的基于进程级别的资源切分使用方式。
这个时候阿里内部的运维体系已经比较庞大了,从应用的构建部署到分发,到一些运行期的监控告警等管控系统,都依赖于一个应用实例跑在一个独立机器里的假定。这个假定已经不经意间贯穿到了研发运维的各个环节里面,包括系统的设计,运维习惯等都严重依赖这个假定。我们不可能重新搭建集群,把存量的业务停掉再到新的集群里面用新的运维模式去跑起来,这个业务和运维上都是没法接受的,不可能电商交易的研发停几个月,系统停几天来搞这个事情。所以我们首先要做到兼容,新的资源使用方式必须兼容原先的假定。我们经过仔细分析了这个假定的内涵,发现每个应用实例归纳下来无非有如下 4 点要求:
有独立IP
能够SSH登陆
有独立的,隔离的文件系统
资源隔离,并且使用量和可见性隔离
首先是有独立 IP,能够 SSH 登录。其次有独立的文件系统,应用程序跑起来,希望程序看到的整个文件系统都是给他专用的,因为现有的代码和配置中必然有很多路径的硬编码,需要满足这个潜在要求。还有不管通过工具还是代码,他只能看到分配给他自己的资源。比如 4 个 CPU,8G 的内存,他能够根据这些资源的用量做一些监控,做一些对自己资源使用量的采集和告警。这四个特点总结下来就是新的资源使用方式要做到和物理机或者 VM 的使用体验一致。能够做到这样的话原先跑在 VM 里的应用就可以很平滑的迁移过来,现有的应用系统和运维系统不需要做很大的改动。
我们为了能达到这四点,最开始是多隆大神手工 Hack 系统调用,glibc 基础库等,实现了一些资源上的隔离。像有独立的 IP 可登录 ,就用虚拟网卡,在每个容器里面起一个 sshd 进程;资源的隔离和可见性上,就用 Cgroup 和 Namespace 等内核特性;后来发现开源的 LXC 项目也在做同样的事情,并且比手工 Hack 更通用化,更优雅一些。于是我们集成 LXC,并且在内核上加了定制的资源可见性隔离的 patch,让用户的实例只能看到分配给他的 CPU和内存,另外还增加了基于目录的磁盘空间隔离的 patch,这样就形成了我们第一代的容器产品。这个产品当时代号是 T4,寓意是第四代淘宝技术,淘宝 4.0;在 2011 年的时候 T4 容器技术灰度上线。T4 相比 VM,完全没有虚拟化 Hypervisor 层的开销,资源切分和分配上更加灵活,可以支持不同程度的资源超卖。这样就很好的支持了业务爆发增长的需求,控制了物理机按业务增长比例膨胀的势头。另外因为 T4 完全兼容了之前研发和运维对物理机和 VM 的使用习惯,绝大多数应用都能够做到透明的切换,应用无感知。因为有这些特性,在接下来的短短几年时间里,T4 逐步接管了交易和电商主体的在线应用。
到 2015 年的时候 Docker 技术火起来了。我们写程序的都知道有个著名的公式,程序=数据结构+算法。从程序交付使用变成一个软件产品的角度来看,我们可以套用这个公式:
软件= 文件(集)+ 进程(组);
从静态来看,软件从构建分发到部署,最终形式是一个有依赖层次的文件集。从动态来看,这些文件集,包括二进制和配置,由操作系统加载到内存后执行,就是一个有交互关系的进程组。我们之前的 T4 容器在进程(组),或者说运行时上做的事情和 Docker 基本类似,比如说都使用了 Cgroup、Namespace、linux bridge 等技术。还有些是 T4 特有的,比如基于目录的磁盘空间的隔离,资源可见性隔离,对老版本内核的兼容等。我们从最早物理机演化到 VM,再到现在的容器,内核的升级周期比较漫长,迭代很慢,15年的时候存量的机器上全部都是 2.6.32 内核,T4是兼容 2.6.32 内核的。 但是另一方面在文件(集)的处理上 Docker 做得更好,更加系统化。 T4 只做了很薄的一层镜像,给相同的业务域做了一个基础的运行和配置环境,这个镜像没有深入到每一个特定的应用。 而 Docker 是将每个应用的整个依赖栈打包到了镜像中。因此在 2015 年我们引入了 Docker 的镜像机制来完善自己的容器。
在将 Docker 镜像整合进来之后,原来基于 T4 的研发运维体系受到了很大的冲击。 首先交付方式变了,之前是 build 一个应用的代码包,把代码包交给我们的部署发布系统,后者创建一个空的容器,根据这个业务所在的很薄的模板把一个空的容器跑起来,再到容器里面安装依赖的一些 IPM 包,设置一些配置,按每个应用定好的一个列表一个一个的安装好,然后把应用包解压启动起来。这个应用依赖的软件和配置列表我们内部叫做应用的基线。引入镜像之后,在将 Docker 镜像整合进来之后,原有的交付方式发生了变化。之前是 build 一个应用的代码包,把代码包交给我们的部署发布系统,后者创建一个空的容器,根据这个业务对应的很薄的一个模板,把一个空的容器跑起来,再到容器里面安装依赖的一些 RPM 包,设置一些配置,按每个应用定好的一个清单一个一个的安装好,然后把应用包解压到主目录启动起来。这个应用依赖的软件和配置清单我们内部叫做应用的基线。引入镜像之后,我们应用的代码包和依赖的所有的这些三方软件、二方软件都会打成一个镜像。之前通过基线维护应用依赖环境,现在都放到每个应用自己的 Dockerfile 中了,整个研发构建和分发运维的过程大大简化了。
做了这个事情之后,研发和运维之间的职责和边界就发生了变化。之前研发只需要关注功能,性能,稳定性,可扩展性,可测试性等等。引入了镜像之后,因为要自己去写 Dockerfile,要了解这个技术依赖和运行的环境倒底是什么,应用才能跑起来,原来这些都是相应运维人员负责的。研发人员自己梳理维护起来后,就会知道这些依赖是否合理,是否可以优化等等。研发还需要额外关注应用的可运维性和运维成本,关注自己的应用是有状态的还是无状态的,有状态的运维成本就比较高。这个职责的转换,可以更好的让研发具备全栈的能力,思考问题涵盖运维领域后,对如何设计更好的系统会带来更深刻的理解。所以引入 Docker 之后对研发也提出了新的要求。我们总结新的时期,新的运维模式下对研发能力要求的几个要素,总结起来就是几个原则:
为了更好的把自己的系统建设好,我们要倡导研发从第一天建立系统的时候,就要考量最终的可运维性,比如参数是否可配置,是否可以随时重启。机器每天都有硬件故障产生,这些硬故障不可能每天都人工处理,必须要尽可能自动化处理,自动化处理时,虽然有些故障只影响了一部分实例,另一部分是好的,但是也可能需要一起处理,比如需要物理机上的业务全部迁移走来维修物理机的时候。所以不管当时容器里的业务是好的还是不好的,都要满足随时可重启,可迁移的要求。原来是部分交付,现在要考虑你到底运行环境是什么样的,什么样的运行环境才能跑起来,尽量做标准化的操作。比如说启动,Dockerfile 里面写好启动的路径,不要再搞一些特殊的处理,如果有任何特殊的处理都没法做统一的调度和运维。统一的业务迁移,机器腾挪也没法做。我们的目标其实就是从一开始的比较粗放的运维,到不断的开发自动化的工具和系统,形成一个体系,通过前期人工运维的过程把一些固定的故障处理的流程模式化,最后提取出来一些可以自动处理故障,自动恢复的机制。我们的最终目标是无人职守。所有这些加起来其实就是我们引入镜像化之后,并且要朝着无人值守的方向演进时,对研发和运维的新的要求。《linux就该这么学》
有疑问加站长微信联系(非本文作者)