Docker 核心技术原理

Docker 介绍

为什么用docker

  • 更高效利用系统资源
  • 更快速的启动时间
  • 一致的运行环境
  • 持续交付和部署
  • 更轻松地迁移
  • 更轻松地维护和扩展

虚拟机和容器对比

1694090795516

特性或原理 Docker Container 虚拟机
核心原理 进程隔离,共享操作系统内核 通过虚拟化技术,硬件虚拟化,在宿主操作系统上再跑一个操作系统
如何保证一致性 镜像(rootfs) 安装一个相同的操作系统
启动速度 秒级-进程级启动 分钟级-系统级启动
硬盘使用 MB-镜像分层的优势 GB
性能 几乎无损耗 有损耗
单机支持量 单机支持上千个容器 一般几十个

当然,docker也存在一些弊端,主要是隔离性不足以及隔离不足所带来的安全风险。虚拟机基于硬件虚拟化,每个虚拟机上都运行着独立的系统内核,可以保证与宿主机和其他虚拟机有强隔离,跑在虚拟机内的应用可以随便折腾而无需担心影响到“邻居”。而docker所依赖的namespace提供的是有限的隔离,典型的是系统时间没有被隔离,容器内修改系统时间会直接体现在宿主机上;所以开发过程中,当我们的应用需要修改内核参数时,务必谨慎,明确自己在干啥。

Docker 核心原理

Namespace

什么是Namespace

  • Linux Namespace 是一种 Linux Kernel 提供的资源隔离方案:
    • 系统可以为进程分配不同的 Namespace;
    • 并保证不同的 Namespace 资源独立分配、进程彼此隔离,即 不同的 Namespace 下的进程互不干扰 。

在日常使用 Linux 或者 macOS 时,我们并没有运行多个完全分离的服务器的需要,但是如果我们在服务器上启动了多个服务,这些服务其实会相互影响的,每一个服务都能看到其他服务的进程,也可以访问宿主机器上的任意文件,这是很多时候我们都不愿意看到的,我们更希望运行在同一台机器上的不同服务能做到完全隔离,就像运行在多台不同的机器上一样。

Linux 的命名空间机制提供了以下七种不同的命名空间,包括 CLONE_NEWCGROUP、CLONE_NEWIPC、CLONE_NEWNET、CLONE_NEWNS、CLONE_NEWPID、CLONE_NEWUSER 和 CLONE_NEWUTS,通过这七个选项我们能在创建新的进程时设置新进程应该在哪些资源上与宿主机器进行隔离。

这里我们先观察一下已经搭建好的集群容器的情况:

1
2
3
4
5
6
# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE
business-manager-666f454f7f-bg2bt 1/1 Running 0 27s 172.30.76.4 192.168.0.21
business-manager-666f454f7f-kvn5z 1/1 Running 0 27s 172.30.76.5 192.168.0.21
business-manager-666f454f7f-ncjp7 1/1 Running 0 27s 172.30.9.4 192.168.0.22
data-product-6664c6dcb9-p5xkw 1/1 Running 0 7m17s 172.30.9.3 192.168.0.22

接下来我们进入其他一个容器执行ps,查看容器里都有些什么进程:

1
2
3
4
5
6
# kubectl exec -ti business-manager-666f454f7f-ncjp7 sh
$ ps -efj
UID PID PPID PGID SID C STIME TTY TIME CMD
root 1 0 1 1 0 14:47 ? 00:00:03 ./business-manager -conf /business-manager/config/config.json
root 22 0 22 22 0 15:25 pts/0 00:00:00 sh
root 28 22 28 22 0 15:29 pts/0 00:00:00 ps -efj

进入docker 容器内部,ps查看所有的进程,pid=1的是我们的应用程序,pid=22和28的分别是我们这一步操作执行的sh程序和ps程序。了解linux系统的同学应该知道,pid=1的不是内核的init进程吗。那么init进程哪去了呢?
其实上面显示的1号进程,是docker容器的障眼法,这个business-manager进程就是跑在宿主机上的一个特殊的进程,我们查看下宿主机的真实进程情况:

1
2
3
# ps -efj | grep business-manager
root 38156 38138 38156 38156 0 14:47 ? 00:00:03 ./business-manager -conf /business-manager/config/config.json
root 47393 3471 47392 3471 0 15:34 pts/2 00:00:00 grep --color=auto business-manager

上面这个pid=33156的才是宿主机上对应business-manager容器的真实进程。

在当前的宿主机器上,可能就存在由上述的不同进程构成的进程树:

image-20221212155344389

这就是在使用 clone 创建新进程时传入 CLONE_NEWPID 实现的,也就是使用 Linux 的命名空间实现进程的隔离,Docker 容器内部的任意进程都对宿主机器的进程一无所知。

linux namespace 底层结构

1694437477540
  • linux 中每个进程都有一个 nsproxy 结构
  • nsproxy 中又有 uts_ns\ipc_ns\mnt_ns\pid_ns\net_ns 等结构实现

linux Namespace 操作方法

  • clone

    在创建新进程的系统调用时,可以通过 flags 参数指定需要新建的 Namespace 类型:

    // CLONE_NEWCGROUP / CLONE_NEWIPC / CLONE_NEWNET / CLONE_NEWNS / CLONE_NEWPID / CLONE_NEWUSER / CLONE_NEWUTS

    int clone(int (*fn)(void *), void *child_stack, int flags, void *arg)

  • setns

    该系统调用可以让调用进程加入某个已经存在的 Namespace 中:

    Int setns(int fd, int nstype)

  • unshare

    该系统调用可以将调用进程移动到新的 Namespace 下

    int unshare(int flags)

NameSpace 之间的隔离性

1694437757148

  • Pid namespace

  • 不同用户的进程就是通过 Pid namespace 隔离开的,且不同 namespace 中可以有相同 Pid。

    • 有了 Pid namespace, 每个 namespace 中的 Pid 能够相互隔离。
  • network namespace

  • 网络隔离是通过 net namespace 实现的, 每个 net namespace 有独立的 network devices, IP addresses, IP routing tables, /proc/net 目录。

    • Docker 默认采用 veth 的方式将 container 中的虚拟网卡同 host 上的一个 docker bridge: docker0 连接在一起。
  • ipc namespace

  • Container 中进程交互还是采用 linux 常见的进程间交互方法 (interprocess communication – IPC), 包括常见的信号量、消息队列和共享内存。

    • container 的进程间交互实际上还是 host上 具有相同 Pid namespace 中的进程间交互,因此需要在 IPC 资源申请时加入 namespace 信息 - 每个 IPC 资源有一个唯一的 32 位 ID。隔离性 - Linux Namespace
  • mnt namespace

    • mnt namespace 允许不同 namespace 的进程看到的文件结构不同,这样每个 namespace 中的进程所看到的文件目录就被隔离开了。
  • uts namespace

    • UTS(“UNIX Time-sharing System”) namespace允许每个 container 拥有独立的 hostname 和 domain name, 使其在网络上可以被视作一个独立的节点而非 Host 上的一个进程。
  • user namespace

    • 每个 container 可以有不同的 user 和 group id, 也就是说可以在 container 内部用 container 内部的用户执行程序而非 Host 上的用户。

关于namespace 常见命令

  • 查看当前系统的 namespace:
    • lsns –t [net/pid]
  • 查看某进程的 namespace:
    • ls -la /proc//ns/
  • 进入某 namespace 运行命令:
    • nsenter -t -n ip addr

Cgroup

Cgroup 是一个 Linux 内核特性,对一组进程的资源使用(CPU、内存、磁盘 I/O 和网络等)进行限制、审计和隔离。

cgroups(Control Groups) 是 linux 内核提供的一种机制,这种机制可以根据需求把一系列系统任务及其子任务整合(或分隔)到按资源划分等级的不同组内,从而为系统资源管理提供一个统一的框架。

简单说,cgroups 可以限制、记录任务组所使用的物理资源。本质上来说,cgroups 是内核附加在程序上的一系列钩子(hook),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的。

Cgroup 特性罗列

  • Cgroups (Control Groups)是 Linux 下用于对一个或一组进程进行资源控制和监控的机制;
  • 可以对诸如 CPU 使用时间、内存、磁盘 I/O 等进程所需的资源进行限制;
  • 不同资源的具体管理工作由相应的 Cgroup 子系统(Subsystem)来实现 ;
  • 针对不同类型的资源限制,只要将限制策略在不同的的子系统上进行关联即可 ;
  • Cgroups 在不同的系统资源管理子系统中以层级树(Hierarchy)的方式来组织管理:每个Cgroup 都可以包含其他的子 Cgroup,因此子 Cgroup 能使用的资源除了受本 Cgroup 配置的资源参数限制,还受到父Cgroup 设置的资源限制 。

Cgroup-Control Groups 可配额

1694439416131

Cgroup 涉及的子系统

cgroups 实现了对资源的配额和度量, 下面是cgroup涉及的子系统

  • blkio: 这个子系统设置限制每个块设备的输入输出控制。例如:磁盘,光盘以及 USB 等等
  • cpu: 这个子系统使用调度程序为 cgroup 任务提供 CPU 的访问
  • cpuacct: 产生 cgroup 任务的 CPU 资源报告
  • cpuset: 如果是多核心的 CPU,这个子系统会为 cgroup 任务分配单独的 CPU 和内存
  • devices: 允许或拒绝 cgroup 任务对设备的访问
  • freezer: 暂停和恢复 cgroup 任务
  • memory: 设置每个 cgroup 的内存限制以及产生内存资源报告
  • net_cls: 标记每个网络包以供 cgroup 方便使用
  • ns: 名称空间子系统
  • pid: 进程标识子系统

CPU 子系统

cpu.shares: 可出让的能获得 CPU 使用时间的相对值。

cpu.cfs_period_us:cfs_period_us 用来配置时间周期长度,单位为 us(微秒)。 默认10w

cpu.cfs_quota_us:cfs_quota_us 用来配置当前 Cgroup 在 cfs_period_us 时间内最多能使用的 CPU 时间数,单位为 us(微秒)。

cpu.stat : Cgroup 内的进程使用的 CPU 时间统计。

nr_periods : 经过 cpu.cfs_period_us 的时间周期数量。

nr_throttled : 在经过的周期内,有多少次因为进程在指定的时间周期内用光了配额时间而受到限制。

throttled_time : Cgroup 中的进程被限制使用 CPU 的总用时,单位是 ns(纳秒)。

使用方式

以busyloop 程序为例

  • 在 cgroup cpu 子系统目录中创建目录结构
    • cd /sys/fs/cgroup/cpu
    • mkdir cpudemo
    • cd cpudemo
  • 运行 busyloop
  • 执行 top 查看 CPU 使用情况,CPU 占用 200%
  • 通过 cgroup 限制 cpu
    • cd /sys/fs/cgroup/cpu/cpudemo
    • 把进程添加到 cgroup 进程配置组
      • echo ps -ef|grep busyloop|grep -v grep|awk ‘{print $2}’ > cgroup.procs
  • 设置 cpuquota

    • echo 10000 > cpu.cfs_quota_us
  • 执行 top 查看 CPU 使用情况,CPU 占用变为10%

cpuacct 子系统

用于统计 Cgroup 及其子 Cgroup 下进程的 CPU 的使用情况。

  • cpuacct.usage:包含该 Cgroup 及其子 Cgroup 下进程使用 CPU 的时间,单位是 ns(纳秒)。
  • cpuacct.stat: 包含该 Cgroup 及其子 Cgroup 下进程使用的 CPU 时间,以及用户态和内核态的时间

Memory 子系统

  • memory.usage_in_bytes:cgroup 下进程使用的内存,包含 cgroup 及其子 cgroup 下的进程使用的内存
  • memory.max_usage_in_bytes:cgroup 下进程使用内存的最大值,包含子 cgroup 的内存使用量。
  • memory.limit_in_bytes:设置 Cgroup 下进程最多能使用的内存。如果设置为 -1,表示对该 cgroup 的内存使用不做限制。
  • memory.soft_limit_in_bytes:这个限制并不会阻止进程使用超过限制的内存,只是在系统内存足够时,会优先回收超过限额的内存,使之向限定值靠拢。
  • memory.oom_control:设置是否在 Cgroup 中使用 OOM(Out of Memory)Killer,默认为使用。当属于该 cgroup 的进程使用的内存超过最大的限定值时,会立刻被 OOM Killer 处理。

使用方式

  • 在 cgroup memory 子系统目录中创建目录结构
    • cd /sys/fs/cgroup/memory
    • mkdir memorydemo
    • cd memorydemo
  • 运行 malloc 进程
  • 查看内存使用情况
    • watch ‘ps -aux|grep malloc|grep -v grep‘
  • 通过 cgroup 限制 memory
    • 把进程添加到cgroup进程配置组
    • echo ps -ef|grep malloc |grep -v grep|awk ‘{print $2}’ > cgroup.procs
    • 设置 memory.limit_in_bytes
    • echo 104960000 > memory.limit_in_bytes
  • 等待进程被 oom kill

Cgroup driver

systemd、cgroupfs 的介绍

https://blog.csdn.net/qq_37705525/article/details/124717044

  • systemd:

    • 当操作系统使用 systemd 作为 init system 时,初始化进程生成一个根 cgroup 目录结构并作为 cgroup
      管理器。
    • systemd 与 cgroup 紧密结合,并且为每个 systemd unit 分配 cgroup。
  • cgroupfs:

    • docker 默认用 cgroupfs 作为 cgroup 驱动。

    存在问题:

    • 在 systemd 作为 init system 的系统中,默认并存着两套 groupdriver。
    • 这会使得系统中 Docker 和 kubelet 管理的进程被 cgroupfs 驱动管,而 systemd 拉起的服务由
      systemd 驱动管,让 cgroup 管理混乱且容易在资源紧张时引发问题。
    • 因此 kubelet 会默认–cgroup-driver=systemd,若运行时与docker cgroup 不一致时,kubelet 会报错。

Union FS

联合文件系统作用

  • 将不同目录挂载到同一个虚拟文件系统下 (unite several directories into a single virtual filesystem) 的文件系统
  • 支持为每一个成员目录(类似Git Branch)设定 readonly、readwrite 和 whiteout-able 权限
  • 文件系统分层, 对 readonly 权限的 branch 可以逻辑上进行修改(增量地, 不影响 readonly 部分的)。
  • 通常 Union FS 有两个用途, 一方面可以将多个 disk 挂到同一个目录下, 另一个更常用的就是将一个 readonly 的 branch 和一个 writeable 的 branch 联合在一起。

容器镜像

Dockerfile每个命令, 对应容器镜像的一层

不同的Dockerfile 相同的命令可以共用镜像层

1694697071731

Docker 的文件系统

Linux 文件系统

典型的 Linux 文件系统组成: 对与不同的操作系统 Bootfs基本一致,rootfs有区别。

  • Bootfs(boot file system)

    • Bootloader - 引导加载 kernel,
    • Kernel - 当 kernel 被加载到内存中后 umount bootfs。
  • rootfs (root file system)

    • /dev,/proc,/bin,/etc 等标准目录和文件。
    • 对于不同的 linux 发行版, bootfs 基本是一致的, 但 rootfs 会有差别。

    1694697333685

Docker 启动过程

  • Linux

    在启动后,首先将 rootfs 设置为 readonly, 进行一系列检查, 然后将其切换为 “readwrite”供用户使用。

  • Docker启动

    初始化时也是将 rootfs 以 readonly 方式加载并检查,然而接下来利用 union mount 的方式将一个 readwrite 文件系统挂载在 readonly 的 rootfs 之上; 并且允许再次将下层的 FS(file system) 设定为 readonly 并且向上叠加。 这样一组 readonly 和一个 writeable 的结构构成一个 container 的运行时态, 每一个 FS 被称作一个 FS 层。

1694697607008

Docker写操作

由于镜像具有共享特性,所以对容器可写层的操作需要依赖存储驱动提供的写时复制和用时分配机制,以此来 支持对容器可写层的修改,进而提高对存储和内存资源的利用率。

  • 写时复制
    • 写时复制,即 Copy-on-Write。一个镜像可以被多个容器使用,但是不需要在内存和磁盘上做多个拷贝。在 需要对镜像提供的文件进行修改时,该文件会从镜像的文件系统被复制到容器的可写层的文件系统进行修改, 而镜像里面的文件不会改变。不同容器对文件的修改都相互独立、互不影响。
  • 用时分配
    • 按需分配空间,而非提前分配,即当一个文件被创建出来后,才会分配空间。

OverlayFS 存储驱动

OverlayFS 也是一种与 AUFS 类似的联合文件系统,同样属于文件级的存储驱动,包含了最初的 Overlay 和 更新更稳定的 overlay2。

Overlay 只有两层:upper 层和 lower 层,Lower 层代表镜像层,upper 层代表容器可写层

容器和镜像的区别就在于,所有的镜像都是只读的,而每一个容器其实等于镜像加上一个可读写的层,也就是同一个镜像可以对应多个容器。

1694697910897

OverlayFS 文件系统使用

创建四个目录

1
2
3
4
5
$ mkdir upper lower merged work
$ echo "from lower" > lower/in_lower.txt
$ echo "from upper" > upper/in_upper.txt
$ echo "from lower" > lower/in_both.txt
$ echo "from upper" > upper/in_both.txt

MOUNT overlay

1
2
3
4
5
6
7
8
9
10
11
12
sudo mount -t overlay overlay -o 
lowerdir=`pwd`/lower,
upperdir=`pwd`/upper
,workdir=`pwd`/work
`pwd`/merged
# 挂载选项支持(即"-o"参数):
# 1)lowerdir=xxx:指定用户需要挂载的lower层目录(支持多lower,最大支持500层);
# 2)upperdir=xxx:指定用户需要挂载的upper层目录;
# 3)workdir=xxx:指定文件系统的工作基础目录,挂载后内容会被清空,且在使用过程中其内容用户不可见-用来提供要原子性保证;
#4)default_permissions:功能未使用;
#5)redirect_dir=on/off:开启或关闭redirect directory特性,开启后可支持merged目录和纯lower层目录的rename/renameat系统调用;
# 6)index=on/off:开启或关闭index特性,开启后可避免hardlink copyup broken问题。

验证结果

1
$ cat merged/in_both.txt          # "from upper!

rootfs

Namespace对应用进行了隔离,而cgroup则完成了资源的分配和限制,现在一个针对应用程序的沙盒已经成型。接下来就是考虑一致性问题了?

一致性主要是为了解决应用跑在不同的宿主机上不受宿主机环境的差异影响的问题。容器技术出来之前,手动或脚本迁移应用的时候,往往会遇到新的宿主机缺少某个关键组件、或是某些依赖版本差异甚至是操作系统内核差异等因素导致的不一致问题。现在我们来看看docker是怎么解决这个问题的。

对于单个应用程序进程来说,对环境的依赖,关键体现在对操作系统所提供的文件系统的依赖,所以docker所要做的就是通过以下3步给你一套想要的文件系统:

  • 通过mount namespace(2.1章节)将应用的文件系统隔离开
  • 将应用所需要的文件系统(比如centos:7的所有文件)拷贝到某个目录D下
  • 调用chroot将应用的根目录调整为目录D(mount namespace的隔离作用在这里体现出来了,chroot只对在当前namespace下的应用生效,应用在这个“根目录”下可以随便折腾,而不会影响到真实的宿主机根目录。)

上面这3步所构造出来的文件系统,我们称之为rootfs(根文件系统)。应用程序执行“cd /“指令进入的根目录,将被限定在上述目录D下。下面我们简单验证一下这个rootfs。
这里我们在容器的根目录新建一个文件jo1,然后在宿主机上查找这个文件,定位到如下位置:

1
2
3
# cd /var/lib/docker/overlay2/b2231f9f15050ae8d609726d308c2ead60114df3fc5404a24c688d805d4a9883/merged/
# ls
anaconda-post.log bin business-manager data dev etc home jo1 lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var

可以看到,上面这个文件夹正是我们的容器所在的“根目录”。

我们也可以用mount指令查看当前宿主机的挂载情况,限于篇幅,这里就不展开解析mount了,下面是mount的输出节选, 可以看到该容器的merge层就是通过OverlayFS文件驱动生成的联合文件系统:

1
2
3
4
# mount|grep overlay2
overlay on /var/lib/docker/overlay2/be512d9faf97c7d860fa16ecc4ecd5057e12feffc8b0804115923f0795bb9f75/merged type overlay (rw,relatime,seclabel,lowerdir=/var/lib/docker/overlay2/l/ZSXN3J4UXRRBMRG3F3RNUZGWUB:/var/lib/docker/overlay2/l/2CCAOTBFIGCYNR73TQEE333CO6:/var/lib/docker/overlay2/l/6Y5GTV75CBUR4WORCMOHYJZQ7Y:/var/lib/docker/overlay2/l/VWENHOE7X3WDA4NK5P7DTTPSIX,upperdir=/var/lib/docker/overlay2/be512d9faf97c7d860fa16ecc4ecd5057e12feffc8b0804115923f0795bb9f75/diff,workdir=/var/lib/docker/overlay2/be512d9faf97c7d860fa16ecc4ecd5057e12feffc8b0804115923f0795bb9f75/work)
overlay on /var/lib/docker/overlay2/8a7546027e2e354bf0bba12600fdb6ac87c199d09589ba90952f28aed74d13b6/merged type overlay (rw,relatime,seclabel,lowerdir=/var/lib/docker/overlay2/l/KOZRXUFTWITQUNAKOZMWSYWTSH:/var/lib/docker/overlay2/l/2CCAOTBFIGCYNR73TQEE333CO6:/var/lib/docker/overlay2/l/6Y5GTV75CBUR4WORCMOHYJZQ7Y:/var/lib/docker/overlay2/l/VWENHOE7X3WDA4NK5P7DTTPSIX,upperdir=/var/lib/docker/overlay2/8a7546027e2e354bf0bba12600fdb6ac87c199d09589ba90952f28aed74d13b6/diff,workdir=/var/lib/docker/overlay2/8a7546027e2e354bf0bba12600fdb6ac87c199d09589ba90952f28aed74d13b6/work)
...

Docker 的网络

Docker网络类型

主机网络

  • Null(–net=None)

    • 把容器放入独立的网络空间但不做任何网络配置;
    • 用户需要通过运行 docker network 命令来完成网络配置。
  • Host

    • 使用主机网络名空间,复用主机网络。
  • Container

    • 重用其他容器的网络。
  • Bridge(–net=bridge) 默认

    • 使用 Linux 网桥和 iptables 提供容器互联,Docker 在每台主机上创建一个名叫 docker0 的网桥,通过 veth pair 来连接该主机的每一个 EndPoint。

      1694701366949

      在这种模式下,除了分配隔离的网络命名空间之外,Docker 还会为所有的容器设置 IP 地址。当 Docker 服务器在主机上启动之后会创建新的虚拟网桥 docker0,随后在该主机上启动的全部服务在默认情况下都与该网桥相连。

      在默认情况下,每一个容器在创建时都会创建一对虚拟网卡,两个虚拟网卡组成了数据的通道,其中一个会放在创建的容器中,会加入到名为 docker0 网桥中。

      docker0 会为每一个容器分配一个新的 IP 地址并将 docker0 的 IP 地址设置为默认的网关。网桥 docker0 通过 iptables 中的配置与宿主机器上的网卡相连,所有符合条件的请求都会通过 iptables 转发到 docker0 并由网桥分发给对应的机器。

      我们在当前的机器上使用 docker run -d -p 6379:6379 redis 命令启动了一个新的 Redis 容器,在这之后我们再查看当前 iptables 的 NAT 配置就会看到在 DOCKER 的链中出现了一条新的规则:

      1
      DNAT       tcp  --  anywhere             anywhere             tcp dpt:6379 to:192.168.0.4:6379

      上述规则会将从任意源发送到当前机器 6379 端口的 TCP 包转发到 192.168.0.4:6379 所在的地址上。

      Docker 通过 Linux 的命名空间实现了网络的隔离,又通过 iptables 进行数据包转发,让 Docker 容器能够优雅地为宿主机器或者其他容器提供服务。

跨主机网络

  • Remote(work with remote drivers)
    • Underlay(复用底层网络):
      • 使用现有底层网络,为每一个容器配置可路由的网络 IP。
    • Overlay(libnetwork, libkv) :
      • 通过网络封包实现。

Docker 架构

OCI 容器标准

Open Container Initiative

  • OCI 组织于 2015 年创建,是一个致力于定义容器镜像标准和运行时标准的开放式组织。
  • OCI 定义了镜像标准(Runtime Specification)、运行时标准(Image Specification)和分发标准(Distribution Specification)
    • 镜像标准定义应用如何打包
    • 运行时标准定义如何解压应用包并运行
    • 分发标准定义如何分发容器镜像

Docker 引擎架构

1694700034813

容器都有一个相应的 “shim” 守护进程,这个守护进程会提供一个 API,Containerd 使用该 API 来管理容器基本的生命周期(启动/停止),在容器中执行新的进程、调整 TTY 的大小以及与特定平台相关的其他操作。shim 还有一个作用是向 Containerd 报告容器的退出状态,在容器退出状态被 Containerd 收集之前,shim 会一直存在。这一点和僵尸进程很像,僵尸进程在被父进程回收之前会一直存在,只不过僵尸进程不会占用资源,而 shim 会占用资源。

shim 将 Containerd 进程从容器的生命周期中分离出来,具体的做法是 runc 在创建和运行容器之后退出,并将 shim 作为容器的父进程,即使 Containerd 进程挂掉或者重启,也不会对容器造成任何影响。这样做的好处很明显,你可以高枕无忧地升级或者重启 Containerd,不会对运行中的容器产生任何影响。

刘小恺(Kyle) wechat
如有疑问可联系博主