翻译自: What even is a container: namespaces and groups
当我第一次听说容器时我的反应是 - 啥, 啥玩意儿?
容器是一个进程吗? Docker 是什么? 容器就是 Docker 吗? 天啊!
准确来说 "容器 (container)" 这个词并不代表任何含义. 它基本上只是 Linux 内核提供的一些新特性 ("namespaces" 和 "cgroups"), 它们使你得以将进程互相隔离. 当你在使用这些特性的时候, 你可以把它们称为 "容器".
这些特性好像让你以为你创建了一些虚拟机, 虽然它们实际上根本不是虚拟机, 它们只是同一个 Linux 内核中运行的一些进程而已. 让我们仔细看看!
namespaces
Okay, 现在假设我们想要一个类似虚拟机的东西. 其中一个我们想要的特性就是 - 我们的进程应该与计算机上的其他进程隔离开来, 对吧?
这里我们就要提到 Linux 提供的一项特性: namespaces. 它有几种不同类型:
- 在一个 pid namespace 中你将成为 1 号进程, 并且可以开启其他的子进程. 所有其他的程序都消失了
- 在一个 networking namespce 中你可以使用任何你想使用的端口, 同时不与其他在运行的进程冲突
- 在一个 mount namespace 中你可以挂载和卸载任何文件系统, 同时不影响主机上的文件系统. 因此可以在其中挂载完全不同的设备 (通常比主机少)
实际上创建一个 namespace 超级简单! 只要运行 unshare
命令 (命令名称来自于同名的系统调用) 就可以.
接下来我们创建一个 PID namespace 并在其中运行 bash!
sudo unshare --fork --pid --mount-proc bash
看看发生了什么?
root@kiwi:~# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 28372 4148 pts/6 S 23:01 0:00 bash
root 2 0.0 0.0 44432 3836 pts/6 R+ 23:01 0:00 ps aux
Wow, 我们来到了一个全新的世界! 这里只有两个进程 - bash 和 ps. Cool, 就是这么简单!
不过如果我们从常规的 PID namespaces 来看这些进程, 它们就变得毫无价值, 我们可以看到以上新 PID namespace 中的进程是这样的:
root 14121 0.0 0.0 33264 4044 pts/6 S+ 23:09 0:00 htop
这个 14121 号进程 (常规 namespace 中的) 就是我们新的 PID namespace 下的 3 号进程. 相当于是我们从两个角度看到的同一个东西, 只不过后者的限制更严格而已.
进入另一个程序的 namespace
我们也可以进行别一个运行中的程序的 namespace! 这时可以用到 nsenter
这个命令. 我想这就是 docker exec
的工作原理吧? 可能是?
cgroups: 资源限制
Okay, 我们已经成功创建了一个与旧世界隔离的, 拥有自己的新进程和插口 (sockets) 的新世界. Cool!
如果我们想要限制某些进程占用的内存或者 CPU, 又该怎么做呢? 我们很幸运. 因为在 2007 年一些人就开发出了 cgroups
. 它和对一个进程使用 nice
命令差不多, 不过提供了许多新的特性.
接下来创建一个 cgroup! 我们希望创建一个只限制内存使用的 cgroup:
$ sudo cgcreate -a bork -g memory:mycoolgrou
看看里面有什么!
$ ls -l /sys/fs/cgroup/memory/mycoolgroup/
-rw-r--r-- 1 bork root 0 Okt 10 23:16 memory.kmem.limit_in_bytes
-rw-r--r-- 1 bork root 0 Okt 10 23:14 memory.kmem.max_usage_in_bytes
哦, 最大使用的 bytes 数 (max_usage_in_bytes)! 好的, 我们尝试一下! 10 MB 应该够任何人用了! 10 MB 应该够任何人用了!
$ sudo echo 10000000 > /sys/fs/cgroup/memory/mycoolgroup/memory.kmem.limit_in_bytes
太棒了, 让我们尝试用用我们的 cgroup!
$ sudo cgexec -g memory:mycoolgroup bash
我执行了一些命令, 都正常运行了. 然后我尝试编译一个 Rust 程序 :) :) :)
$ root@kiwi:~/work/ruby-stacktrace# cargo build
error: Could not execute process `rustc -vV` (never executed)
Caused by:
Cannot allocate memory (os error 12)
完美! 我们成功限制了我们的程序的内存使用量!
seccomp-bpf
Okay, 最后一个特性! 你在隔离进程的时候, 除了希望限制它们的内存和 CPU 使用, 可能还会想要限制它们能进行的系统调用! 像是 "你不可以连接到网络!". 这对安全应该有所帮助! 我们喜欢安全的东西.
这时我们就要用到 seccomp-bpf 了, 它是一个 Linux 内核特性, 允许你指定进程可以运行的系统调用.
什么是容器?
Okay, 现在你已经见识了这两个特性, 你可能会说 "wow, 太好了, 我可以根据这些特性写一些脚本然后实现一些很酷的功能!" 这样的功能是十分轻量的, 而且进程们可以在彼此间隔离开来, wow!
过去也有人是这么想的! 所以他们利用这些特性做了一个叫做 "Docker 容器" 的东西 :). 这就是 Docker! 诚然 Docker 如今已经有了很多的特性, 但是它们的大多数都是从最基本的 Linux 内核基础上构建起来的.