先下一个定义,Docker 容器本质上是一个进程,一个被 Namespace 修改进程视图、被 Cgroup 限制资源的特殊进程。通过 Namespace 使容器只能看到所限定的资源,通过 Cgroup 限制容器使用的资源大小,从而实现进程之间的隔离。而传统的虚拟机,则通过硬件虚拟化功能模拟出运行操作系统所需的各种硬件,比如 CPU、内存、I/O 设备等,并在这些虚拟的硬件上安装客户操作系统,再在新创建的操作系统中运行进程,从而实现进程间的隔离和资源限制。因此,Docker 容器相比于虚拟机的最大区别是,使用 Docker 时,宿主机中并没有一个独立的 “Docker 容器” 实体在运行,Docker 帮助用户启动的还是原来的应用进程,只不过在创建这些进程时,为它们添加了各种各样的 Namespace 参数和 Cgroup 参数。与之不同,在使用虚拟机的时候,会真真正正有一个客户操作系统运行在宿主机上。
我们可以通过以下命令进行一个简单的验证:
在宿主机上查看nginx容器对应的PID,先查询nginx容器在宿主机上的进程信息。再进入容器内部,执行ps -ef 查看进程信息,此时,我们发现进程数量是对应的。(如果容器内没有ps命令,通过 apt-get update && apt-get install -y procps 进行安装。)

docke相比于虚拟机的优点在于,用户在容器中创建的应用进程,跟宿主机上的其他进程一样,都由宿主机操作系统统一进程管理。而虚拟化技术则需要Hypervisor来负责创建虚拟机,而这个虚拟机是真实存在的,并且它里面必须运行一个完整的客户操作系统来执行用户的应用进程,这就不可避免地带来额外的资源占用和性能损耗。
不过,有利就有弊,基于Namespace机制相比于虚拟化技术存在很多不足之处,其中,一个最重要的问题就是隔离得不彻底。首先,既然容器是宿主机上运行的一种特殊进程,那么多个容器之间还是使用的同一个宿主机的操作系统内核,这就导致了我们不能够在Windows操作系统上运行Linux容器,在低版本的Linux宿主机上运行高版本的Linux容器。其次,在Linux中很多资源和对象是不能够被Namespace化的,其中比较典型的就是时间和一些内核核心参数(/proc/sys/kernel/下大部分项,如 pid_max、sysrq 开关)。此外,容器相对于虚拟机来说暴露的攻击面更大,容器应用篡改宿主机的难度也比虚拟机低得多。
下面,举一个容器视图的例子来说明容器隔离不彻底的情况。
我们简单运行一个容器,在宿主机和容器内部同时执行top命令,按下1。我们发现在容器内部看到CPU和内存信息和宿主机上展示的信息一致。

接下来,我们在容器启动的时候,对容器的CPU和内存占用进行限制。
docker run -d --name my-nginx --cpus 1.5 --memory 500m -p 80:80 nginx:latest

验证容器的资源限制是否生效,资源限制已经生效CPU=1.5,memory=500MB

我们发现,即使对容器的CPU和内存进行了限制,看到的视图仍然是宿主机上的。这对于类似Go语言这样的程序来说,能够看到的CPU数量为16,但是真正可以被调度的为1.5,容易引起协程调度之间的竞争。
总结
docker容器的本质就是一个进程,相比于虚拟机来说更加的轻量化,占用的资源更小,系统调用的开销更低。也正是由于docker容器本质上是进程,导致隔离不彻底的弊端。实现docker资源隔离和资源限制的核心技术是Namespace和Cgroup。
759

被折叠的 条评论
为什么被折叠?



