网站首页 > 基础教程 正文
目录
一、Docker 容器并不神秘
二、使用 Docker 构建容器是多余的
三、部署隔离并非新鲜事
四、默认情况下 Docker 对安全性无用
五、应用程序容器太荒谬了
六、Docker 的替代品
Docker 最近非常流行,可惜它不太好。
提前说明:这绝对不是说我认为 Docker 太“有主见”,也不是说其他工具更灵活。我认为,学习和使用 Docker 比学习和使用我下面介绍的工具要复杂得多。Docker 确实比其他工具更复杂、更难用,这些工具也碰巧比 Docker 更灵活,但这不是我推荐它们的原因:我之所以推荐,是因为它们更容易学习和使用。如果它们除了更简单易用之外,还真的更灵活,那也只是因为它们整体设计更出色而已。
一、Docker容器并不神秘
首先,简要介绍下容器的工作原理。Linux 容器[1]建立在两个内核特性之上,即命名空间和 cgroups。它们的架构相当容易理解。
我鼓励大家阅读主要的命名空间手册页:man 7 namespaces。它写得很好,很容易让人理解这个概念。如果你为所有[2]这两个命名空间创建一个新实例,你就得到了一个类似容器的东西。
cgroups 文档(位于Linux 源代码本地副本中的Documentation/cgroups-v1/和Documentation/cgroup-v2.txt )不太直观,但仍然比我能写的解释更好。其基本思想是 cgroups 是一种进程分组机制。此机制用于实现其他系统,如 man 7 cpuset,后者用于跟踪和调度容器进程。
如果你进行相应的系统调用,进入 cpuset cgroup 并创建新的命名空间,你就拥有了一个容器。这并不难。
可以编写一个相对简短的 C 语言程序并创建一个Unix 风格的实用程序,使用这些系统调用来启动一个新的容器。你可以通过使用man 1 nsenter和man 1 unshare亲自了解这一点,它们是命名空间系统调用的最小包装器。
解释这一点的目的是为了表明 Linux 容器功能相当简单。Docker(或任何其他容器软件)在启动容器这一特定领域并没有做任何特别神秘的事情。有了这些知识,让我们看看 Docker 实际上还做了什么。
二、使用 Docker 构建容器是多余的
我们首先看看 Docker 如何为你构建容器镜像。你从 Docker 中心下载某种镜像,Docker 会兴奋地运行一会儿,同时你会看到滚动的内容和进度条填充。最终,你会得到一个来自某个 Linux 发行版的文件系统树,其顶部还添加了一些内容。
有些人可能会感到惊讶,我们这样做已经有几十年。
事实上,每次在机器上安装 GNU/Linux 时,我们都会这样做。该文件系统树中的大多数文件来自某个发行版的软件包。软件包管理器当然能够将软件包安装到任意目录中;这就是它们安装新系统的方式。
事实上,大多数软件包管理器甚至有整洁的小封装脚本来帮你安装!而这些只需要安装apt-get install debootstrap(或类似软件)即可!为最流行的几个发行版[3]构建文件系统树:
- debootstrap focal /srv/trees/ubuntu
- debootstrap stable /srv/trees/debian
- dnf -y --releasever=33 --installroot=/var/lib/machines/f33 --disablerepo='*' --enablerepo=fedora --enablerepo=updates install systemd passwd dnf fedora-release vim-minimal glibc-minimal-langpack
- pacstrap /srv/trees/arch
当然,你也可以使用这些命令选择要安装的其他软件包,或进行其他更改。几十年来,人们一直使用这种方法来构建 chroot,稍后我会详细介绍。还有更多新颖的软件包管理器,如 Nix 和 Guix,它们具有有趣的功能,可以让事情变得更加简单。
但是等等,node.js 的发行版太旧了!我该如何获取最新版本?
如果你需要更新版本,首先要做的就是启用发行版的更新软件包仓库:Ubuntu backports、Debian backports、Fedora EPEL。软件包管理器不仅仅是摆设;它们确实可以让你的系统保持最新状态,还有许多其他优点[4]。
如果无法通过发行版渠道获得合适的更新版本,我认为的下一个最佳选择是更新发行版软件包,这可能相当容易,具体取决于你的发行版。或者,如果没有可用的软件包,你可以自己创建一个。如果你处于早期开发阶段,这可能会有点麻烦(尽管有一些工具可以让这变得更容易[5]),但同样也会有很多好处[4]。
不过,大多数人还是使用传统的黑客手段。你可以通过 chroot 进入,然后像使用 Dockerfile 中的一些 "RUN "指令一样,执行通常的 pip install foo 或 gem install bar 或 npm install baz 或 ./configure && make && make install。
所以至少在这里,Docker 没有真正的优势。
最重要的是,你可以使用与普通 Linux 机器相同的安装脚本,不需要将所有内容重写到 Dockerfile 中。你可以手动安装,可以使用 shell 脚本,可以使用 Ansible,可以用 Java 编写精致的 ConfigurationManagementFactory,想做什么都行。这只是安装软件而已,这并不复杂,除非你把它弄复杂。据说,Dockerfile 比debootstrap在脚本开头运行更简单,但我不确定我是否理解为什么。在我看来,Docker 并不比标准方式更简单或更容易。
现在,Docker 确实使用分层技术,提高构建新容器的磁盘空间和时间效率。它默认使用OverlayFS来执行此操作[6]。你可以使用一个小的 shell 脚本和一些 mount 调用轻松地自己重新实现它,但没有必要。
相反,我只使用man 8 btrfs-subvolume。btrfs 是一种写入复制(copy on write)文件系统,它可以在“子卷(subvolume)”中即时创建节省空间的文件系统树副本,用户看到的只是普通目录。
你可以使用 btrfs subvolume create /srv/trees/ubuntu && debootstrap focal /srv/trees/ubuntu/, 将现有的 Ubuntu 文件系统树复制到子卷中 。然后,当你想要使用特定软件构建新容器时,只需复制该子卷,并在副本上执行修改;也就是说,btrfs subvolume snapshot /srv/trees/ubuntu /srv/containers/webapp,继续进行操作/srv/containers/webapp。如果要复制这些修改,再拍一次快照即可。
这实际上比 OverlayFS 更好,因为无需维护大量有关挂载层的状态,也无需在重启时重新设置它们。你的容器文件系统只是位于卷中,等待你去启动。
当然,如果你出于某种原因不喜欢 btrfs,你完全可以使用 zfs、OverlayFS、AUFS 或其他任何文件系统;没必要为了进行简单的写入复制或分层操作而实施“存储驱动程序”。
如果你想在构建系统时进行某种更改跟踪,应将其保存在适当的层,或者使用专用工具。/usr应该是不可变的,并由软件包构建,你的应用程序数据应该位于/srv或/var中,并被挂载在其中,因此作为系统构建过程中的所有配置数据都应该保存在/etc中。要跟踪这些数据,只需使用 etckeeper 并将/etc保存在 git 存储库中。这是正确和恰当的,因为/usr应该是不可变的。如有必要,OSTree 可让你对整个文件系统进行版本控制。
如果出于某种原因,你仍需要提取 Docker 镜像,可以将其视为构建文件系统树的另一种方式。有一些工具可以帮你做到这一点,例如 machinectl pull-dkr。
三、部署隔离并非新鲜事
但是等等!Docker 不仅仅是构建文件系统树这一简单任务上,添加了一个毫无意义的抽象层!它能让你可以在容器中实际使用这些文件系统树!
不过,这并不是什么新鲜事。正如我之前所说,这些工具被用来构建 chroot 已经有几十年的历史了。
什么是 chroot?man 1 chroot是一个已有几十年历史的工具,它可以让你更改根目录/指向的内容;例如,你可以 point/at/srv/container/webapp,所有程序都在根目录的子目录中查找库和二进制文件,例如/usr/lib和/usr/bin。因此,通过使用 chroot,你可以拥有一组完全不同的库和二进制文件;当你在 chroot 中运行程序时,它们将只看到你在该文件系统树中安装的库和软件。
为了更好解释 chroot 的用途,这里有我“写”的一篇关于 chroot 的小短文。
- 系统管理员使用 chroot 为开发、QA 和生产团队提供标准化环境,从而减少“在我的机器上工作”的相互指责。通过对应用平台及其依赖项进行“chroot”,系统管理员可以消除操作系统发行版和底层基础架构之间的差异。
这听起来确实很有用。但等等,Docker 又出现了。让我们看看他们怎么说。
- 系统管理员使用 Docker 为他们的开发、QA 和生产团队提供标准化环境,从而减少“在我的机器上工作”的相互指责。通过“Docker 化”应用平台及其依赖项,系统管理员可以抽象出操作系统发行版和底层基础架构之间的差异。
Docker 提供这些功能并不是什么新鲜事。不过,他们如此大张旗鼓地营销,倒是很有新意。
四、默认情况下 Docker 对安全性无用
但是等等!Docker 是“容器”,新颖、奇特、令人兴奋。chroot 既陈旧又无趣。容器肯定比 chroot 好!
好吧,chroot 虽老旧且无趣,但确实有优势,比如“它不会随机地坏掉”。但可以肯定的是,容器确实有其自身的显著优势。
举个例子:chroots 的安全并不可靠的,如果在 chroot 中以 root 身份运行,很容易就会被破解。容器特别安全,独一无二,对吗?
错了!对于大多数用途来说,Docker 容器提供的主要有趣功能是隔离网络。也就是说,Docker 容器会阻止容器内的应用程序绑定外部网络接口上的端口。还有什么能阻止应用程序使用端口?是服务器上安装的防火墙。同样,这又是一个毫无意义的抽象,用来解决已经解决的问题。
事实上,如果你遵循疯狂的默认做法,以 root 身份在容器中运行应用程序,那么你的系统的安全性可能会大大低于正确实施的 chroot。
从非特权 chroot 中突破取决于一个众所周知且研究充分的漏洞领域:Linux 特权升级。Linux 命名空间容器带来了全新的安全问题;它们很可能存在固有漏洞,内核无法在不破坏未包含功能的情况下纠正这些漏洞。事实上,Docker 自己的开发人员也热情地承认,Docker 目前还不能以 root 身份安全地运行代码。几十年来,人们一直在 chroot 中以非特权用户身份运行应用程序,以减少这种威胁。默认情况下,Docker 并不会这样做。
五、应用程序容器太荒谬了
不过,容器还是很酷的,对吧?只有随着命名空间和 cgroups 的发展,Docker 才能最终正确使用“应用容器”。与 chroot 相比,Docker 带来的隔离功能大大增强了功能;我们终于可以在生产中部署 "应用容器 "了。我们终于可以通过运送整个文件系统来实现应用程序的主机独立性!对吧?
对于那些不了解术语的人来说,Docker 将其容器方法称为“应用容器”。基本思想是,你拥有所有这些命名空间和 cgroup,然后创建一个容器,然后在容器内运行单个软件。我想,这很酷。另一种方法是在容器内运行 init 系统,这将启动一个完整的“传统”操作系统。容器提供足够的隔离性,因此你可以把它们视为非常轻量级的虚拟机。Docker 反对这种做法,因为……
好吧,我其实不确定 Docker 开发人员是怎么想的。难道是为了让容器更 "轻量级",不把它们当做虚拟机,而是运行一个 init 系统?难道他们只是想到可以在容器内运行单个服务,而不是运行完整的系统,却从未想过这是否是个好主意?
“应用容器”的实际问题众所周知。僵尸孤儿进程[7]会填满你的容器并消耗资源,却没有启动程序来获取资源;传统的 cron 和 syslog 守护进程不会自动可用;等等。这些都是问题,但如果我们编写了足够多的新软件,使应用程序容器运行良好,就一定能克服这些问题。
更根本的问题是,“应用容器”并不意味着什么。我们已经解决了文件系统隔离方面的问题;我们知道,没有Docker,没有容器,我们也能做到这点,那么什么是“应用容器”呢?
它只是另一个系统服务!只是另一个守护进程!所以如果你想要隔离一个服务,那就这么做吧!没有必要把它称为“容器”,以免混淆术语。
就像其他人一样,只需使用 Linux 命名空间功能即可隔离你的应用程序。几十年来,我们一直使用 chroot 和 su 来保护和隔离应用程序;命名空间和 cgroups 只是这个工具箱中的另一个工具。我将以 systemd (它是将这些技术用于系统服务的先驱)为例,但 sysvinit 和其他 init 系统也可以同样轻松地使用命名空间和 cgroups 进行隔离。
由此可见,应用容器的概念显然并没有什么特别新颖之处。当然也没有什么值得采用 Docker 的全新方法,因为它抛弃了大量现有的 GNU/Linux 堆栈!
六、Docker 的替代品
我想我已经深入介绍了 Docker 各个部分的替代方案。还有一点要说,我在第一部分提到,一个简单的、Unix 风格的实用程序可以提供容器化功能,其模式与 chroot 类似。我的感觉是man 1 systemd-nspawn 就是这个实用程序。它的手册页甚至明确将其与 chroot 进行了比较:
- systemd-nspawn 可用于在轻量级命名空间容器中运行命令或操作系统。它在很多方面与 chroot(1) 类似,但功能更强大,因为它完全虚拟化了文件系统层次结构、进程树、各种 IPC 子系统以及主机和域名。
而且每个 systemd 系统上都有该功能,因此很容易上手。请查看手册页中的示例debootstrap。将它与 GNU/Linux 生态系统的其他部分(如 debootstrap 和 btrfs)相结合,你可以获得具有 Docker 的所有功能甚至更多[7]的功能,而无需复杂性开销。最终,Docker 相对于其提供的简单功能来说太复杂了;根本就不需要它。
猜你喜欢
- 2024-12-08 被 Docker 日志坑惨了
- 2024-12-08 软路由的用法(自动追剧配置)
- 2024-12-08 Docker 使用命令
- 2024-12-08 WizNote-为知笔记Docker私有部署教程
- 2024-12-08 在 Linux Debian 上安装配置 Kubernetes 集群
- 2024-12-08 玩Docker,不求人~保姆级入门教程!
- 2024-12-08 Docker镜像服务器关停,“硬控”国内NAS玩家?解决方法来咯!!
- 2024-12-08 Docker最全详解(万字图文总结)
- 2024-12-08 Debian 12.8 发布 | Mistral AI 推出批量 API,成本降低 50%
- 2024-12-08 如何安装Docker和Docker Compose的详细指南?
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)