系统和应用的中小型化,可以比较好的解决应用自身的复杂性问题,但这些小应用的部署却成了一个不大不小的问题。说到部署,对于大多数从没做过系统维护的程序猿,可能也不能完全理解,因为这属于编程之外的另一个领域——系统运维。一个系统的运维工作并不比编程复杂,但要用有限的人力去运维数十甚至上百个完全不一样的应用系统,就是一项繁琐的任务了。
虚拟化技术的广泛应用
在普通人看来,要弄个系统,不就是买台服务器,把程序往上一装就完事儿了吗?但事情本身远不是那么简单:首先,你得给服务器找个地方,好在学校有个双路供电恒温恒湿的机房;其次,服务器要接上双路电,即便是机房,也不能说绝对不会掉电,服务器大规模断电宕机,对于运维而言可是个恐怖的事情;再次,要接上网络,交换机的网口可不是随便就能变出来的,如果位置不好,还要飞一根长长的网线。所有这些事情听着都不复杂,但想想这些事情要猫在轰鸣的机房里做几十上百遍,就会头疼。
所幸在 2011 年,网络服务中心就已经完成了基于 VMware 的虚拟化集群搭建,并开始逐步将旧应用向虚拟化集群迁移。虚拟化,简单的说就是利用软件技术,把原来一台实体服务器,切分成十台、二十台甚至更多的虚拟服务器,然后把切分出来的每一部分拿出来去运行一个应用系统。现今网络信息技术中心的机房中,有一组由八台服务器组成的集群,运行着数字校园的大多数系统和学校的各种网站。
在虚拟化平台上开一台虚拟机就简单多了,简单也就意味着虚拟机的数量会快速膨胀甚至泛滥。每部署一个应用就开一台虚拟机对于系统管理而言依然非常麻烦:安装操作系统、规划硬盘分区、配置IP地址、安装应用并配置好权限、开启安全策略,然后还要定期打操作系统补丁、监控系统的各项指标……特别是对于那种需要进行分布式部署,又用到了内存数据库、消息队列等组件的应用来说,就不是部署一台两台虚拟机的问题了。
这些事情每一个都很简单,但应用增加的量变,就会逐渐导致运维工作的质变。总而言之,忙不过来就会忙中出错。
PaaS 化的尝试
在遇到虚拟机泛滥的问题之后,我们开始尝试解决这个问题,简单的说,就是要用一台虚拟机,运行多个应用。
最初的时候,试用了一些商业的中间件如 WebLogic 来解决一台虚拟机部署多个应用的问题。WebLogic 虽然可以运行多个应用,却不能简便彻底地隔离多个应用,如果应用之间互相可以访问文件,那么一个应用出现漏洞就可能导致全盘的信息泄露。再加上 WebLogic 昂贵的价格,和其只能支持 Java 程序,测了没多久,我们就放弃了。
随着云计算的不断流行,我们开始寻找一款能够在私有云环境下部署的 PaaS 平台,IBM 和 VMware 都推出了类似的产品,但在国内并不流行,也很难找到可以试用部署的程序,再加上复杂的结构和连篇累牍的文档,更是让人望而却步。在这个过程中,我们发现了 RedHat 公司推出的 OpenShift 产品,一款可以在私有云环境下部署的 PaaS 平台,并且有社区版可以安装。大概花了两周的业余时间和无数次的反复,我们终于配置好了一套可以运行应用的 OpenShift 环境,并且开始在上面部署一些不太重要的 PHP 或 Java 程序进行尝试。
但作为一个系统管理员,OpenShift 这个环境给人的感觉就是复杂和弱不禁风,我一直很担心,万一这个环境坏掉了,需要多长时间才能把它给恢复回来。同时,OpenShift 2 还有另外一个严重的问题,应用运行时所产生的文件型数据并没有合适的地方可以保存,如果存在本地硬盘就无法进行横向扩展,当时我们也没有一个简单稳定的支持类 Amazon S3 或 Swift API 的本地对象存储环境。再加上购买商业软件所需要的冗长流程,最终我们也并没有将 OpenShift 用于生产,而是仅仅作为测试环境用了一段时间。
从 2011 年到 2015 年的这段漫长的时间里,我们一直用不同的方式部署着不同的应用,有时机器多到要为找连续的 IP 地址发愁,虽然很无奈,但一直没有什么太好的办法。
初识 Docker
第一次听到 Docker,是从离开北京外国语大学不久的范桢嘴里,起初并没有太在意,但一次漫无目的的 Google 之后,我忽然发现这正是我多年来梦寐以求的应用运行环境,为什么这么说呢?
统一的运维界面
每个不同的技术团队,有其自身习惯的开发工具或编程语言,而不同的编程语言,也适合用来解决不同的问题。在我们目前维护的各种系统和应用中,就包含着 PHP、Java、Python、C 等各种语言所编写而成的软件。不同的软件又有不同的版本,即便都是 Linux,不同的发行版对于不同的语言支持也有各种差异,这无疑给系统运维带来了极大的复杂性。而 Docker 将系统自身所需的运行环境,完全打包在容器内,无论是用什么语言开发,运行的是什么版本,这些跟系统管理员都没有什么太大关系。系统管理员只要知道如何管理容器就可以了,其余的问题都是开发者要解决的。
运行环境的安全性
在一个操作系统内运行多个应用,又要保证多个应用的相对隔离,即便是熟练的系统管理员,也还是要花费不少时间的。而容器技术天生就是为了这一目标而发明的。不同的容器虽然都运行在同一个内核上,但它们无论是从进程空间、网络地址还是文件系统,都是完全隔离的。从一个容器内部,根本无法看到其它容器的任何内容。这样就不必担心应用的安全问题会被无限放大。
运行环境的一致性
稍有系统开发经验的人都知道,开发环境和生产环境的差异,经常是造成系统上线后无法正常使用的原因。但由于开发人员、部署实施人员水平的参差不齐,这种运行环境一致性是极难保证的。虽然虚拟机技术的正确运用,可以在一定程度上降低这种差异性,但实际上我还从没见过哪个国内厂商的应用是以自动化打包好的虚拟机方式提供部署的,基本每个应用都要从头安装。
安装部署的便捷性
一个应用系统,很可能会随着应用规模的扩展而改变其部署方式。以我们使用了十年的 Moodle 教学平台为例,最初只有一门课程使用的时候,就是在一台服务器上安装就够了;但使用人数的增加,就需要应用和数据库分别安装了;而当使用人数又增多的时候,就需要对应用服务器进行横向扩展。不仅每一次调整都需要手工完成,当要对应用进行升级时,也需要逐一更新每一台服务器上的软件。而使用 Docker 之后,由于软件自身的各种运行环境都已经通过镜像进行了提前配置,应用的横向扩展,就是分分钟的事情了。
生产环境下的应用部署实践
一种技术很好是一回事儿,把好的技术用好是另一回事儿。为了测试 Docker 环境的稳定性并摸索 Docker 环境在实际工作中的应用方案,我们开始将独立虚拟机部署的应用向 Docker 环境进行迁移。
部署模型设计
在做所有的事情之前,首先要有个大概的方案,而这个方案本身要考虑的最重要的事情,就是当这个环境彻底坏掉的时候,用多长时间能够恢复。我们能想到的一种极端情况,就是数字校园赖以运行的私有云环境彻底没有了,譬如学校中关村主机房电路故障导致存储系统大量硬盘损坏,或是暴雨中机房进水,所有的服务器都洗了个澡。这时,我们可以从学校良乡机房的备份环境中找到数据库的备份和应用系统的文件备份,但要快速恢复整个环境,必须提前做好准备。
在综合考虑了各方面因素之后,我们设计了一个相对简单又能保证系统快速恢复的部署模型:
- 首先,为了充分利用物理服务器的性能,Docker 环境必然是一个虚拟机集群,并且分布在整个 VMware 集群的不同服务器之上;
- 其次,容器运行时,为了保证系统的稳定性和性能,容器内的文件必须存储在 Docker 环境的本地硬盘上;
- 再次,利用 NAS 卷存储所有 Docker 环境配置文件、应用所产生的数据文件和日志文件,通过 NFS 将 NAS 卷挂载到每一个 Docker 虚拟机之中,并由 NAS 存储负责将所有文件备份到异地的存储系统中;
- 最后,主要的关系型数据库依然采用传统方式部署。
操作系统选择
目前主流的 Linux 发行版的最新版本,都已经可以支持 Docker 环境的运行。最初,我们习惯性地使用了 CentOS 7 和 Ubuntu 14.04 进行 Docker 环境部署。但这两个系统最大的问题就是它们并非专为 Docker 设计,需要系统管理员修改各种相关配置文件才能正常工作,从安装系统到可以部署应用,依然需要很多工作,且系统升级后 Docker 环境可能需要重新调整配置。虽然可以利用 Ansible 等工具实现自动化部署,但依然非常麻烦。
在厌烦了手工配置之后,我们开始尝试专为 Docker 定制的 Linux 系统,其中 CoreOS 和 RancherOS 都非常有特色。但 RancherOS 并不支持在容器外挂载 NFS 分区,因此最终我们将所有的 Docker 应用服务器更换为 CoreOS 系统。
CoreOS 可以通过 CloudConfig 方式进行配置,简单的说,就是提前做好配置文件并打包成 ISO 格式的文件,从 OVF 模板导入虚拟机之后,将该文件直接作为光驱挂载到虚拟机上,然后开机,一切搞定。
集群方案选择
Docker 的早期版本,只提供了本地容器间网络通讯的方案,直到最近才正式加入了跨主机之间容器的网络连接。因此在这个领域,不同公司的开源项目提供了几种不同的集群方案。包括 Google 的 Kubernate、Docker 自己的 Swarm 和 Rancher 的 Cattle 等等。
在测试了 Docker Swarm 和 Rancher 的 Cattle 集群之后,我们开始使用 Rancher。一方面,是因为 Swarm 集群需要依赖 ZooKeeper 或 Consul 之类的 集群化键值存储服务才能运行,而这个存储服务就又要平白无故增加几台虚拟机,且服务发现功能需要依赖其它软件,非常麻烦。另一方面,Rancher 提供了非常不错的管理界面,可以让管理员对整个 Docker 集群的运行状况一目了然。
写在最后
从 2015 年 5 月至今,经过了大概一年半的反复折腾,我们自己开发或维护的应用已经彻底转到了容器化环境之中。而这个环境,也大大降低了从系统开发到投入生产的难度,在相当程度上提高了工作的效率。几个集群化部署的应用,譬如基于 Moodle 的教学网站“乐学”、四六级考试报名系统、计算机实验教学中心的考试系统等,在整个环境下的运转已经非常流畅。
采用 Docker 之后,应用部署的难度大大降低,我们也就可以腾出更多的时间用于应用的开发,但系统要想运转好,并非能运行了那么简单。今天任何一个系统都离不开身份认证,可能还要获取数据、发送即时通知、进行支付,那么这些东西如果都由系统自身彻底解决,不仅用户体验差,而且也会带额外的开发和运维工作量。因此,通过建设一组提供基础性服务的应用,组合成“基础服务与 API 平台”,就成了顺理成章的事情。
数字校园实践系列
- 之一 《从构筑大系统,到编写小应用》
- 之二 《从虚拟化向容器化迈进》
- 之三 《看不见的基础服务层》