深入剖析Kubernetes系列连载(三)隔离技术Namespace


Kubernetes的学习往往让人摸不着头脑,很难理解其中的原理。

深入剖析Kubernetes系列连载是学习《深入剖析Kubernetes》课程的笔记和总结,记录学习的过程,并且传递知识。

PID Namespace

int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);

clone()是线程操作,但linux 的线程是用进程实现的

这时,新创建的这个进程将会“看到”一个全新的进程空间,在这个进程空间里,它的 PID是 1。之所以说“看到”,是因为这只是一个“障眼法”,在宿主机真实的进程空间里,这个进程的 PID 还是真实的数值,比如 100

还有Mount、UTS、IPC、Network 和 User 这些Namespace,用来对各种不同的进程上下文进行“障眼法”操作

Mount Namespace

int container_main(void* arg)
{
     printf("Container - inside the container!\n");
     // 如果你的机器的根目录的挂载类型是 shared,那必须先重新挂载根目录
     // mount("", "/", NULL, MS_PRIVATE, "");
     mount("none", "/tmp", "tmpfs", 0, "");
     execv(container_args[0], container_args);
     printf("Something's wrong!\n");
     return 1;
}

在容器进程启动之前,加上了一句 mount(“none”, “/tmp”, “tmpfs”, 0, “”) 语句。就这样,我告诉了容器以 tmpfs(内存盘)格式,重新挂载了 /tmp 目录。

这就是 Mount Namespace 跟其他 Namespace 的使用略有不同的地方:它对容器进程视图的改变,一定是伴随着挂载操作(mount)才能生效

Mount Namespace 正是基于对 chroot 的不断改良才被发明出来的,它也是Linux 操作系统里的第一个 Namespace

为了能够让容器的这个根目录看起来更“真实”,我们一般会在这个容器的根目录下挂载一个完整操作系统的文件系统,比如 Ubuntu16.04 的 ISO。这样,在容器启动之后,我们在容器里通过执行 "ls /" 查看根目录下的内容,就是 Ubuntu 16.04 的所有目录和文件

而这个挂载在容器根目录上、用来为容器进程提供隔离后执行环境的文件系统,就是所谓的“容器镜像”。它还有一个更为专业的名字,叫作:rootfs(根文件系统)

由于 rootfs 里打包的不只是应用,而是整个操作系统的文件和目录,也就意味着,应用以及它运行所需要的所有依赖,都被封装在了一起。这种深入到操作系统级别的运行环境一致性,打通了应用在本地开发和远端执行环境之间难以逾越的鸿沟

Network Namespace

通过 open() 系统调用打开了指定的 Namespace 文件,并把这个文件的描述符 fd 交给 setns() 使用。在 setns() 执行后,当前进程就加入了这个文件对应的 Linux Namespace 当中了

Docker 还专门提供了一个参数,可以让你启动一个容器并“加入”到另一个容器的Network Namespace 里,这个参数就是 -net,比如:

docker run -it --net container:4ddf4638572d busybox ifconfig

我们新启动的这个容器,就会直接加入到 ID=4ddf4638572d 的容器的 Network Namespace 中

而如果我指定–net=host,就意味着这个容器不会为进程启用 Network Namespace。这就意味着,这个容器拆除了 Network Namespace 的“隔离墙”,所以,它会和宿主机上的其他普通进程一样,直接共享宿主机的网络栈。这就为容器直接操作和使用宿主机网络提供了一个渠道

实际上是在创建容器进程时,指定了这个进程所需要启用的一组 Namespace 参数。Namespace 技术实际上修改了应用进程看待整个计算机“视图”,这样容器就只能“看”到当前 Namespace所限定的资源、文件、设备、状态,或者配置。而对于宿主机以及其他不相关的程序,它就完全看不到了

容器化后的用户应用,却依然还是一个宿主机上的普通进程,这就意味着这些因为虚拟化而带来的性能损耗都是不存在的;而另一方面,使用 Namespace 作为隔离手段的容器并不需要单独的 Guest OS,这就使得容器额外的资源占用几乎可以忽略不计

基于 Linux Namespace 的隔离机制相比于虚拟化技术也有很多不足之处,其中最主要的问题就是:隔离得不彻底

首先,既然容器只是运行在宿主机上的一种特殊的进程,那么多个容器之间使用的就还是同一个宿主机的操作系统内核

其次,在 Linux 内核中,有很多资源和对象是不能被 Namespace 化的,最典型的例子就是:时间。如果你的容器中的程序使用 settimeofday(2) 系统调用修改了时间,整个宿主机的时间都会被随之修改

尽管在实践中我们确实可以使用 Seccomp 等技术,对容器内部发起的所有系统调用进行过滤和甄别来进行安全加固,但这种方法因为多了一层对系统调用的过滤,一定会拖累容器的性能

(完)