5分钟Docker容器原理 | namespace 观察和实践篇

517 阅读4分钟

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