Docker是一个开源的应用容器引擎,是近些年最火的技术之一,Docker公司从Docker项目开源之后发家致富把公司商标改为了Docker,收购了fit项目,整合为了docker-compose,前景一片大好,但是据说Docker在社区中话语权过于强硬,得罪了不少公司,google与rethub等牵头发起了kubernetes项目,虽说让Docker在市场上损失很大,但为相信Docker未来前景会很好,k8s虽然强大,但主流也是采用了Docker的容器规范,只会更好。

Docker创建一个容器的方式很简单,使用docker run命令。

docker run -it busybox /bin/sh

-it 参数告诉了Docker项目在启动容器后,需要给我们分配一个文本输入输出环境,也就是TTY,跟容器的标准输入相关联,这样我们就可以和这个Docker容器进行交互了。

/bin/sh 就是我们要在 Docker 容器里运行的程序。

所以,上面这条指令翻译过来就是:请帮我启动一个容器,在容器里执行/bin/sh,并且给我分配一个命令行终端跟这个容器交互。

在这之前,我们先看看宿主机存在的进程。

image.png

可以看到当前管理员下sudo的PID为4987,在此基础上我们bash的PID为4988,bash中执行了ps命令,PID与PPID的关系很明显。

此时运行我们的docker容器。

在容器中查看该容器中的进程,出去这个ps,只有一个PID为1的/bin/sh进程。

image.png

再来从我们的宿主机查看一下进程信息,排除掉sudo,bash和ps的进程,只有一个sh,可以与容器中的/bin/sh容器作联系,可以看到他的进程PID是7355,父进程是7332。

image.png

找到PPID为7332的这个进程,可以知道是一个叫containerd-shim的进程创建了该容器。

image.png

先不考虑containerd-shim进程的事,你应该已经注意到容器是什么了,其实就是一个“特殊”的进程。

特殊在何处?

我们宿主机ps查看进程是可以看到容器进程的,而在容器中使用ps命令查看却只能看到容器他本身,一个PID为1的进程,PID为1在Linux有着特殊的意义。

PID为0的进程是idle进程,是由系统自动创建,运行在内核态,创建第一个用户进程,也就是PID为1的init进程,init进程是linux系统中其它所有用户进程的祖先进程,主要作用是处理僵尸进程。

当某个父进程比子进程提前消亡时,父进程会给子进程重新寻找“养父进程”,一般就是init进程,由init进程负责处理该子进程的消亡。

在容器中的进程居然是一个PID为1的进程,他可以管理他所能看到的进程生死,而看不到外面的宿主机进程,一叶障目,不见泰山。

这时候引入一张Docker商标图片最为合适了。

image.png

鲸鱼背上的一个个集装箱就是所谓的容器,完全封闭起来,我们可以看到它,在它里面却不知道我们。

这就是Docker的容器封闭技术了。

他的实现其实很简单,Linux的进程创建函数clone,可以接受一个参数CLONE_NEWPID,这样创建的这个进程将会“看到”一个全新的进程空间,在这个进程空间里,它的PID是1。之所以说“看到”,是因为这只是一个“障眼法”,在宿主机真实的进程空间里,这个进程的 PID 还是真实的数值。

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

这种技术叫做NameSpace,上面说的是PID NameSpace,当然Linux其他的东西也可以有NameSpace,比如说Mount、UTS、IPC、Network和User。

Mount NameSpace可以让被隔离进程只看到当前Namespace里的挂载点信息, Network Namespace,用于让被隔离进程看到当前Namespace里的网络设备和配置。

综上,可以发现容器其实就是一个带了NameSpace参数特殊创建的进程。

这暴露出了几个问题,第一是所有容器都是共用了同一个宿主机的操作系统内核,第二是如果我们在低版本的Linux上使用高版本的容器,是会出现问题的,还有第三是这种NameSpace的隔离并不如虚拟化技术隔离的彻底。

Linux中其实有很多资源都是无法做到隔离的,比如说时间,如果你的容器中尝试使用修改时间的系统调用,就会影响到宿主机的时间。

所以说,Docker的容器其实还是有未知的隐患存在。

容器虽然觉得自己是PID为1的init进程,但是对于我们宿主机来说,他是一个正常的进程,和其它进程一样,竞争着系统中的资源。

为此,Linux内核诞生了一个新特性,Linux Cgroups,可以为进程设置资源限制指标。

Linux Control Group,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。

Cgroups的使用接口是利用文件系统开放的,你可以在这个目录下发现很多以资源命名的文件。

image.png

image.png

在创建容器的时候你可以指定资源分配的参数,如下。

docker run -it --cpu-period=100000 --cpu-quota=20000 busybox /bin/sh

使用该命令创建运行容器之后,可以看到运行的容器id为f0c0f3e5d0d2。

image.png

image.png

此时你可以在之前所说的sys/fs/cgroup下找到一个docker目录,其中就有一个f0c0f3e5d0d2开头的目录。

image.png

在这个目录中就可以找到容器f0c0f3e5d0d2的资源分配参数,提供给Cgroups,来为Docker容器分配资源。

image.png

还有一个重要地方,是关于容器本身的文件系统,当然和PID NameSpace一样,是利用Mount NameSpace实现的,独立出一片独立空间的文件系统。

虽说是这样,但有一点细节需要知道,Mount NameSpace创建一片独立空间时是会继承宿主机本身的已挂载空间的,所以在创建之后需要重新挂载所有节点。

此处细节,可以看左耳朵耗子的这篇博客,非常详细。

https://coolshell.cn/articles/17010.html