Docker Namespace
May 3, 2019 16:15 · 1099 words · 3 minute read
容器实质上是一种沙盒技术。容器技术的核心功能,就是通过约束和修改进程的动态表现,从而为其创造出一个“边界”。
Namespace
Namespace(命名空间) 是 Linux 内核的一个特性,是 Linux 容器的基础,通过修改进程视图来提供了一层隔离。Docker 组合使用各种命名空间来完成容器所需的隔离,保持可移植性并避免影响主机系统的其它部分。
macOS 和 Windows 版 Docker 与宿主机之间还有一层轻量化的 Linux 虚拟机,这里需要纯 Linux 环境,推荐 Ubuntu 或者 CentOS。
首先我们创建一个容器:
$ docker run -it busybox /bin/sh
/ #
看一下容器内部的进程:
/ # ps
PID USER TIME COMMAND
1 root 0:00 /bin/sh
6 root 0:00 ps
1号进程就是启动容器时执行的 /bin/sh。
接着看一下宿主机上的进程:
$ ps
PID TTY TIME CMD
1892 pts/0 00:00:00 bash
8200 pts/0 00:00:00 sh
9747 pts/0 00:00:00 ps
有一个 PID 为 8200 的 sh 进程。
杀掉这个进程:
$ kill -9 8200
$ ps
PID TTY TIME CMD
1892 pts/0 00:00:00 bash
10579 pts/0 00:00:00 ps
再看一下 Docker 容器:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
$ docker container ls --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0fe869689cf1 busybox "/bin/sh" 18 minutes ago Exited (137) 3 minutes ago adoring_kepler
容器已经退出了,这说明了容器内的1号进程实际上就是宿主机上的8200号进程。但是 Docker 对应用的进程空间做了手脚,使得这些进程被重新分配了一个进程编号,而且没法看到不在这个进程空间中的其他应用程序。这就是命名空间。
那么如何使用命名空间呢?
在 Linux 系统中调用 clone()
创建进程:
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sched.h>
int clone(int (*fn)(void *), void *child_stack,
int flags, void *arg, ...
/* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );
clone() creates a new process, in a manner similar to fork(2). It is actually a library function layered on top of the underlying clone() system call, hereinafter referred to as sys_clone. A description of sys_clone is given toward the end of this page.
If CLONE_NEWPID is set, then create the process in a new PID namespace. If this flag is not set, then (as with fork(2)), the process is created in the same PID namespace as the calling process. This flag is intended for the implementation of containers.
https://linux.die.net/man/2/clone
指定 CLONE_NEWPID 参数:
int pid = clone(main_func, stack_size, CLONE_NEWPID | SIGCHLD, NULL);
这样新创建的进程有着一个全新的进程空间,在这个进程空间中,它的进程号为1。但是这个进程在宿主机进程空间中的 PID 才是真正的进程号,比如8200。
每一个容器,都用 PID Namespace 修饰过,而容器中的应用程序,都被分配了虚假的 PID,它们既看不到宿主机上真正的进程空间,也无法感知到其他命名空间,也就看不到其他命名空间中的进程了。
Linux 提供了各种命名空间来提供方方面面的“掩饰”,比如 Network Namespace 修改了被隔离进程看到的网络设备的视图,而 Mount Namespace 让被隔离进程只能看到当前命名空间中的挂载点信息。
命名空间种类:
- PID
- Mount
- IPC
- User
- Network
容器的本质只是一个加了限定参数的进程。
但是 Linux Namespace 的隔离机制最大的问题在于隔离不彻底,容器和宿主机共享同一个操作系统的内核。也正是因为距离内核如此之近,容器的性能和敏捷度远强于虚拟机。
尽管可以通过 Mount Namespace 单独挂载不同的操作系统文件,但并不能改变共享宿主机内核的事实,这意味着基于 Windows 的容器化应用在 Linux 上无法运行,甚至低版本的 Linux 宿主机上也不能运行高版本的 Linux 容器。
**还有很多的资源和对象无法被命名空间化。**所以容器给应用暴露的攻击面相当大,有一定的危险性。