📅 2024年10月28日
🏆 容器技术基础
容器本身的价值非常有限、真正有价值的是"容器编排"
❓ 容器到底是什么?
容器其实是一种沙盒技术,沙盒就是可以向一个集装箱一样把你的应用装进去,可以搬来搬去(移植能力)

⭐️对 Docker
以及各种容器技术来说,Cgroup
技术是用来制造约束的主要手段,而 Namespace
则是用来修改容器内进程的视图
Namespace
:
比如在宿主机上运行了一个 bash
,操作系统会给这个程序分配一个 PID
,假设这个 PID=100
,可以理解未它是这个公司的第 100
员工,前面还有 1-99
个员工,而如果是在容器中运行可以通过它的PID依然是100,但是在容器内可以通过一个障眼法来让它以为它是1号员工,那么它就永远看不到前面99个员工了,但是在宿主就中它还是100号进程;
Namespace
是 linux
在创建进程的一个可选参数,在linux中创建进程的系统调用是 clone
,在 clone
时如果指定一个 CLONE_NEWPID
,就会创建一个全新的进程空间,它在此空间内PID为1,Namespace
有很多种以上提到的时 PID Namespace
,除此之外还有 mount、UTS、IPC、Nework、User
等,比如 Mount Namespace
让此名称空间中的进程只看到当前空间的挂载信息
⭐️Namespace
实际上修改了应用进程看待整个计算机的"视图"的视野,既它的视线受到了操作系统的管控

容器技术和虚拟化对比:
- 虚拟化要比容器隔离更加安全,容器虽然有
namespace
作为隔离,但是隔离的并不彻底,依然是宿主机的某个进程,多个容器之间使用还是同一个宿主机的内核,相比之下虚拟化就安全多了,虚拟化是通过Hypervisor
负责创建,虽说还需要在宿主机中运行,但是虚拟机有单独的内核,在虚拟机中的应用并不会受到宿主机内核的影响 - 容器要比虚拟化更加的轻量,由于虚拟机需要通过
hypervisor
创建,并且虚拟机是有一套完整的系统的,这样资源占用难免较高,容器说明白还是运行在宿主机内的一个应用程序不存在虚拟化尝试的性能损耗,并且Namespace
时linux
本身就支持的功能,资源损耗几乎可以忽略不计
⚠️ 在
linux
中,有很多不可以被namespace
的资源,比如time
,如果你在容器中修改了实际,那么同时也会修改整个namespace
的时间,在容器中要考虑清楚,什么能做?什么不能做?所以容器向应用暴露的攻击面是非常大的,应用越狱的难度也比虚拟机大的多

Cgroup
:
为什么有了 namesapce
还要有一个 cgroup
?因为虽然给这个容器进行上了障眼法,但是它和宿主机中的进程资源竞争依旧是平等关系,也就是说它可能用光宿主机的所有资源。
Linux Cgroup
是专门为进程设置资源限制的一个功能,,它的作用是限制一个进程组能够使用的资源上限,包括 CPU
、内存、磁盘、网络
等,还可以对进程进行优先级设置、审计,以及将进程挂起恢复。

⭐️ 回到问题容器是什么? 容器就是启用了多个 Namesapce
的进程,并且通过 Cgroup
进行资源管理
⭐️ 还有一个重要概念是: 容器是一个单进程模型
由于容器本来就是一个进程,用户的应用进程实际上就是容器里面的PID=1的进程,也是其它进程候选创建的所有进程的父进程,这就意味着在一个容器中,无法同时创建两个不同的应用,除非可以事先找到一个公共PID=1的程序来充当两个不同应用的父进程。
⭕️ Cgroup
的不完善点: /proc
问题,此文件是用于查看 Linux
系统资源和进程的一个文件夹,在宿主机中使用top命令就可以看到整个宿主机的进程和资源使用情况,就是查看这个文件夹,但是如果在容器中使用top命令也是查看的宿主机的资源,造成这个问题的原因就是 /proc
并不知道用户通过 Cgroup
给这个容器做了怎样的资源限制,也就是它便立即 Cgroup
限制的存在
深入理解容器镜像
首先了解了解一下容器启动之后,我们是如何看到容器想让我们看到的东西的:
- 用户首先创建了一个容器,也就是通过了
clone
系统调用创建了一个系统进程,并且指定了要开启MOUNT Namespace
- 这是还只是启用了这个名称空间,但是
MOUNT Namespace
修改的只是容器对文件系统挂载点的认知,只有在挂载操作发生之后,进程的视图才会被改变,如果没有发生挂载,容器还依然是整个宿主机的各个挂载点,此时需要对容器的整个 ”/“ 目录进行重新挂载, - 如何将根目录重新挂载呢?在
linux
中有个chroot
的命令可以帮助change root file system
,将进程的根目录的视图改变位置(在开始学习Linux时,搭建DNS
服务就可以用到这个chroot-bind
),直接使用chroot /opt/work [进程名]
就可以将当前进程的根视角切换至/opt/work
,Mount Namespace
就是经过chroot
不断改良从而发明 - 为了使得容器挂载的这个更目录看起来很真实,以便会在在容器的根目录下挂载一个完整的操作系统的文件系统,至此通过上面几段操作就完成我们时如何看到容器想让我们看到的东西

⭐️ 挂载在容器的根目录下并且被隔离的文件系统也就是所谓的"容器镜像",也叫 rootfs
,但是rootfs是不存在操作系统内核的,它只包含操作系统的躯壳,并没有灵魂
注: 容器并不一定使用的
chroot
也有可能时pivot_root
或者其他技术
有了 rootfs
可以解决 Paas
一直强调的一致性的问题,可以使得容器可以像集装箱一样从本地搬到云端,从云端搬到本地
Docker容器镜像
虽然有了 rootfs
但是也引出了一个比较棘手的问题: 比如我使用 Centos
系统的 iso
做了一个 rootfs
,并且在上面发布了我的go应用,这时候如果你想发布其他go应用而不需要之前的go应用的时候,你还需要重复上一步操作,这样就很麻烦
docker
公司在实现镜像的时候引出了层的概念,这也是 docker
项目比其他容器项目要火的原因,也就是用户在制作镜像的每一步操作都会生成一个层,也就是一个增量的 rootfs
,使用 UnionFS
(联合文件系统)将不同位置的目录联合挂载到同一个目录;
也就是说现在镜像并不是一个 rootfs
而是由多个 rootfs
进行增量挂载分为多个层,在 docker
中将镜像分为三个层级(是层级不是层):
- 只读层,挂载方式是
ro+wh,
比如基于centos
发布的应用的容器,它的只读层就是Centos
操作系统的一部分 - 可读写层,故障方式rw,它是容器的
rootfs
最上一层,在写入文件之前,这个目录是空的;但是一旦在容器里面进行了写操作,修改的部分就会以增量的方式出现在该层,如果需要删除只读层一个文件呢?比如我要删除只读层里面的foo
文件,那么这个删除操作实在可读写层创建了一个名为wh.foo
的文件。这样两个层被联合挂载之后,foo
文件就会被wh.foo
文件遮挡,这样就从而消失,这个就是ro+whiteout的功能,他又被称为白障 - init层,它是由docker项目单独生成的一个内部层,专门用于存储
/etc/hosts,/etc/resolv.conf
等信息的
❓ docker exec 是如何进入容器的?
容器进程的 namespace
信息是在宿主机上存在的,一个进程可以加入一个进程的 namespace
,从而实现进入容器