Skip to main content

namespace 观察和实践篇

《5分钟Docker容器原理 | 开篇》 提到,Docker容器的隔离性是通过 Linux namespace 技术来实现的,本篇详细介绍下这个技术,并以一个Docker容器作为切入点,验证和实践下Linux namespace的隔离性。

Linux namespace 的类型

名称隔离对象描述内核版本
MNT namespaceMount points提供磁盘挂载点和文件系统的隔离2.4.19
IPC namespaceSystem V IPC, POSIX message queues提供进程间通信的隔离2.6.19
UTS namespaceHostname and NIS domain name提供主机名和域名的隔离2.6.19
PID namespaceProcess IDs提供进程的隔离2.6.24
Net namespaceNetwork devices, stacks, ports, etc.提供网络的隔离2.6.29
User namespaceUser and group IDs提供用户和权限的隔离3.8

Docker容器本质上是一个进程,为了在分布式的环境下进行通信和定位,Docker容器必须拥有自己独立的IP、端口、路由,这个时候就需要用到Net namespace提供的网络隔离能力。网络通信需要隔离,进程间通信也需要隔离,所以需要用IPC namespace。还有用户权限、用户组权限,也需要和宿主机区分开,就用到了User namespace。容器里面的进程也拥有自己的PID,这个是PID namespace起到的隔离作用......
所以,Docker容器本质上是一个带有不同的namespace参数的进程,正是这些namespace的存在, 容器内部就只能看到自己当前namespace限定的资源、文件、设备、状态、配置等。
上面这几种namespace类型,我们可以通过指令unshare --help来查看。

$ 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进入容器

$ 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

/ # 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相关进程

$ 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进程。如下

$ 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
接下来我们在宿主机打印出相关进程

$ 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的说明文档,继续做下相关的测试和验证。