【AICon】 如何构建高效的 RAG 系统?RAG 技术在实际应用中遇到的挑战及应对策略?>>> 了解详情
写点什么

唱衰 Docker,给大红大火的 Docker 泼点冷水

  • 2015-02-13
  • 本文字数:3780 字

    阅读完需:约 12 分钟

从我上一次对 Docker 进行评价到现在已然经过了一年有余,想当初我在文章里对这套容器技术方案的架构设计缺陷与糟糕的用户体验做出了严厉的批判。不过在这段时间当中,Docker 项目也开始逐步走向成熟,迎来自己的 1.0 版本并在 Amazon 的推动下声名大噪,但同时用户挫败感、过度宣传引发的指责甚至因漏洞遭利用而引发主机感染越来越多。当然, Docker Hub 中私有库的引入让用户不必再为了托管部署而运行自有 Registry 系统,再配合 webhook 以及同 GitHub 的紧密集成, Docker 看起来有一个良好的前途。

有鉴于此,我决定再给Docker 一次机会,并以六个月为周期将其引入生产环境。结果非常糟糕,Docker 性能极差,而旁门左道的解决方案加上以用户体验为代表的种种短板简直令人抓狂。实际上,Docker 的性能表现实在太差,禁用缓存功能竟然能够加快build 速度。

(感兴趣的朋友可以查看 reddit ycombinator 网站上与该主题相关的讨论内容)。

Dockerfile

Dockerfile 存在着一系列问题,它令人讨厌、充满局限、不伦不类而且包含根本性缺陷。假如要构建一个库的多个镜像,例如第二个镜像包含内容调试工具,但两个镜像拥有同样的基础运行要求。Docker 不支持这种做法(详见 9198 号问题),我们无法扩展 Dockerfile(详见 735 号问题),使用子目录会破坏构建上下文并导致用户无法使用 ADD/COPY(详见 2224 号问题)或者“管道(piping)”(详见 2112 号问题),我们也不能在构建过程中通过环境变量实现有条件的指令变更(详见 2637 号问题)。

我们给出的解决方案是创建一个基础镜像,两个特定环境的镜像以及其它一些包括重命名以及sed 替换功能的Makefile 自动化。除此之外,Docker 中还有一些意想不到的“功能”有可能导致环境变量$HOME 消失,进而产生无用的错误信息。太令人厌恶了。

Docker 缓存 / 层

Docker 有能力利用 COW(即写入时复制)文件系统实现缓存 Dockerfile 指令,这一点与 LVM快照机制相似,而且直到最近都只支持AuFS,而后者还存在大量问题。之后, 0.7 版本引入了多种不同的 COW 实现方式以改进稳定性与性能,感兴趣的朋友可以点击此处了解更多细节信息。

然而,这套缓存系统不智能,它不能阻止单一指缓存(详见 1996 号问题),产生了一些意料之外的副作用。它的运行速度也极为缓慢,如果禁用缓存并避免使用层,其构建速度甚至能够得到提升。而Docker Hub 缓慢的上传/ 下载速度则让情况进一步恶化,这个问题我们将在下文作进一步评述。

这些问题均源自Docker 整体所采用的糟糕的架构设计,这直接导致它即使是在完全不适用的情况下,依然会强制执行线性指令(详见 2439 号问题)。作为构建缓慢的解决方案,可以使用支持异步执行的第三方工具,例如 Salt Stack Puppet 甚至 bash ,它们完全能够达成层的目的而使层变得没用。

Docker Hub

Docker 鼓励用户通过 Docker Hub 进行社会化合作。用户可以在上面发布 Dockerfile——包括公开与私有文件。其他用户可以通过 FROM 指令而不是复制 / 粘贴来继承并使用这些 Dockerfile。该生态系统类似于 AWS市场以及Vagrant Boxes 中的 AMI,从理论上讲还是非常有用的。

然而由于一些原因,Docker Hub 的实现存在缺陷。Dockerfile 不支持多 FROM 指令(详见 3378 号 5714 号以及 5726 号问题),这意味着只能继承单个镜像。此外,它没有版本强制。举例来说,dockerfile/ubuntu:14.04 的作者可以替换该标签的内容,这相当于允许用户使用软件包管理器但又不没有版本强制机制。而且正如下文所提到,Docker Hub 在这方面存在着令人沮丧的速度缓慢的限制。

Docker Hub 还拥有一套自动化构建系统,能够检测到库中新提交内容并触发容器构建。因为许多原因,这项功能也是完全没用。由于几乎不能定制,构建配置受到了极大限制,甚至无法支持最基本的脚本执行前 / 后的钩子。Docker Hub 采用一套特殊的项目结构,一个项目下只能有一个 Dockerfile,这破坏了我们先前提到的构建解决方案,而且构建速度极为缓慢。

我们的解决方案是使用 CircleCI ,它是一个优秀的托管 CI 平台,能够从 Makefile 触发 Docker 构建并推送到 Docker Hub。虽然这种方式无法解决速度慢的问题,但唯一的可选方案是使用我们自己的 Docker Registry,其复杂程度都到了荒唐的地步。

安全性

Docker 最初使用 LXC 作为默认执行环境,但现在, 0.9 版本默认使用 libcontainer。这使得用户可以调整命名空间功能、权限,并且可以在使用合适的exec-driver 时使用自定义的LXC配置文件

这需要一直在主机上运行一个root 守护进程,而且Docker 一直存在着若干安全漏洞,例如 CVE-2014-6407 以及 CVE-2014-6408 。坦率地讲,这些问题起初就不应该存在。甚至 Gartner 公司也在其追踪报告中给出了糟糕评价,并表达了对Docker 的不成熟和安全性问题的担忧

按照设计,Docker 对于命名空间功能给予充分信任,这就导致其攻击面要比其它典型的虚拟机管理程序更宽。Xen 拥有 129 项 CVE,相比之下 Linux 则拥有 1279 项。在某些情况下上述问题并非不可接受,例如在 Travis CI 当中进行公开构建,但这对于私有、多用户环境来说无疑是危险的。

容器与虚拟机并不是一回事

命名空间与 cgroups 功能极为强大,允许一个进程及其子进程拥有一个共享内核资源——例如网络堆栈以及进程表——的私有视图。这种细粒度控制与隔离机制配合上chroot jailing 与 grsec ,能够提供非常出色的保护层。一些应用程序,如 uWSGI ,可以在没有 Docker 的情况下直接利用这些特性的优点,而不支持命名空间的应用程序则可以利用 firejail 实现沙箱化处理。如果您有冒险精神,可以将这种支持直接添加到自己的容器化项目的代码中,例如LXC 以及Dokcer,从而在单一内核空间中利用这些特性的有点高效地运行多套发行版。相较于虚拟机管理程序,这种作法有时候会有降低内存使用率和减少启动时间的好处,但其代价就是降低安全性、稳定性以及兼容性。举个与 Linux Kernel Interface 相关的最糟糕的极端案例,在内核及用户空间中运行不兼容或者未经测试的glibc 组合版本很可能引发意料之外的行为。

早在2008 年LXC 尚处于构思阶段时,硬件辅助虚拟化也仅仅诞生了几年时间,许多虚拟机管理程序都存在着性能以及稳定性问题。因此,虚拟化技术并没有得到广泛应用,而面对成本以及物理基础设施占用减少等优势,上述问题是可以接受的。不过如今我们的虚拟机管理程序在性能表现方面几乎与裸机设备不相上下,而且有趣的是,在某些情况下速度更快。另外,托管的、按需分配的虚拟机速度越来越快,成本越来越低, DigitalOcean 在性能与成本方面都要远远胜过 EC2,这也使应用程序与虚拟机之间进行一对一映射从经济角度讲成为可能。

[编辑意见] 正如 Bryan Cantrill 提出的观点,虚拟化技术的性能将受到工作负载类型的显著影响。例如,IO 任务繁重的应用程序会导致性能降低

在某些特定的应用场景中,容器化确实是恰当的解决方案,不过除非能够明确说明为什么在你的应用场景中选择此类处理方式,否则你可能应用使用虚拟机管理程序代替。而且即使使用虚拟化技术方案,你仍然应该利用命名空间的优点,而在应用程序没有对这些特性提供原生支持的情况下,像 firejail 这样的工具可以提供帮助。

Docker 并不是必须的

Docker 增加了一个复杂的侵入层,使开发、故障排查以及调试工作的难度大幅上升,它带来的问题往往多于它能够解决的问题。它在部署上也没有任何优势,因为你仍然需要利用快照实现响应式自动扩展。更糟糕的是,如果大家并没有使用快照机制,那么生产环境的扩展就会依赖于 Docker Hub 的稳定性。

目前有不少项目都在滥用容器化技术,例如 baseimage-docker ,该镜像旨在通过运行作为入口的 init.d 简化检查、调试和兼容,甚至还提供一个可选的 SSH 服务器,实际上是将容器当作虚拟机对待,虽然作者本人以无甚说服力的言词反对这种观点。

总结

如果开发流程合乎情理,那么你已经明白Docker 不是必须的。Docker 中号称能够带来助益的全部功能要么完全无用,要么实现很差,而其最大的优势直接使用命名空间就很容易实现。如果放在8 年前,Docker 是一个有趣的概念,但以现在的眼光来看,它几乎是没用的。

修正/ 改进

从表面上看,Docker 还有很多事情要做。它的生态系统鼓励开发人员倾向于“不可变部署(immutable deployment)”这样一种理念,新项目能够更快更轻松的地完成,在这一点上其实用性确实得到了许多数人的肯定。然而需要注意的是,这篇文章的主旨在于探讨Docker 的日常使用和长远使用,包括在本地及生产环境中。

尽管前面提到的大部分问题都清晰易懂,但文章并没有提及Docker 如何才能做得更好。有许多可选的方案可以替代Docker,每种方案中都有自己的优点和不足,而我将在接下来的文章中进行详细阐述。

感兴趣的读者可以查看 a-ko markbnj 的进一步讨论,前者讨论了容器化的长远影响,后者从技术上进行了反驳,你可能会觉得它们都非常有用。

我要对在百忙中抽出时间给出个人反馈意见的每位朋友表示由衷的感谢。看到文章受到大家的关注确实令人倍感振奋,而且在读过多位工程技术大牛——其中包括许多年来都一直给我启发的那些人——的回应后,我也颇有种诚惶诚恐之感。

查看英文原文: Lets review… Docker (again)


感谢谢丽对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2015-02-13 21:539147

评论

发布
暂无评论
发现更多内容

java学习-数据类型和运算符,Java爬虫爬取视频

Java 程序员 后端

Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day2,mysql数据库教程实验二答案

Java 程序员 后端

java教程——线程,整合springboot集成实现动态刷新配置

Java 程序员 后端

java整理,springboot2精髓百度云

Java 程序员 后端

Java日志体系(二) log4j 配置文件详解 缓存问题,mybatis基本工作原理

Java 程序员 后端

Java并发(五),大厂程序员35岁后的职业出路在哪

Java 程序员 后端

Java岗大厂面试百日冲刺【Day45】,java开发实战经典第二版pdf

Java 程序员 后端

Java已死,有事烧纸!,java工程师面试宝典下载

Java 程序员 后端

Java并发关键字-final,36套java架构师百度云

Java 程序员 后端

Java并发(三),java程序设计教程雍俊海第三版答案

Java 程序员 后端

Java数组的拷贝 优化冒泡排序 二分查找,神策数据java面试题

Java 程序员 后端

Java实现人脸检测,oppojava后端面试几面

Java 程序员 后端

Java实现堆及其功能,mybatis的原理实现过程

Java 程序员 后端

Java如何在运行时识别类型信息?,java发展史百度百科

Java 程序员 后端

Java并发(二),redis深度笔记

Java 程序员 后端

Java开发两年备战金三银四:多线程+IO,zookeeper面试题总结

Java 程序员 后端

Java实现数据结构中的八种排序方法,深入讲解Java

Java 程序员 后端

Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day5,docker教程学习

Java 程序员 后端

Java市场饱和了吗?现在转行学习Java会不会太晚了?,linux操作系统基础

Java 程序员 后端

Java并发工具AbstractQueuedSynchronizer实现详解,如何保证高可用

Java 程序员 后端

Java应用日志如何与Jaeger的trace关联,腾讯、字节跳动面经已发

Java 程序员 后端

Java开发五年裸辞美团,八个月后跳槽阿里涨薪20w!,大学java课程视频

Java 程序员 后端

Java开发必备 Git 分支开发:规范指南及完全学会Git的24堂课笔记

Java 程序员 后端

java教程——泛型,java零基础教学视频

Java 程序员 后端

Java实现AES加密算法,2021最新百度、头条等公司Java面试题目

Java 程序员 后端

Java实现各种内部排序算法,mysql排它锁之行锁

Java 程序员 后端

Java岗大厂面试百日冲刺【Day42】,两年java开发面试题

Java 程序员 后端

Java学习路线图(如何快速学Java),java数据结构与算法面试题

Java 程序员 后端

Java实现单链表、栈、队列三种数据结构,linux内核编程入门篇

Java 程序员 后端

Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day4,kafkastreams实战

Java 程序员 后端

Java并发包源码学习系列:LBD双端阻塞队列源码解析,linux内核架构与底层原理

Java 程序员 后端

唱衰Docker,给大红大火的Docker泼点冷水_服务革新_Cal Leeming_InfoQ精选文章