容器和虚拟机的区别
传统虚拟机
运行应用程序所需的一切都包含在虚拟机里–虚拟化的硬件,操作系统以及任何所需的二进制文件和库。因此,虚拟机具有自己独立的基础架构。
- 优点
- 虚拟机彼此独立隔离;
- 虚拟机与宿主机操作系统隔离,是进行实验和开发应用程序的安全场所。
- 减少在服务器设备上的支出;
- 缺点
- 占用主机的大量系统资源。在虚拟机上运行程序需要启动Guest OS以及相关所有硬件的虚拟副本。
- 应用迁移成本高,与操作系统绑定。
容器
容器是一个不依赖于操作系统,运行应用程序的环境。它通过Linux的chroot、Namespaces和Cgroups技术对应用程序进程进行隔离和限制的。从而达到进程级的隔离,是运行在宿主机上的一种特殊的进程,多个容器之间使用的还是同一个宿主机的操作系统内核。
- AUFS(chroot) – 用来建立不同的操作系统和隔离运行时的硬盘空间,将子目录变成根目录,达到视图级别的隔离;进程在 chroot 的帮助下可以具有独立的文件系统,对于这样的文件系统进行增删改查不会影响到其他进程;
- Namespace – 用来隔离容器的执行空间:使用 Namespace 技术来实现进程在资源的视图上进行隔离。
- Cgroup – 分配不同的硬件资源:为了减少进程彼此之间的影响,可以通过 Cgroup 来限制其资源使用率,设置其能够使用的 CPU 以及内存量。
Docker 利用 Linux 提供的资源隔离机制创建,为该进程创建隔离的内容有:
- 文件系统(设置为镜像)
- 网络空间
- 进程空间(也就是 PCB 列表)
- 等等
"单进程"模型
容器的"单进程模型",并不是指容器里只能运行"一个"进程,而是指容器没有回收孤儿进程的能力,从而导致成为僵尸进程。本质其实是容器中的 1 号进程并不具有管理多进程、多线程等复杂场景下的能力。
这是因为容器里的主进程(PID=1 的进程)就是应用本身(即创建容器时(docker run)指定的命令所对应的进程),其他的进程都是这个主进程的子进程。Docker只能识别主进程的状态,如果主进程正常,Docker的状态就是Running。所以在容器里不推荐跑多个进程。
运行容器前:
运行了两个容器:
fzz@ubuntu:~$ docker top 0da69d02ca05
UID PID PPID C STIME TTY TIME CMD
root 5710 5681 0 21:52 pts/0 00:00:00 bash
root 5916 5681 0 22:03 pts/1 00:00:00 /bin/bash
fzz@ubuntu:~$ ps -ajx // 截取了部分
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
1 1718 1718 1718 ? -1 Ssl 0 0:08 /usr/bin/dockerd -H unix://
3165 3228 3228 3228 pts/19 5894 Ss 1000 0:00 /bin/bash
3165 4780 4780 4780 pts/2 5652 Ss 1000 0:00 /bin/bash
4780 5652 5652 4780 pts/2 5652 Sl+ 1000 0:00 docker run --name test2 -it ubuntu bash
1 1278 1278 1278 ? -1 Ssl 0 0:05 /usr/bin/containerd
1278 5681 5681 1278 ? -1 Sl 0 0:00 containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/
5681 5710 5710 5710 pts/0 5710 Ss+ 0 0:00 bash
5681 5916 5916 5916 pts/1 5916 Ss+ 0 0:00 /bin/bash
3228 5894 5894 3228 pts/19 5894 Sl+ 1000 0:00 docker exec -it 0da69d02ca05 /bin/bash
fzz@ubuntu:~$
其中:
- docker run对应的容器宿主机进程:containerd-shim,对应的容器内进程是PID为1的进程。
- containerd-shim进程又是由containerd进程所生成。
- docker容器内的进程也会在宿主机中呈现出来,加入到宿主机的PCB列表中参与调度(因为内核是共享的啊)
- docker top [容器id] 所看到的活动进程均是宿主机上的进程ID。
查看某一个容器内的进程:
fzz@ubuntu:~$ docker exec -it 0da69d02ca05 /bin/bash
root@0da69d02ca05:/# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 13:52 pts/0 00:00:00 bash // docker run 所创建
root 11 0 0 14:03 pts/1 00:00:00 /bin/bash // docker exe 所创建
root 18 11 0 14:03 pts/1 00:00:00 ps -ef // 容器内部执行进程
root@0da69d02ca05:/#
Docker Engine
Docker Engine经过了重构,被拆分成了dockerd,docker-containerd,docker-containerd-shim以及docker-runc四部分,每个容器的1号进程被一个docker-containerd-shim进程管理。
通过上述实验可以看出,docker的引擎使用的是moby容器管理引擎。
moby daemon 会对上提供有关于容器、镜像、网络以及 Volume的管理。
moby daemon 所依赖的最重要的组件就是 containerd,containerd 是一个容器运行时管理引擎,其独立于 moby daemon ,可以对上提供容器、镜像的相关管理。
containerd 底层有 containerd shim 模块,其类似于一个守护进程,这样设计的原因有几点:
- containerd 需要管理容器生命周期,而容器可能是由不同的容器运行时(runC kata gVisor)所创建出来的,因此需要提供一个灵活的插件化管理。而 shim 就是针对于不同的容器运行时所开发的,这样就能够从 containerd 中脱离出来,通过插件的形式进行管理。
- 因为 shim 插件化的实现,使其能够被 containerd 动态接管。如果不具备这样的能力,当 moby daemon 或者 containerd daemon 意外退出的时候,容器就没人管理了,那么它也会随之消失、退出,这样就会影响到应用的运行。
- 因为随时可能会对 moby 或者 containerd 进行升级,如果不提供 shim 机制,那么就无法做到原地升级,也无法做到不影响业务的升级,因此 containerd shim 非常重要,它实现了动态接管的能力。