摘要:docker与传统虚拟机的区别在于隔离级别,传统虚拟机是在系统之上运行一个新系统,隔离级别是系统级别的,docker使用回原来系统的内核,只是进程与宿主主机隔离了。如图:
docker:
虚拟主机:
那docker是如何工作的呢?我们从三个方面着手去解释其中的运行机制。
- 进程是如何隔离的?
- 如何限制进程使用的资源?
- 数据/环境是怎么存储的?
进程隔离
我们都知道docker里的进程 和外界的进程是一致的,你无论是在容器里还是在宿主主机,在使用top命令查询进程列表,都可以查到这个容器PID相同,docker容器实质就是物理机的一个进程,至于容器里的进程会被隔离是因为linux本身的机制Namespace,在创建容器进程时,指定了这个进程所需要启用的一组 Namespace 参数。这样,容器就只能“看”到当前 Namespace 所限定的资源、文件、设备、状态,或者配置。而对于宿主机以及其他不相关的程序,它就完全看不到了,Namespace 技术实际上修改了应用进程看待整个计算机“视图”,即它的“视线”被操作系统做了限制,只能“看到”某些指定的内容
基于 Linux Namespace 的隔离机制相比于虚拟化技术也有很多不足之处,其中最主要的问题就是:隔离得不彻底
在 Linux 内核中,有很多资源和对象是不能被 Namespace 化的,最典型的例子就是:时间,你在容器内的时间和宿主主机的时间是一致的,要改大家一起改,以及你所用的系统采用不同版本的linux内核也不一定兼容,不过这些微小的缺陷丝毫不影响docker发光发热
限制资源
如上述所说,容器是一种特殊的存在宿主主机的进程,这就意味着他共享着宿主主机的cpu,内存,io等资源,如果不加限制会导致容器例如突然暂CPU 100%的各种情况,docker作为软件是无法限制系统资源的,无非也是通过Linux提供的Cgroups文件组,linux会根据该文件组下的限制类型所配置的PID进行资源的限制
/sys/fs/cgroup/cpu
/sys/fs/cgroup/memory
/sys/fs/cgroup/...
在docker上我们只要执行:
docker run --memory 2048m ... //限制内存 2048
数据/环境存储
就算有Namespace 的作用是“隔离”,但无论系统进程或者子进程,执行shell命令的rootfs(根文件系统)都是同一个,而通过pivot_root或chroot命令指定就可以改变运行指令的根目录,如果你没挂载出来,docker就默认把你的根目录挂载到和容器的同一个文件里,你是不可见的,打包镜像时候也顺带一起打包了,如果你通过 -v 挂载出来,那么你挂载出来的数据就独立到宿主主机,你可以直接在宿主主机访问到该文件,当然打包时候是不会把你这些独立出来的文件一起打包的。
rootfs 只是一个操作系统所包含的文件、配置和目录,并不包括操作系统内核。在 Linux 操作系统中,这两部分是分开存放的,操作系统只有在开机启动时才会加载指定版本的内核镜像。而我们装的各种运行环境其实是Linux内核的一种扩展,这种扩展也是开机启动时候加载然后创建一个属于自己的rootfs。
这么说就意味着每种语言环境的每个版本都有属于自己的rootfs,比如.net 和java他们的环境不一致,那么rootfs也不一致,这时你可能已经发现了另一个非常棘手的问题:难道我每开发一个应用,或者改造一下现有的应用,都要重复制作一次 rootfs (就是配置环境变量)吗?
我个人觉得rootfs和git原理大同小异!
用户制作镜像的每一步操作,都会生成一个层,也就是一个增量,也就是说你基于一个镜像创建的容器进行的所有操作都只是在这个增量层去处理,删除其实是遮罩上一层的内容,修改是先遮罩在创建新的。