最近也用了不少docker了,还是蛮好奇容器的原理的。
1. docker隔离与边界
1.1 进程
程序,即数据和代码本身的二进制文件,执行程序时,它就从磁盘上的二进制文件,变成了计算机内存中的数据、寄存器里的值、堆栈中的指令、被打开的文件,以及各种设备的状态信息的一个集合,这个一个计算机执行环境的总和,被称为进程。
1.2 容器
容器技术的核心功能,就是通过约束和修改进程的动态表现,从而为其创造出一个“边界”。Cgroups 技术是用来制造约束的主要手段,而 Namespace 技术则是用来修改进程视图的主要方法。
docker run -it busybox /bin/sh
当宿主机上运行了一个 /bin/sh 程序,操作系统都会给它分配一个进程编号,比如 PID=100。
而当通过 Docker 把这个 /bin/sh 程序运行在一个容器当中。这时候,Docker 就会在这个第 100 号员工入职时给他施一个“障眼法”,让他永远看不到前面的其他 99 个员工。
这种机制,其实就是对被隔离应用的进程空间做了手脚,使得这些进程只能看到重新计算过的进程编号,比如 PID=1。可实际上,他们在宿主机的操作系统里,还是原来的第 100 号进程。即,Linux 里面的 Namespace 机制,对各种不同的进程上下文进行“障眼法”操作。
容器,其实是一种特殊的进程。用户运行在容器里的应用进程,跟宿主机上的其他进程一样,都由宿主机操作系统统一管理,只不过这些被隔离的进程拥有额外设置过的 Namespace 参数。
尽管可以在容器里通过 Mount Namespace 单独挂载其他不同版本的操作系统文件,比如 CentOS 或者 Ubuntu,但这并不能改变共享宿主机内核的事实。这意味着,如果在 Windows 宿主机上运行 Linux 容器,或者在低版本的 Linux 宿主机上运行高版本的 Linux 容器,都是行不通的。
1.3 Cgroups
既然容器只是宿主机上的一个特殊进程,那么该进程和其它进程之间是平等,宿主机的资源可能被其它进程占用,这显然是不符合预期要求的。
Linux Control Group。它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。此外,Cgroups 还能够对进程进行优先级设置、审计,以及将进程挂起和恢复等操作。
Cgroups 给用户暴露出来的操作接口是文件系统,即它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。容器起起来之后,可以在宿主机上查到Cgroups对容器的资源配置文件。
一个相关的问题:
在容器里执行 top 指令,就会发现,它显示的信息是宿主机的 CPU 和内存数据,而不是当前容器的数据。因为top指令的数据来源是宿主机的/proc 目录。
一个解决方案:lxcfs(没有实践过)
top 是从 /prof/stats 目录下获取数据,所以道理上来讲,容器不挂载宿主机的该目录就可以了。lxcfs就是来实现这个功能的,做法是把宿主机的 /var/lib/lxcfs/proc/memoinfo 文件挂载到Docker容器的/proc/meminfo位置后。容器中进程读取相应文件内容时,LXCFS的FUSE实现会从容器对应的Cgroup中读取正确的内存限制。从而使得应用获得正确的资源约束设定。kubernetes环境下,也能用,以ds 方式运行 lxcfs ,自动给容器注入争取的 proc 信息。
理解1:
docker pull下来的基础镜像比如:centos 7,镜像里面没有内核信息,但是封装了systemd等进程管理工具,使得产生的新的进程都是一号进程的子进程,执行ping netstat命令,也会是这个容器一号进程的子进程。
1.4 Mount Namespace
文件挂载,其实有点难懂,我一直不太明白linux的文件系统,但是有一个实际的使用例子,当我启动一个容器后,cd到根目录下,能够看到一个和宿主机内容不一样的文件系统。
简单的解释就是,Mount Namespace把原宿主机上的某一目录,例如/home/test,做成了容器里的rootfs 根文件系统。
1.5 layer分层镜像
Docker 在镜像的设计中,引入了层(layer)的概念。也就是说,用户制作镜像的每一步操作,都会生成一个层,也就是一个增量 rootfs。
通过联合文件系统(Union File System),将多个不同位置的目录联合挂载(union mount)到同一个目录下。
通过该命令可以查看images的rootfs信息
docker image inspect docker.io/mysql:latest
AuFS 会在可读写层创建一个 whiteout 文件,把只读层里的文件“遮挡”起来。
(不过现在联合文件系统好像改成overlayFS)
上面的读写层通常也称为容器层,下面的只读层称为镜像层,所有的增删查改操作都只会作用在容器层,相同的文件上层会覆盖掉下层。知道这一点,就不难理解镜像文件的修改,比如修改一个文件的时候,首先会从上到下查找有没有这个文件,找到,就复制到容器层中,修改,修改的结果就会作用到下层的文件,这种方式也被称为copy-on-write。
1.6 docker exec
既然容器只是宿主机上一个特殊的进程,docker exec命令是如何进入容器内的呢?
首先采用如下命令查看一个容器,可以看到该进程所有 Namespace 对应的文件。一个进程,可以选择加入到某个进程已有的 Namespace 当中,从而达到“进入”这个进程所在容器的目的,这正是 docker exec 的实现原理。该操作依赖setns() 的 Linux 系统调用。
docker inspect --format '{{ .State.Pid }}' 2586bc6c334e
ls -l /proc/11564/ns
1.7 -v
宿主机和容器之间的文件挂载是通过Volume 机制,允许你将宿主机上指定的目录或者文件,挂载到容器里面进行读取和修改操作