距我上次炮轰Docker的缺陷构架设计和糟糕的用户体验已经一年多了。项目不久前发布了1.0版本并且亚马逊曾给过一些差评, 但期间伴随着用户的失意,漫天的指责还有甚至利用缺陷引起的主机污染。尽管如此, Docker Hub 引入的私有源终结了主机部署需要自己注册的麻烦, 同时web钩子与紧凑的一体化Github编译,都是一个良好的开端。
所以我决定再给Docker一个机会,投入生产环境6个月。结果糟透了的性能,业余的工作区和让人生气的用户体验让我只想拿脸撞桌子。实际上性能差到禁用缓存导致了更快的编译用时。
如果你期待Docker的正面消息,或者它的维护者,那么你真是太不走运了。
Dockerfile 有很多问题,丑陋、约束、矛盾还有从根本上的缺陷。比方说在一个仓库里生成多个镜像,所有的镜像都需要同样的依赖环境,但是其中一个包含调试工具。Docker不支持这个(per #9198 ),你也不能扩展Dockerfile(per #735 ),使用子目录会破坏创建环境同时会阻止你使用ADD/COPY命令(per #2224 ),因为dockerfile使用管道方式读入的(per #2112 ),你也不能使用环境变量在生成镜像的时候根据条件来改变指令(per #2637 )。 我们的
Docker有通过使用COW(写时复制)文件系统 缓存 Dockerfile指令的能力。和LVM 快照 类似,并且直到至今只支持AuFS,这个有无数的问题。接下来在 0.7 发布版中不同于COW实现被引入了来改善稳定性和性能, 详情 。
然而这个缓存系统是不智能的,导致了一些令人惊讶的 负效应 ,不能阻止来自缓存的单指令( per #1996 )。它也慢的痛苦,从这一点来说,如果阻止缓存和使用层,会让构建快一些。缓慢的上下载速度使Docker Hub的性能变得更糟糕。接下来详细描述。
这些问题都是由Docker作为一个完全的强制行线性指令执行甚至在它完全不适合的条件下的架构设计导致的。作为一个慢速构建的工作环境,你可以使用第三方工具支持异步执行,比如 Salt Stack , Puppet 或者 甚至是 bash , 完全打败层的目标和使他们一无是处。
Docker鼓励通过Docker Hub进行社区协作,在Docker Hub上你可以以公开或私有的方式发布你的Dockerfile,这样其他用户可以通过FROM命令来使用或者扩展你的Dockerfile,而不是复制粘贴。但是这样做也是有问题的。 Dockerfile不支持多个 FROM 命令(per #3378 , #5714 and #5726 ),意味着你只能继承一个镜像。Docker Hub也没有强制的版本,比如 dockerfile/ubuntu:14.04
的作者可以更换标签对应的内容,这就像使用一个没有强制版本的打包管理器。而且它TMD还有速度限制,这个下面会提到。
Docker Hub还有一个自动构建系统,它会检查你仓库中新的提交然后触发一个容器构建。这是完全无用的。构建配置几乎没有什么定制的能力,甚至没有最基础的构建前/后的脚本钩子。这迫使我们创建一个特殊的项目结构,在项目的根目录放一个单独的Dockerfile,这破坏了我们之前提到的构建环境,而且构建时间太TMD长了。
我们的小组曾近使用 CircleCI ,一个很特别的托管CI平台,它可以根据Makefile触发Docker的构建然后上传到Docker Hub上。这个也没有解决龟速的问题,看来唯一的选择就是使用我们自己的Docker仓库,但是这个实在是太 复杂 了。
Docker最初使用LXC作为基础的运行环境,但是从0.9版开始他们使用自己的容器库。通过它可以 调整 命名空间的性能、权限,而且可以通过定制LXC 配置 使用适当的exec-driver。
它需要在宿主机上启动一个守护进程,Docker上有 很多 安全上的缺陷,比如 CVE-2014-6407 和 CVE-2014-6408 ,坦白说这些缺陷都不应该出现在第一位。甚至当Gartner看到他们 跟踪记录 上可怜的评估时,对Docker的不完善和安全问题表示了 关注 。
从设计上来说,Docker太信任命名空间的 能力 ,而命名空间相比普通的hypervisor有太多的漏洞,像Xen现在又 129 个公开的漏洞,而Linux里有 1279 个漏洞。在一些场景下这是可以接收的,比如在Travis CI上的公开构建,但是在多用户的私有环境里是非常危险的。
集装箱化的项目,比如 LXC 和 Docker,利用这些特性可以非常有效的在同一个内核空间里运行多个发行版。与 hypervisors 相比 ,这样的 优势 在于更低的内存占用,更快的启动速度,但是会降低安全性、稳定性和兼容性。一个极端的 情况 就是 Linux 内核接口 ,在内核和命名空间中运行不相容的或者未经测试的 glibc 可能产生一些无法预测的操作。
退回到2008年,当时LXC还在设计阶段,硬件辅助虚拟化才刚面世几年,还没有被广泛的使用,很多hypervisors在性能和稳定性上都存在问题,不过鉴于其使用成本低廉而且可以减少实体消耗(pysical footprint我觉得就是实体计算机占地面积的意思,实际上就是提高了计算机利用率,减少了计算机数量),这些缺点都是可以接受的。但是现在我们的hypervisor已经和纯物理机跑的一样快了,有时候甚至 更快 一点。按需托管的虚拟机也同样变得更快更便宜,比如 DigitalOcean 要比 EC2 性能 更强而且更便宜,这使得我们不需要花费太多就可以做到虚拟机和应用的1对1部署。有不少特别的案例说明集装箱化是一个正确的解决途径,但是除非你能说明为什么在你的案例中它是不可或缺的,那么你就应该使用hypervisor。而且使用虚拟化你也可以同样享用命名空间的优点,即使你的应用自身不支持,像 firejail 这样的工具也可以帮助你获得这些特性。
Docker 侵入式的增加了复杂的一层,让开发、调试、修复bug异常的的复杂,经常创造很多问题而不是解决问题。对于开发它带不来任何好处,因为你依然需要利用快照来实现响应式的自动扩展。更糟糕的是,如果你不使用快照那么你的产品环境的扩展将依赖于Docker Hub的稳定性。
另外在实际使用中,有很多项目把错误的把容器当做VM来使用。例如 baseimage-docker ,这个镜像使用init.d作为进入点,可以选择性的开启ssh server,从而简化了监控、调试、更换。但是该镜像作者们用简单的 说明 拒绝了这个观点。
如果你的开发工作流还算清醒,那么你应该已经理解Docker不是必需的。所有它宣称的那些有用的特性不是没用就是实现的很糟糕,并且用命名空间就可以简单地直接实现它的主要优势。八年前Docker会是一个不错的主意,但现在它是鸡肋。
感谢 CYPHERDEN 提供的封面图,取自 PewDiePie