在 《5分钟Docker容器原理 | 开篇》 提到,Docker容器 的隔离性是通过 Linux namespace 技术来实现的,本篇详细介绍下这个技术,并以一个Docker容器 作为切入点,验证和实践下Linux namespace的隔离性。
Linux namespace 的类型
名称 | 隔离对象 | 描述 | 内核版本 |
---|---|---|---|
MNT namespace | Mount points | 提供磁盘挂载点和文件系统的隔离 | 2.4.19 |
IPC namespace | System V IPC, POSIX message queues | 提供进程间通信的隔离 | 2.6.19 |
UTS namespace | Hostname and NIS domain name | 提供主机名和域名的隔离 | 2.6.19 |
PID namespace | Process IDs | 提供进程的隔离 | 2.6.24 |
Net namespace | Network devices, stacks, ports, etc. | 提供网络的隔离 | 2.6.29 |
User namespace | User and group IDs | 提供用户和权限的隔离 | 3.8 |
Docker容器 本质上是一个进程,为了在分布式的环境下进行通信和定位,Docker容器必须拥有自己独立的IP、端口、路由,这个时候就需要用到Net namespace 提供的网络隔离能力。网络通信需要隔离,进程间通信也需要隔离,所以需要用IPC namespace 。还有用户权限、用户组权限,也需要和宿主机区分开,就用到了User namespace 。容器里面的进程也拥有自己的PID,这个是PID namespace 起到的隔离作用......
所以,Docker容器 本质上是一个带有不同的namespace 参数的进程,正是这些namespace 的存在, 容器内部就只能看到自己当前namespace 限定的资源、文件、设备、状态、配置等。
上面这几种namespace 类型,我们可以通过指令unshare --help
来查看。
sql
$ uname -r
3.10.0-1160.el7.x86_64
$ unshare --help
Usage:
unshare [options] <program> [<argument>...]
Run a program with some namespaces unshared from the parent.
Options:
-m, --mount unshare mounts namespace
-u, --uts unshare UTS namespace (hostname etc)
-i, --ipc unshare System V IPC namespace
-n, --net unshare network namespace
-p, --pid unshare pid namespace
-U, --user unshare user namespace
-f, --fork fork before launching <program>
--mount-proc[=<dir>] mount proc filesystem first (implies --mount)
-r, --map-root-user map current user to root (implies --user)
--propagation <slave|shared|private|unchanged>
modify mount propagation in mount namespace
-s, --setgroups allow|deny control the setgroups syscall in user namespaces
-h, --help display this help and exit
-V, --version output version information and exit
可以看到,当前OS的内核版本是3.10.0, 目前unshare
支持的namespace 类型是6种,跟上面的表格是一致的。
大家如果想看到更具体的信息,可以使用man unshare
指令看看详细的文档。
在 《离线环境部署docker及私有镜像仓库》 中,我们启动了一个Docker容器,接下来我们来观察下这个容器,以PID namespace为例,来进行简单的验证和实践。
PID namespace的观察
首先我们找到并使用sh
进入容器
bash
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
98cb5c25871b registry:latest "/entrypoint.sh /etc..." 4 days ago Up 39 minutes 0.0.0.0:5000->5000/tcp, :::5000->5000/tcp private-registry
$ docker exec -it 98cb5c25871b sh
/ #
接下来我们打印当前容器运行的所有进程。可以看到容器内主进程registry
的PID为1
arduino
/ # ps -ef
PID USER TIME COMMAND
1 root 0:00 registry serve /etc/docker/registry/config.yml
11 root 0:00 sh
18 root 0:00 ps -ef
我们打开另外一个终端,在宿主机中查看docker相关进程
bash
$ ps -ef | grep docker
......
root 1917 1 0 07:26 ? 00:00:01 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 98cb5c25871b94420418094efb19f36c45c7fbe18274229e9f8c4b00328ec180 -address /var/run/docker/containerd/containerd.sock
root 1945 1917 0 07:26 pts/0 00:00:01 registry serve /etc/docker/registry/config.yml
......
由于显示的内容非常多,在这里我只截取了2个进程信息。可以看到registry
进程在宿主机中的PID是1945
,而且它的父进程ID正是容器进程的PID1917
。这正是PID namespace的作用。
PID namespace的实践
我们可以用unshare
指令来创建一个PID隔离的bash进程。如下
bash
$ sudo unshare --pid --fork --mount-proc /bin/bash
#
# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 08:16 pts/0 00:00:00 /bin/bash
root 10 1 0 08:16 pts/0 00:00:00 ps -ef
# sleep 1024 &
# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 08:16 pts/0 00:00:00 /bin/bash
root 13 1 0 08:22 pts/0 00:00:00 sleep 1024
root 14 1 0 08:22 pts/0 00:00:00 ps -ef
可以看到,我们的bash 进程,已经成为了1号进程,并且我们创建的sleep
静默进程,此时的父进程ID为1
接下来我们在宿主机打印出相关进程
perl
$ ps -ef | grep sleep
root 17301 15805 0 08:22 pts/0 00:00:00 sleep 1024
......
$ ps -ef | grep 15805
root 15805 15803 0 08:16 pts/0 00:00:00 /bin/bash
root 17301 15805 0 08:22 pts/0 00:00:00 sleep 1024
......
可以看到,宿主机中运行的bash
进程PID为15805
,而它的子进程sleep
,在宿主机中的PID为17301
。
以上就是对PID namespace 的观察与实践,大家如果对其他Linux namespace 也感兴趣,可以参考下unshare
的说明文档,继续做下相关的测试和验证。