前几天高可用架构翻译了一篇文章《数据库不适合Docker及容器化的7大原因》(后面简称7文),于是在群里争论起来,争论没说清楚,后来我说写文章回应。于是有了这篇文章,这篇文章不仅仅是反驳该文,同时也想说说应用容器化以及云化的价值。阅读本文时建议先阅读一下本人写的《2016年容器技术思考:Docker, Kubernetes, Mesos 将走向何方?》(后面简称容文),本文中会直接引用该文的一些观点。

《7文》虽然列举了7大原因,总结一下其实主要是两点:

  1. 容器化数据库没有带来太多额外价值,数据库不需要经常构建和部署,也不需要经常升级,数据库实例的环境也不需要经常变,用 Ansible 也可以轻松部署和设置。
  2. 引入容器带来的,安全,IO,网络等方面的技术成本和风险,如非必要,勿增实体。

本文主要也从这两点开始,逐个分析。因为该文其实本质上是在分析一项通用技术的价值,所以我们先聊下如何对一项通用技术做价值评估。

如何对一项通用技术做价值评估

任何通用的技术,都承载着一定的技术使命,有其历史背景和最终目标。评价其价值是要思考它的使命和目标,不能单纯的从自己的个例出发。

比如以该文中提到的 Ansible 来说,一次我给一技术负责人推荐 Ansible 的时候,他说 Ansible 的功能我们自己用 shell 写的一套框架已经搞定了,完全没必要引入额外的工具增加复杂度。我说 Ansible 可以屏蔽操作系统的细节,容易些写出跨操作系统的通用配置脚本,他说我们的操作系统都是 ubuntu ,这个功能对我们没价值。我说 Ansible 可以把变量和配置脚本分离,提高脚本的复用程度,他说我们的脚本也一定程度上做了变量分离,我们只分离必要的,满足我们当前的项目了。我说 Ansible 文档详细,学习成本低,他说 shell 脚本看看源码就会了,并且改起来也容易。最后这个争论没有任何结果,谁都没说服谁。

后来我也一直在思考,到底问题在哪儿?其实每种技术的推广时,无论是工具,框架还是语言,都会遇到类似的争论。后来我想明白了,任何通用的技术的最重要的目标其实是增加软件的复用能力,无论是该技术本身还是由该技术衍生出来的产物,但如果不考虑复用,应用到具体的场景时效果都会打折,所以不能只用具体的场景去评估通用技术的价值。

再拿前面的 Ansible 为例,Ansible 本身是一个服务器配置管理工具,它的目标是让服务器的配置变更代码化(Configuration management Infrastructure as Code),然后让应用的安装以及配置的能力组件化,它称之为 Playbook,然后可以共享复用。所以你可以在网上搜索到各种 Ansible 的 Playbook,比如数据库集群的安装配置等。它为了实现这个目标,抽象了一套配置语法,通过声明式的配置来定义服务器上的基础设施状态,也一定程度上屏蔽了操作系统细节(实在屏蔽不了的,也可以通过简单的配置规则来适配),同时做了变量和配置的分离,避免和具体的环境的耦合。也就是说,只有能接受它的核心思想 —- 服务器基础设施变更代码化,然后考虑到复用价值,复用别人的 Playbook 或者将自己的 Playbook 复用到更多的项目或者团队,Ansible 的价值才会体现出来。比如前面争论的那个案例,如果考虑到以后会有更复杂的操作系统环境,可能有更多的,更复杂的项目需要管理,避免运维手动操作引入不预期的变更,导致无法追踪,才值得引入 Ansible。

所以,不要仅仅从自己当前的业务需求来断定一个通用技术的价值,比如该文章的标题如果改成《我们当前的数据库不适合Docker以及容器化的7大原因》,争议就小很多。看一个文章要搞清楚它是在做具体的选型分析还是通用的价值判断。

数据库容器化的目标和价值

我们再从数据库容器化这个场景分析一下。

当前我们开发出的任何软件,到用户部署运行变成一个服务,之间是有巨大的鸿沟的。比如以数据库(MySQL/PostgreSQL)为例,厂商交付给用户的是一个安装包,而用户期望得到的是一个主从的数据库集群,能支持故障主从切换,自动迁移恢复,自动备份,还要能监控报警,当然要是有个 dashboard 来通过界面操作,实现自动升级,就更好了,更复杂的需求可能还需要支持读写分离和自动数据库切分。可以看出,二者之间是有巨大的鸿沟的,而这个鸿沟当前是靠用户的运维人员来填充的。

运维人员怎么填充呢?先从网上找到一些技术资料,怎么做 MySQL 主从复制,怎么做高可用(keepalived,虚IP等),怎么做双主,怎么通过代理做读写分离,然后将这些组件用脚本粘结起来,部署到服务器上。当然这还是运维实例比较强的,如果运维实力不够(大多数创业公司不可能在这种基础设施上投入研发精力的)可能连主从和备份都做不好,即便是自己用脚本写了一些简单的工具,由于测试不够充分,环境异常考虑不周,正式用的时候可能就出错了,比如前一段时间的 gitlab 删库事件。

简单回顾下 gitlab 事件,本来单节点的数据删除是不应该影响整个集群的,但因为从库数据同步不完备(所以可以推断从库应该是没有启用过,没有做读写分离),不能直接升级从库为主库,而其他的多种备份工具都没生效。

这大概是大多数公司的现状,有一些基础设施工具,但基本都是和环境耦合的脚本,也正如《7文》中所说,数据库等基础服务部署后很少需要去做变动,并且随着数据越来越多,越来越不敢动,每次变更,比如升级等,就是一个复杂的工程,差不多要发起一场战役,但一旦出现预期外的故障,就缺少必要的工具和经验去应对。那如何避免这种问题呢?左耳朵耗子的文章《从GITLAB误删除数据库想到的》提出了一个建议:『设计出一个高可用的系统,通过自动化的方式去处理问题』。

但是这个基础设施的自动化高可用系统,有那么容易设计么?一方面大多数公司受限于研发实力,没时间和精力做这种系统,另外一方面即便是有研发实力,这种系统并不能直接产生价值,如何得到高层的支持?能得到多大资源支持?那数据库厂商或者其他商业公司能否提供这样一个数据库服务,再或者能否通过开源项目打造出一个数据库服务,用户可以一键部署呢?这样就能将各公司的运维经验沉淀成具体的工具和组件,而不是像现在,运维经验的沉淀和传播基本都只能通过技术分享或者人员流动,这对业界是一种很大的浪费。

那我们设想一下,如果要做前面描述的这样一个系统,都需要什么条件。首先,得有一种应用的安装包的环境无关的封装,如果要适配不同的操作系统,解决不同的环境异构问题,就很难了。其次,基础环境可编程化,可以在程序中实现网络,存储等环境的动态适配。再次,要有一个调度层,可以做动态迁移。最后,需要一个编排文件来定义各种组件,以及一种打包格式,将多个组件封装到同一个包中做分发。

Ansible/Puppet 等配置管理工具一直想做这个事情,并想封装成可复用的组件,可惜由于基础设施的环境不统一,不可编程化,而配置管理工具只能一定程度解决部署时的复杂性,应对不了动态的故障,基本很难达到这个目标。

IaaS 云实现了基础设施的标准化,可编程化,可动态调度。所以现在 IaaS 云基本都有 RDS(Relational Database Service),功能和前面的描述的用户需求非常类似。但 IaaS 的问题是当前 IaaS 的 API 基本都是指令式的,是面向资源的,不是面向应用的,第三方很难通过这种 API 来调度应用,所以这种服务第三方很难实现,基本都是云厂商自己定制(IaaS 上也有镜像市场,但只能是单个镜像的应用,不能实现复杂的应用),同时 IaaS 的镜像都是 VM,很难实现跨云的分发。

于是,Docker 出现了。Docker 的镜像,几乎完美实现了前面提到的安装包的环境无关的封装,也就是大家说的集装箱能力,又通过镜像仓库提供了分发机制。上面封装一层编排调度系统(Swarm,Kubernetes,Mesos),再加上标准化的网络和存储,于是基本达到了我们上面所描述的条件。

我在《容文》中也论述了

容器平台的最终目标其实是屏蔽分布式系统的资源管理细节,提供分布式应用的标准运行环境,同时定义一种分布式应用的 package,对开发者来说降低分布式系统的开发成本,对用户来说降低分布式应用的维护成本,对厂商来说降低分布式应用的分发成本,也就是 DataCenter OS 或 Distributed OS,可简称 DCOS。

也就是说,仅仅把数据库弄成容器镜像,这仅仅是第一步,是为了后面的目的服务的。有了这一步,才有可能依托容器编排调度系统封装更高级的通用服务。

有了这种能力后,运维的经验就可以沉淀成代码,积累成具体的工具和服务。软件的价值在于复用,可复用的频次越高范围越广,产生的价值越大,越值得投入。比如 RDS 这种服务,研发本身的复杂度本来不高,关键在对各种异常情况的处理方案的经验积累。一个公司遇到的异常状况肯定有限,只有放在社区中逐渐积累改进才会逐渐完备。IaaS 云的 RDS 的优势其实也是这一点,积累了云上的各种用户的各种使用场景和异常处理经验,无论是业务增长还是错误使用带来的异常。前两天 Instapaper 由于MySQL数据文件过大、达到 ext3 的 2TB 文件大小限制,而导致其数据库故障,业务中断31个小时,用的就是 AWS 上的 RDS 。虽然使用 RDS 并不能避免故障,但经过这次故障之后,AWS 肯定会改进 RDS, 将这种故障的应对经验沉淀到产品中去,其他用户就可以避免再次踩坑了。

当然还会有人问,我们当前没有任何精力做更高级的封装,只是把数据库简单的用容器镜像跑起来,还有意义么?也正如我在 《容文》中说的,对容器技术可以做渐进式的接纳,第一步先当做安装包使用,第二步考虑隔离,引入网络解决混合部署带来的网络冲突,第三步再考虑调度编排系统。《7文》中也承认了容器在开发测试环境中的意义,既然开发测试环境中可以接纳容器,保持环境的一致性不更好么?我在文章《基础设施服务的微服务化》中分析了为什么应该将基础服务也作为微服务的组件,一体化管理。只有将数据库等基础设施作为微服务的一个组件,和业务应用的微服务互相可以感知,才能实现最终意义上的全自动化。

当然,只是有了标准化的运行环境,并不一定能带来丰富的应用,还得依赖商业模式。这种基础设施服务原来的商业模式基本只能是 on-premise 私有部署模式,销售和实施成本非常高。企业应用领域是否可能出现一个类似于 Apple 的 AppStore 的市场来降低销售和实施成本? 这方面很多厂商都在尝试,各容器平台都在尝试推出自己的应用规范,IaaS 厂商也在考虑提供声明式的编排 API,让渡出基础设施服务,由第三方实现(比如 QingCloud 即将发布的 AppCenter)。如果这个商业模式成熟,不仅独立的基础设施服务以及中间件公司会涌现出来,同时各公司的基础研发部门可能会从原来的支撑部门,变为 2B 业务的营收部门。

引入容器带来的技术成本和风险

引入任何技术都会带来技术成本和风险,当然容器也不例外。但成本和风险是可以评估的。

  1. 安全 经常有人拿容器和虚拟机的安全做比较,然后说明容器的安全有问题。但实际上,大多数场景下容器并不是用来替代虚拟机的。如果用户只是用容器来封装原来直接运行在宿主机上的服务进程,那安全系数是增加了呢还是降低了?肯定是增加了啊,因为容器多了一层基于 cgroup 和 namespace 的隔离,最差的情况是这层隔离没有生效,那也和进程直接运行在宿主机上的安全效果一样。容器也并没有额外对外暴露端口等增加网络接触面,如果不是将容器直接暴露出去让第三方用户当虚拟机一样部署应用程序,容器并没有额外增加安全性问题。
  2. IO 性能影响 容器默认情况下一般都不会对 IO 进行限制,并且经过很多测试,基本上容器中的 IO 和宿主机基本没有差距,影响甚微,基本可以忽略。至于 《7文》中所说的丢数据的影响,明显是一个错误的表述,Docker 中持久化数据,一般是通过 volume 或者和宿主机目录映射,并不会直接直接写到镜像的 AUFS 上。
  3. 网络性能影响 首先不确定《7文》中所说的 Docker 1.9 版本中依然存在的网络问题是什么问题,该文也没给出 issue 链接。即便是容器的网络方案不成熟, 但 Docker 的网络标准 libnetwork 以及 Kubernetes 的容器网络标准 CNI,都是插件式的,用户可以选择更成熟的。如果服务已经运行在云上,容器一般都是可以复用云的 SDN 来实现网络的,比如 AWS 上的基于 VPC 路由的容器网络方案,QingCloud 上的 SDN 网络直通(SDN Passthrough)方案,和直接使用宿主机网络效果差距很小或者基本一样。再退一步说,如果网络性能真那么重要,并且一台服务器只运行一个服务,那可以直接用 host 模式么。
  4. 成本 容器的接纳成本已经非常小了,即便是复杂一些的 Kubernetes,相对于 OpenStack 也简单许多,所以才有人通过 Kubernetes 去部署 OpenStack 。

所以说,容器引入的成本和风险相对于收益来说,绝对是可以接受的,即便是它现在存在着许多问题,但绝对是一个潜力股,值得投入。

总结下,对技术的接纳很多情况下其实是纯粹的观念问题。最初 IaaS 出来的时候,不也有很多人说,数据库服务不适合跑在虚拟机上了,大数据服务不适合跑在虚拟机上了,现在不也有很多用户用的很好。合适不合适,脱离具体的业务场景和需求,是说不清楚的,对于大多数用户和产品来说,数据库的易用性,易维护性,可用性,要大于性能等其他方面的要求的,对另外一部分用户来说,数据库肯定要跑到物理机上,甚至 PC 服务器都不能满足。所以还是不要仅仅从自己的当前的业务需求来断定一个技术通用价值。技术人喜欢仅仅从自己的角度去进行价值判断,我个人也是这样。最近也在反思,技术人要突破,无论是创业,还是设计对客户有价值的产品,都需要把视角放宽一些,谨以此文与各位共勉。

相关链接


  1. WHY DATABASES ARE NOT FOR CONTAINERS 英文原文

  2. 数据库不适合Docker及容器化的7大原因 高可用架构译文

  3. 2016年容器技术思考:Docker, Kubernetes, Mesos 将走向何方?

  4. 从GITLAB误删除数据库想到的 左耳朵耗子原文

  5. 基础设施服务的微服务化