什么是容器?

所谓容器,其实就像上图:
把 VR 玩家放在一个其以为是家的地方(chroot),创造目标所需环境(namespaces),再定期定量的提供水、食物(cgroups)供其维续生命。
chroot、namespace、cgroups,就是容器的核心技术。
本文将使用几个有限的命令,用 15 分钟的时间,让读者直观的了解容器。
chroot
chroot 比较简单,不演示了。
namespaces
namespaces,wikipedia 定义: Namespaces are a feature of the Linux kernel that partitions kernel resources such that one set of processes sees one set of resources while another set of processes sees a different set of resources.
文档见 namespaces
不同的进程,可以处于不同的 namespaces 中,这样它们就被隔离了。
namespaces 从类型来讲,分为 6 种,分别用于不同场景。这 6 种 namespaces 分别是:
| 名称 |
宏定义 |
隔离内容 |
| Mount namespaces |
CLONE_NEWNS |
Mount points |
| UTS namespaces |
CLONE_NEWUTS |
Hostname and NIS domain name |
| IPC namespaces |
CLONE_NEWIPC |
System V IPC, POSIX message queues |
| PID namespaces |
CLONE_NEWPID |
Process IDs |
| Network namespaces |
CLONE_NEWNET |
Network devices, stacks, ports, etc. |
| User namespaces |
CLONE_NEWUSER |
User and group IDs |
uts namespaces
我们先从最简单的 uts namespaces开始。
下面开始我们的第一个目标:隔离hostname。
- 查看当前 namespaces 的 hostname 和当前进程所在的 namespaces:
1
2
3
4
|
[root@outside ~]# hostname
outside
[root@outside ~]# readlink /proc/$$/ns/uts
uts:[4026531838]
|
- 解除 namespaces share, 创建新的进程:
1
2
3
4
5
6
7
|
[root@outside ~]# unshare --uts /bin/sh
sh-4.2# hostname inside
sh-4.2# hostname
inside
sh-4.2# readlink /proc/$$/ns/uts
uts:[4026532328]
sh-4.2# exit
|
以上可看到,新的 hostname 在内部已经生效,并且内部进程的 uts namespaces 已发生变化(4026531838 -> 4026532328)。
- 验证外部的 namespaces,其 hostname 未受到影响:
1
2
|
[root@outside ~]# hostname
outside
|
那是如何实现的?下例说明:
1
2
3
4
|
[root@outside ~]# strace -fe unshare,execve unshare --uts /bin/sh
execve("/usr/bin/unshare", ["unshare", "--uts", "/bin/sh"], [/* 27 vars */]) = 0
unshare(CLONE_NEWUTS) = 0 # <-- 关键点
execve("/bin/sh", ["/bin/sh"], [/* 27 vars */]) = 0
|
对于unshare(2), 请参考 man page
pid namespaces
如果只是修改 hostname 而不影响宿主,那也没什么意思。下面我们来看看如何隔离pid 资源。
实现目标:在新的进程中看到全新的一套 pids。
1
2
3
4
5
|
[root@outside ~]# unshare --fork --pid --mount-proc /bin/sh
sh-4.2# ps
PID TTY TIME CMD
1 pts/5 00:00:00 sh
2 pts/5 00:00:00 ps
|
如此,我们就在新的 namespaces 中,隔离了 pids,在这个容器内部看来,就是全新的一套 pids(第一个 pid 为 1)。
如何实现的呢?仍使用strace验证:
1
2
3
4
5
6
7
|
[root@outside ~]# strace -fe unshare,execve,mount unshare --fork --pid --mount-proc /bin/sh
execve("/usr/bin/unshare", ["unshare", "--fork", "--pid", "--mount-proc", "/bin/sh"], [/* 27 vars */]) = 0
unshare(CLONE_NEWNS|CLONE_NEWPID) = 0 # <-- 关键点
Process 97554 attached
[pid 97554] mount("none", "/proc", NULL, MS_REC|MS_PRIVATE, NULL) = 0
[pid 97554] mount("proc", "/proc", "proc", MS_NOSUID|MS_NODEV|MS_NOEXEC, NULL) = 0
[pid 97554] execve("/bin/sh", ["/bin/sh"], [/* 27 vars */]) = 0
|
cgroups
cgroups,wikipedia 定义: cgroups is a Linux kernel feature that limits, accounts for, and isolates the resource usage (CPU, memory, disk I/O, network, etc.) of a collection of processes
我们只需要知道 cgroups,能限制资源就行了。下面我们来直观感受一下 cgroup 的功能。
限制目标: 进程数
新启动一个sh:
1
2
3
|
[root@outside ~]# sh
sh-4.2# echo $$
14838 # <-- 记住这个 pid
|
先挂载 cgroups(pids子系统):
1
2
|
[root@outside ~]# mkdir -p cgroup/pids
[root@outside ~]# mount -t cgroup -o pids pids ./cgroup/pids
|
配置 cgroups:
1
2
3
4
|
[root@outside ~]# cd ./cgroup/pids/
[root@outside pids]# mkdir x
[root@outside pids]# cd x
[root@outside x]# echo 3 > pids.max # <-- 限制只能启动 3 个进程
|
将上面的 sh 加入到我们新建立的 cgroups 中(默认也影响其子进程):
1
|
[root@outside x]# echo 14838 > cgroup.procs
|
下面来看看效果:
1
2
3
4
5
6
7
8
9
10
11
12
|
sh-4.2# pstree -p $$
sh(14838)───pstree(15421)
sh-4.2# sleep 100 & # <-- 启动第 1 个进程
[1] 15427
sh-4.2# sleep 100 & # <-- 启动第 2 个进程
[2] 15429
sh-4.2# sleep 100 & # <-- 第 3 个进程启动失败, 因为最开始的 sh 进程也算
sh: fork: retry: Resource temporarily unavailable
sh: fork: retry: Resource temporarily unavailable
sh: fork: retry: Resource temporarily unavailable
sh: fork: retry: Resource temporarily unavailable
sh: fork: Resource temporarily unavailable
|
可以查看一下当前的pids数量:
1
2
|
[root@outside x]# cat pids.current
3 # <-- 的确是 3 个
|
总结
以上使用unshare命令与cgroups,初步了解了容器的几个基本技术。
关于容器更深入的内容,有待读者自己去学习。
玩得开心 :)
-EOF-