DBA视角:把数据库放入Docker是一个馊主意( 四 )


另一个Docker喜欢讲的例子是软件版本升级:例如用Docker升级数据库小版本,只要简单地修改Dockerfile里的版本号,重新构建镜像然后重启数据库容器就可以了 。没错,至少对于无状态的应用来说这是成立的 。但当需要进行数据库原地大版本升级时问题就来了,用户还需要同时修改数据库状态 。在裸机上一行bash命令就可以解决的问题,在Docker下可能就会变成这样的东西:https://github.com/tianon/docker-postgres-upgrade 。
如果数据库容器不能像AppServer一样随意地调度,快速地扩展,也无法在初始配置 , 日常运维,以及紧急故障处理时相比普通脚本的方式带来更多便利性,我们又为什么要把生产环境的数据库塞进容器里呢?
Docker和K8s一个很讨喜的地方是很容易进行扩容,至少对于无状态的应用而言是这样:一键拉起起几个新容器,随意调度到哪个节点都无所谓 。但数据库不一样,作为一个有状态的应用,数据库并不能像普通AppServer一样随意创建,销毁,水平扩展 。譬如,用户创建一个新从库,即使使用容器,也得从主库上重新拉取基础备份 。生产环境中动辄几TB的数据库,创建副本也需要个把钟头才能完成,也需要人工介入与检查,并逐渐放量预热缓存才能上线承载流量 。相比之下 , 在同样的操作系统初始环境下,运行现成的拉从库脚本与跑docker run在本质上又能有什么区别 —— 时间都花在拖从库上了 。
使用Docker盛放生产数据库的一个尴尬之处就在于,数据库是有状态的,而且为了建立这个状态需要额外的工序 。通常来说设置一个新PostgreSQL从库的流程是,先通过pg_baseback建立本地的数据目录副本,然后再在本地数据目录上启动postmaster进程 。然而容器是和进程绑定的,一旦进程退出容器也随之停止 。因此为了在Docker中扩容一个新从库:要么需要先后启动pg_baseback容器拉取数据目录 , 再在同一个数据卷上启动postgres两个容器;要么需要在创建容器的过程中就指定定好复制目标并等待几个小时的复制完成;要么在postgres容器中再使用pg_basebackup偷天换日替换数据目录 。无论哪一种方案都是既不优雅也不简洁 。因为容器的这种进程隔离抽象,对于数据库这种充满状态的多进程,多任务 , 多实例协作的应用存在抽象泄漏,它很难优雅地覆盖这些场景 。当然有很多折衷的办法可以打补丁来解决这类问题,然而其代价就是大量额外复杂度,最终受伤的还是系统的可维护性 。
总的来说,Docker 在某些层面上可以提高系统的可维护性,比如简化创建新实例的操作,但它引入的新麻烦让这样的优势显得苍白无力 。
性能
性能也是人们经常关注的一个维度 。从性能的角度来看 , 数据库的基本部署原则当然是离硬件越近越好,额外的隔离与抽象不利于数据库的性能:越多的隔离意味着越多的开销,即使只是内核栈中的额外拷贝 。对于追求性能的场景,一些数据库选择绕开操作系统的页面管理机制直接操作磁盘,而一些数据库甚至会使用FPGA甚至GPU加速查询处理 。
实事求是地讲,Docker作为一种轻量化的容器 , 性能上的折损并不大,通常不会超过 10%。但毫无疑问的是,将数据库放入Docker只会让性能变得更差而不是更好 。
总结
容器技术与编排技术对于运维而言是非常有价值的东西,它实际上弥补了从软件到服务之间的空白,其愿景是将运维的经验与能力代码化模块化 。容器技术将成为未来的包管理方式,而编排技术将进一步发展为“数据中心分布式集群操作系统” , 成为一切软件的底层基础设施Runtime 。当越来越多的坑被踩完后,人们可以放心大胆地把一切应用,有状态的还是无状态的都放到容器中去运行 。但现在起码对于数据库而言,还只是一个美好的愿景与鸡肋的选项 。
需要再次强调的是,以上讨论仅限于生产环境数据库 。对于开发测试而言,尽管有基于Vagrant的虚拟机沙箱,但我也支持使用Docker —— 毕竟不是所有的开发人员都知道怎么配置本地测试数据库环境 , 使用Docker交付环境显然要比一堆手册简单明了得多 。对于生产环境的无状态应用,甚至一些带有衍生状态的不甚重要衍生数据系统(譬如redis缓存) , Docker也是一个不错的选择 。但对于生产环境的核心关系型数据库而言,如果里面的数据真的很重要 , 使用Docker前还是需要三思:这样做的价值到底在哪里?出了疑难杂症能Hold住吗?搞砸了这锅背得动吗?


推荐阅读