27 Sep 2018 11:25 +0000

翻译自: 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 内核基础上构建起来的.


Loading comments...