英文原文:Lets review.. Docker (again)
本文是一篇对 Docker“吐槽”的文章,作者从 Dockerfile、缓存、分层文件系统、Docker Hub、安全、容器和虚拟机几个方面入手,阐述了 Docker 和容器技术目前存在的一些问题,以至于说 Docker 的存在并没有必要。大家可以把这篇文章的观点作为对 Docker 认识的一个补充,对 Docker 有一个更加客观的认识。
概述
距离我上次发表对 Docker 的看法已经一年了,那个时候我狠狠的批评了 Docker 在架构上的缺陷以及其糟糕的用户体验。虽然现在项目已经发展到 1.0,但是还是得到了一些来自亚马逊的不满,用户失望程度也在不断增加,面临大量的指责,甚至还存在一些可能会导致主机污染的漏洞。然而 Docker Hub 上个人私有仓库的引入,使得用户自己不需要再运行个人的 Registry,再加上 webhook 与 GitHub 整合,所有这些看起来是一个良好的开始。
于是我决定再给 Docker 一个“机会”,并且把其投入到生产中运行六个月,看看效果怎样。结果真的令我很失望,在使用过程中,Docker 表现很令人失望,不但性能糟糕,而且由于其本身功能的不足,我们还需要在解决方案上不断地进行变通,整个过程的用户体验也不尽如人意,这使得我几乎想要把自己的脸撞碎在桌子上!事实上,在我看来 Docker 的性能的确很糟糕,以至于缓存被禁用之后编译过程变得更快。(看看 reddit 和 hackernews 上面讨论)
Dockerfile
Dockerfile 有很多问题,在我看来,他很丑陋,有很多局限性,有的地方甚至相互矛盾,并且有很多根本性的缺陷。让我们来说说,假如你想创建单个仓库的多个镜像,例如第二个镜像包含了调试工具, 但是对两个镜像的基本要是是一样的。Docker 不支持这样操作(per #9198), 当前并没有能力去对 Dockerfile 进行扩展(per#735),使用子目录的话会破坏创建的上下文以及阻止你使用 ADD/COPY(per #2224), 就像管道一样(per#2112),在容器构建的时候(build time)你也不能够使用环境变量根据不同的运行条件来改变指令(per#2637)。
我们的变通方案是创建一个基础的镜像,两个指定环境的镜像,以及一些包含重命名和 sed 替换的 Makefile 自动化脚本。还有一些意想不到不到的“特性”导致$HOME 环境变量消失了,还会产生一些没用的错误信息,使用起来的确很不方便。
Docker 缓存/分层
Docker 可以使用 COW(copy-on-write)文件系统来缓存Dockerfile 指令, 类似于 LVM 的快照,到现在仍然仅仅支持满是问题的 AuFS(AnotherUnionFS)。为了提高稳定性以及性能,之后在 0.7 版本中对 COW 做了不同的实现, 你可以在这里了解到详细的情况。但是这个缓存系统并不智能,导致了一些令人惊讶的情况,比如不能阻止某一个指令被缓存(per #1996)。并且很慢, 鉴于这一点,如果你禁用缓存或者是避免使用分层,创建的速度反而快一点, 在 Docker Hub 上传和下载的时候这个表现的更严重,详情会在下面部分进行描述:
这些问题都是由 Docker 的架构设计导致的,Docker 作为一个整体,总是线性的执行指令,即便是在很不合适的情况下也这样 (per #2439)。作为一个改变慢速创建的变通方法,你可以使用一个第三方支持异步执行的工具,例如 Salt Slack, Puppet,甚至 Bash,这样的话就完全放弃分层的思想使其毫无用武之地。
Docker Hub
Docker 鼓励通过 Docker Hub 来进行社交合作, 允许你发布自己的 Dokerfiles,不管是公有的还是私有的,这样其他人可以基于此来通过 FROM 来进行扩展,而不是拷贝,粘贴。这个生态系统类似于 AWS marketplace 的 AMIs,以及 Vagrant 的 boxes,原则上说是非常有用的。
然而由于一些原因,Docker Hub 在实现上是有缺陷的。Dockerfile 不支持多 FROM 指令(per #3378, #5714 and#5726),这就意味着你只能继承自单个的镜像。并且他也没有强制版本化, 例如 dockerfile/ubuntu:14.04 的作者可以替换掉这个标签的内容,这就相当于在使用了没有强制版本的包管理工具。另外在后面也会提到,Docker Hub 在速度上的缺陷也很令人失望。
Docker Hub 也有一个自动构建的系统,它可以监测仓库中新的提交,并且触发一个容器的创建。因为很多原因,这个功能基本上是没用处的。创建服务没有可定制化的功能,甚至连最基本的前/后脚本钩子也没有。它还强制使用一个指定的项目结构,希望在根目录中只有单一的 Dockerfile,导致创建过程变得相当的慢,也破坏了我们之前提到的创建的变通方法。
我们采用的变通方法是使用 CircleCI, 一个持续集成(CI)平台,可以触发基于 Makefile 的 Docker 创建, 并推送到 Docker Hub。这个并不能解决速度慢的问题, 唯一的解法就是使用我们自己的 Docker Registry,这样做确实有点复杂甚至可笑。
安全
Docker 原来使用 LXC 作为默认的执行环境,从 0.9 之后采用 libcontainer 作为默认的执行环境。当使用合适的执行驱动(exec-driver)时,引入它来调整命名空间的能力,权限以及使用定制的 LXC 配置。
Docker 需要一个根守护进程一直在主机上运行,并且有很多安全漏洞,比如 CVE-2014-6407和 CVE-2014-6408,非常坦率的说,这还不应该排在第一位。即便是 Gartner, 根据他们可怜的评估记录,也表达了对 Docker 不成熟,以及安全问题的担忧。
Docker,从设计上来说,对 namespace 能力过分的信任,但实际上 namespace 比一般的 hypervisor 暴露的攻击面要大很多,Xen 在 Linux 里面有 129 个 CVEs(Common Vulnerabilities and Exposures),与之相比它却有 1279 个。当然,这在某些情况下也是可以接受的,比如在 Travis CI 里面以公有的方式来构建, 但是在私有情况下和多用户的环境下,就显得比较危险了。
容器不是虚拟机
namespaces 和 cgroups 是非常强大的,允许一个进程及其子进程有一个共享内核资源的私有视图, 例如网络栈和进程表。这种粒度的控制和隔离,加上 chroot jailing 和 grsec, 可以提供一个很优秀的保护层。 有些应用, 例如 uWSGI, 直接对这些优点加以利用,而不是通过 Docker。 还有些应用不直接支持 namespaces 的可以用 firejail 封装成沙箱。如果你觉得很冒险的话, 你也可以直接在你的代码里面支持 namespace。
容器化项目,诸如 LXC 和 Docker, 利用这些特性,可以高效的在一个相同的内核空间中运行多个 linux 的发行版。与 hypervisor 相比,它们有时候会有一些优点,比如占用更少的内存并且启动速度更快。 但是这是以损失完全性,稳定性和兼容性为代价的。这里有一个跟 Linux 内核接口有关的边缘情况, 在内核和用户空间运行非兼容的和没有经过测试的 glibc 版本组合会导致一些不可预料的行为。
回到 2008 年, 当 LXC 被设想出来的时候,硬件辅助的虚拟化才有几年的时间, 很多 hypervisor 有性能和稳定性的问题,这样的虚拟化并没有被广泛的使用,也只是为了降低花费和减少物理机而做了折衷。但是现在 hypervisor 的性能已经可以和物理机器一样快了,有趣的是,在一些情况下可能更快。运行自定义的虚拟机也变的更快更便宜,随着 DigitalOcean 在性能和花费方面不断的超越EC2, 使得以一对一的方式运行应用和虚拟机,在财政上变的成为可能。
Bryan Cantrill 在这里指出, 虚拟化的性能主要取决于工作负载的类型,比如 IO 很重的应用会导致更低的性能。
对于有些特定的情况,使用容器化是正确的选择, 但是除非你能很明确的解释为什么你要使用容器,否则你便可以使用 hypervisor 来代替。即便是你使用了传统的虚拟化,你也可以直接应用 namespaces 的优势,诸如 firejail 就可以帮助你的应该在缺少本地支持的情况下来实现这样的特性。
Docker 是没有必要的
Docker 增加了一个复杂的入侵层,这使得开发,故障排查以及调试变得非常困难, 常常制造的问题比解决的问题还多。这对部署没有一点好处,因为你仍然需要使用的快照来达到自动扩展的目的。更糟糕的是,如果你不使用快照的话,你的生产环境的扩展需要取决于 Docker Hub 的稳定性。
这个已经被 baseimage-docker 这个项目滥用了,这个镜像试图通过运行 init.d 作为入口使得检查、调试,以及兼容变得更容易,它还尝试提供给你一个可以用 ssh 登录的服务器,从而把一个容器看成是一个虚拟机,尽管作者使用了很无力的论据去反驳这一点。
结论
如果你的开发工作流非常的健全,那么就应该已经明白 Docker 是没有必要的。所有它宣传的特性要么是没用的要么就是实现的非常差,并且它主要的特性直接可以使用 namespaces 来完成。Dokcer 本来应该是 8 年前的一个很可爱的想法,但是今天已经没什么用了。
更正/修订
表面上看,Docker 有很多值得关注的地方。它鼓励开发人员围绕一个一成不变的部署流程, 可以快速的,简单的开始一个新的项目,还有些其他的人认为有用的东西。但是需要提醒大家的是,本文是聚焦在一个每日的,长期使用的 Docker 上面,包括本地和生产环境。
尽管大多数提到的问题都是显而易见的,本文并没有为 Docker 如何变得更好提出什么建议。对 Docker 来说有很多解决方法,毕竟不管什么项目都是有优缺点的,我会在接下来的文章中作详细的解释。
a-ko 对使用容器化有个长期的讨论,markbnj 也有一个详细的技术反驳,这两个都是很有用的。
我想对所有给他们反馈的人说声谢谢,看到大家很喜欢我的写作风格感觉非常的高兴,我也读了几个高级工程师的回应, 包括那些欣赏我的人,这非常震撼人心。
翻译:左伟校对:王哲
===========================
译者介绍
左伟,现就职于 IBM,软件工程师,现从事于 DevOps 相关的研究,实现和推广。