# 原理
# 架构
- 容器(Container)是一个像虚拟机的沙盒,内部可以运行一组进程。
- 镜像(Image)是用于创建容器的模板,包含了应用程序的可执行文件文件、依赖文件、配置信息等。
- Docker 采用 C/S 架构工作:
- 在宿主机上运行一个守护进程 dockerd ,作为服务器,负责管理本机的容器、镜像、数据卷、网络等对象。
- 早期版本的 dockerd 以子进程的形式运行各个容器的 1 号进程,存在单点故障的风险。
- 后来,改为给每个容器创建一个守护进程 containerd-shim (新版本为 containerd-shim-runc-v2 ),作为容器的父进程。
- 用户可以在宿主机上执行 docker 命令,作为客户端,与 dockerd 交互,比如请求启动容器。
- 客户端默认通过本机的
/var/run/docker.sock
文件与 dockerd 通信,也可通过 tcp 端口访问本机或其它主机的 dockerd 。 - 非 root 用户无权访问 docker.sock 文件,导致无权执行 docker ps 等命令。可以将该用户加入 docker 用户组:
sudo usermod leo -aG docker
,从而开通权限。
- 客户端默认通过本机的
- 在宿主机上运行一个守护进程 dockerd ,作为服务器,负责管理本机的容器、镜像、数据卷、网络等对象。
# namespace
Docker 基于 Linux namespace 技术隔离了各个容器可见的系统资源。
- 隔离了进程、网络、文件系统等系统资源,从而为每个容器创建一个运行环境,模拟一个独立的虚拟机。
- 没有隔离硬件资源。例如:一个容器可能占用全部的 CPU 、内存、磁盘;在容器内执行 top 命令会显示整个宿主机的 CPU、内存量。
- 没有隔离 Linux 内核。容器内外的所有进程调用同一个 Linux 内核,容器内进程可能通过内核漏洞逃逸到宿主机上。
namespace 分为多种类型,分别用于隔离不同的系统资源。如下:
类型 Flag 隔离资源 ipc CLONE_NEWIPC System V 系统的 IPC 对象、POSIX 系统的消息队列 network CLONE_NEWNET IP、Socket、路由器、iptables 防火墙 mnt CLONE_NEWNS 文件系统的挂载点 pid CLONE_NEWPID 进程的 PID user CLONE_NEWUSER 用户、用户组 uts CLONE_NEWUTS 主机名、域名 - 每种类型的 namespace 可以创建多个实例。
- 每个进程可以同时使用多种类型的 namespace 实例。
- 如果一个 namespace 内的所有进程都退出,则内核会自动销毁该 namespace 。除非
/proc/<pid>/ns/<namespace>
文件被一直打开或挂载。
- 例:
- 不同 pid namespace 中的进程可以分配相同的 PID 。
- docker 容器内,第一个启动的进程被分配的 PID 为 1 ,而 PPID 为 0 。
- 每种类型的 namespace 可以创建多个实例。
关于 pid namespace :
- pid namespace 支持嵌套,当前 pid namespace 中创建的所有 pid namespace 都属于子实例。
- 父实例可以看到子孙实例的进程信息,但反之不行。
比如在父实例中执行 ps -ef 命令,会将子孙实例中的进程也显示出来,并将它们的 PID 转换成父实例中的 PID 。 - 主机启动之后,第一个创建的 pid namespace 就是最上层的实例。
- 父实例可以看到子孙实例的进程信息,但反之不行。
- 每个 pid namespace 中,第一个创建的进程的 PID 为 1 ,称为 1 号进程。
- 如果其它进程产生了孤儿进程,则内核会将它们改为当前 pid namespace 的 1 号进程的子进程。
- 如果 1 号进程退出,则内核会向当前及子孙 pid namespace 中的所有进程发送 SIGKILL 信号,杀死它们。
- 内核函数 kill() 只允许将已注册 handler 的信号发送给 1 号进程,会忽略 SIGKILL、SIGSTOP 等信号。
- 因此,在容器内执行
kill -9 1
命令不会杀死 1 号进程。但可以在宿主机上查到容器内 1 号进程的 PID ,用 kill 命令杀死它,这样会导致子孙 pid namespace 被销毁。
- 因此,在容器内执行
- 调用 setns() 或 unshare() 时,不会改变当前进程的 pid namespace ,而是影响之后创建的子进程。
- 因此,一个进程在创建之后,其 PID 总是不变。成为孤儿进程时, PPID 会变为 1 。
- 相关 API :
#include <unistd.h> pid_t getpid(void); // 返回当前进程的 PID ,属于当前的 pid namespace pid_t getppid(void); // 返回父进程的 PID 。如果父进程属于不同的 pid namespace ,则返回 0
- pid namespace 支持嵌套,当前 pid namespace 中创建的所有 pid namespace 都属于子实例。
/proc/<pid>/ns/
目录下,通过一些软链接,记录了进程所属的 namespace ID 。如下:[root@CentOS ~]# ll /proc/1/ns total 0 lrwxrwxrwx. 1 root root 0 Mar 26 13:25 ipc -> ipc:[4026531839] lrwxrwxrwx. 1 root root 0 Mar 26 13:25 mnt -> mnt:[4026531840] lrwxrwxrwx. 1 root root 0 Mar 26 13:25 net -> net:[4026531956] lrwxrwxrwx. 1 root root 0 Mar 26 13:25 pid -> pid:[4026531836] lrwxrwxrwx. 1 root root 0 Mar 26 13:25 user -> user:[4026531837] lrwxrwxrwx. 1 root root 0 Mar 26 13:25 uts -> uts:[4026531838]
- 如果两个进程的某种 namespace ID 相同,则说明属于这种类型的同一个 namespace 实例。
- 创建子进程时,默认继承父进程的 namespace 实例。
相关 API :
#include <sched.h> int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg); // 创建一个子进程,并创建指定类型的 namespace ,让子进程加入其中 // child_func :子进程要运行的函数 // child_stack :子进程使用的栈空间 // flags :一种或多种 namespace 的 flag ,比如 flags = CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUSER // args :传给子进程的参数 int setns(int fd, int nstype); // 让当前进程加入已存在的 namespace // fd :一个 namespace 的文件描述符 // nstype :用于检查 fd 指向的 namespace 类型,填 0 则不检查 int unshare(int flags); // 根据 flags 创建新的 namespace ,然后让当前进程加入其中。这会先离开已加入的同类型 namespace
# Cgroup
Docker 基于 Linux Cgroup 技术限制容器对 CPU、内存等系统资源的使用量,每个容器属于一个独立的 Cgroup 节点。
- 在 Cgroup 技术之前,通常通过 ulimit 命令限制 shell 终端对系统资源的使用量,但功能较少。
Cgroup 的版本:
- v1
- 官方文档 (opens new window)
- 本文介绍该版本。
- v2
- 将所有 subsystem 关联到同一个 hierarchy 中。
- 同一个 subsystem 不能同时关联 v1 和 v2 版本的 hierarchy 。
- 只允许在根节点、叶子节点创建 cgroup 。
- 支持限制磁盘 iops 。
- v1
# 主要概念
- task
- :代表一个进程。
- cgroup
- :控制组,包含一组进程。
- cgroup 的最小管理单位是进程,不能管理进程中的某个线程。
- 创建子进程时,默认继承父进程的 cgroup 。但是创建之后,修改父进程或子进程的 cgroup ,不再相互影响。
- hierarchy
- :层级,是由多个 cgroup 实例组成的一个目录树。
- 通过挂载文件系统的方式,创建 hierarchy 。
- 一般将 hierarchy 挂载在
/sys/fs/cgroup/
目录下。 - 通过在 hierarchy 目录下创建、删除子目录的方式,创建、删除 cgroup 节点。
- cgroup 子节点默认继承父节点的属性。
- 如果一个 cgroup 节点不包含任何进程,也没有子节点,则称为空节点,允许被删除。
- 创建 hierarchy 时,根路径下的 cgroup 节点是第一个创建的节点,称为 root cgroup 。
- 一般将 hierarchy 挂载在
- 每个进程同时可以存在于多个 hierarchy 中。
- 在同一个 hierarchy 中,每个进程同时只能属于一个 cgroup ,但可以切换到其它 cgroup 。
- subsystem
- :资源控制器(Resource Controller)。
- 通过将 subsystem 关联到 hierarchy 的方式,控制 cgroup 内进程占用的系统资源。
- 每个 subsystem 同时只能关联一个 hierarchy 。
- 每个 hierarchy 同时可以关联多个 subsystem 。
- subsystem 分为多种类型:
pids # 限制、统计进程总数,包括 Cgroup 当前节点及其子节点 cpu # 限制 CPU 使用量 cpuacct # 统计 CPU 使用量 cpuset # 只允许使用 CPU 指定几个编号的核 memory # 限制、统计内存的使用量 blkio # 限制对块设备的 IO 速度 devices # 控制能否访问某些设备 freezer # 暂停进程。这不是通过发送 SIGSTOP 信号,而是在进程被 CPU 执行时突然阻塞。此时进程依然存在,不能继续执行,不能响应信号 hugetlb # 限制 huge page 的使用量 perf_event # 允许进程被 perf 命令监控 net_prio # 设置对网络接口的访问优先级 net_cls # 通过等级识别符 (classid) 标记进程发出的网络数据包,便于被 iptables 等工具统计
- 每种 subsystem 通过读写一组文件来实现配置,例如:
pids.max # 限制进程总数,取值为 max 则不限制 pids.current # 监控当前的进程总数 cpu.cfs_period_us # Cgroup 的 CPU 调度器采用 CFS ,可以配置 CFS 周期的长度。单位为 us ,取值范围为 1ms~1s ,默认为 100000us 即 100ms cpu.cfs_quota_us # 限制当前 Cgroup 节点在每个 CFS 周期内,占用 CPU 的最大时长。取值范围为 >1ms ,默认为 0 ,表示不限制 cpu.shares # 多个 Cgroup 节点同时申请使用 CPU 时,Linux 会根据每个节点的 cpu_shares 大小比例来分配 CPU 时长 memory.limit_in_bytes # 限制进程最大的内存使用量,取值为 -1 则不限制 memory.oom_control # 取值为 0 时,启用 OOM-killer ,当进程占用的内存超过限制时杀死它。取值为 1 时,禁用 OOM-killer ,只会暂停进程 memory.usage_in_bytes # 监控进程当前的内存使用量,包括 RSS、swap、Page Cache memory.max_usage_in_bytes # 监控进程历史最大的内存使用量
- 有的程序会自动读取
/sys/fs/cgroup/memory/memory.limit_in_bytes
,作为自己的内存上限,从而准确地控制自己的内存开销。而有的程序不会发现自己被 Cgroup 限制,依然尝试使用主机的全部内存,可能占用过多内存而被 OOM 杀死。
- 有的程序会自动读取
- 关于 cpu.shares 。
- docker 创建每个容器所属的 Cgroup 节点时,默认配置 cpu.shares=1024 。因此所有容器的 cpu.shares 之和,可能超过主机的 CPU 总量。
- k8s 中,假设 Pod 配置了 limits.cpu=1 和 requests.cpu=1 ,则创建 Pod 所属的 Cgroup 节点时,会自动配置 cpu.cfs_quota_us=100000 和 cpu.shares=1024 。
- k8s 会严格限制每个 Pod 的 CPU 开销,确保每个主机上所有 Pod 的 requests.cpu 之和,不能超过主机的 CPU 总量。
# 查看
例:查看指定进程的 cgroup 信息
[root@CentOS ~]# cat /proc/$$/cgroup 11:memory:/user.slice 10:cpuset:/ 9:cpuacct,cpu:/user.slice 8:hugetlb:/ 7:net_prio,net_cls:/ 6:devices:/user.slice 5:freezer:/ 4:blkio:/user.slice 3:pids:/user.slice 2:perf_event:/ 1:name=systemd:/user.slice/user-1000.slice/session-3785.scope
- 每行的三个字段分别表示:
- cgroup ID 。
- cgroup 绑定的所有 subsystem 的名称,用逗号分隔。
name=systemd
表示没有绑定 subsystem ,只是定义了名称。
- 进程在 cgroup 树中的路径。
- 这是对于挂载点的相对路径,以 / 开头。
- 对于 Cgroup v2 ,每行总是显示成
0::<PATH>
的格式。
- 每行的三个字段分别表示:
例:查看系统的所有 subsystem
[root@CentOS ~]# cat /proc/cgroups #subsys_name hierarchy num_cgroups enabled cpuset 10 5 1 cpu 9 71 1 cpuacct 9 71 1 memory 11 71 1 devices 6 71 1 freezer 5 5 1 net_cls 7 5 1 blkio 4 71 1 perf_event 2 5 1 hugetlb 8 5 1 pids 3 71 1 net_prio 7 5 1
- 四列分别表示 subsystem 的:
- 名称
- 关联的 hierarchy ID
- 关联的 cgroup 中的进程数
- 是否启用
- 四列分别表示 subsystem 的:
# 创建
创建 Cgroup 的示例:
通过挂载文件系统的方式,创建 hierarchy :
mkdir -p /cgroup/hierarchy_1 # 挂载一个 cgroup 类型的虚拟文件系统,作为 hierarchy ,命名为 hierarchy_1 。默认关联所有 subsystem # mount -t cgroup hierarchy_1 /cgroup/hierarchy_1 # 可以通过 -o 选项传入一些参数,这里是只关联指定的 subsystem # mount -t cgroup -o cpuset,cpuacct hierarchy_1 /cgroup/hierarchy_1 # 挂载 hierarchy ,关联的 subsystem 为空 mount -t cgroup -o none,name=subsystem_1 hierarchy_1 /cgroup/hierarchy_1
- 如果使用的 subsystem 已经被关联,则挂载 hierarchy 时会报错:
mount: hierarchy_1 is already mounted or /cgroup/hierarchy_1 busy
- 删除 hierarchy 的命令:
umount /cgroup/hierarchy_1 /bin/rm -r /cgroup/hierarchy_1
- 如果使用的 subsystem 已经被关联,则挂载 hierarchy 时会报错:
hierarchy 在挂载之后会自动创建 root cgroup 节点,生成一些默认文件。如下:
[root@CentOS ~]# cd /cgroup/hierarchy_1 [root@CentOS /cgroup/hierarchy_1]# ll total 0 -rw-r--r--. 1 root root 0 Mar 26 17:17 cgroup.clone_children # 文件内容默认为 0 。如果为 1 ,则创建子节点时会拷贝当前节点的 cpuset subsystem 配置 --w--w--w-. 1 root root 0 Mar 26 17:17 cgroup.event_control -rw-r--r--. 1 root root 0 Mar 26 17:17 cgroup.procs # 记录该 cgroup 关联的所有进程,每行一个 PID -r--r--r--. 1 root root 0 Mar 26 17:17 cgroup.sane_behavior -rw-r--r--. 1 root root 0 Mar 26 17:17 notify_on_release # 文件内容默认为 0 。如果为 1 ,则 cgroup 退出时会执行 release_agent -rw-r--r--. 1 root root 0 Mar 26 17:17 release_agent # 该文件只存在于 root cgroup 中,包含一些 cgroup 退出时需要执行的命令 -rw-r--r--. 1 root root 0 Mar 26 17:17 tasks # 与 cgroup.procs 类似,记录进程中主线程的 TID ,即与 PID 一致 [root@CentOS /cgroup/hierarchy_1]# head cgroup.procs # root cgroup 默认包含系统的所有进程 1 2 4 6 7 8 9 10 11 12
在 hierarchy 目录下添加 cgroup 节点:
[root@CentOS /cgroup/hierarchy_1]# mkdir cgroup_1 # 创建一个 cgroup 子节点,这会自动生成一些默认文件 [root@CentOS /cgroup/hierarchy_1]# cd cgroup_1/ [root@CentOS /cgroup/hierarchy_1/cgroup_1]# ll total 0 -rw-r--r--. 1 root root 0 Mar 26 17:35 cgroup.clone_children --w--w--w-. 1 root root 0 Mar 26 17:35 cgroup.event_control -rw-r--r--. 1 root root 0 Mar 26 17:35 cgroup.procs -rw-r--r--. 1 root root 0 Mar 26 17:35 notify_on_release -rw-r--r--. 1 root root 0 Mar 26 17:35 tasks [root@CentOS /cgroup/hierarchy_1/cgroup_1]# cat cgroup.procs # 非 root cgroup 的节点,默认不包含任何进程
在 cgroup 节点中增加进程:
[root@CentOS /cgroup/hierarchy_1/cgroup_1]# echo 1 >> cgroup.procs # 向该 cgroup 分组加入一个进程,这会自动将该进程移出之前的 cgroup [root@CentOS /cgroup/hierarchy_1/cgroup_1]# cat cgroup.procs 1 [root@CentOS /cgroup/hierarchy_1/cgroup_1]# head ../cgroup.procs 2 4 6 7 8 9 10 11 12 13 [root@CentOS /cgroup/hierarchy_1/cgroup_1]# cat ../cgroup.procs >> cgroup.procs # 每次只能加入一个 PID cat: write error: Argument list too long [root@CentOS /cgroup/hierarchy_1/cgroup_1]# echo 3 >> cgroup.procs # 不能加入不存在的进程的 PID -bash: echo: write error: No such process [root@CentOS /cgroup/hierarchy_1/cgroup_1]# echo > cgroup.procs # 只能在 cgroup 中加入进程,不支持删除进程 [root@CentOS /cgroup/hierarchy_1/cgroup_1]# cat cgroup.procs 1 [root@CentOS /cgroup/hierarchy_1/cgroup_1]# echo $$ > cgroup.procs # 加入当前终端的 PID [root@CentOS /cgroup/hierarchy_1/cgroup_1]# cat cgroup.procs 1 4593 6121 # 终端执行命令时创建了子进程,因此也加入了其 PID [root@CentOS /cgroup/hierarchy_1/cgroup_1]# echo $$ > cgroup.procs # 加入 PID 时,会自动排序、去重、去掉不存在的 PID [root@CentOS /cgroup/hierarchy_1/cgroup_1]# cat cgroup.procs 1 4593 6160
- 非 root 用户只允许修改自己进程所属的 cgroup 。
- 非空节点的 cgroup 不允许被删除:
[root@CentOS /cgroup/hierarchy_1/cgroup_1]# cd .. [root@CentOS /cgroup/hierarchy_1]# rm -rf cgroup_1/ /bin/rm: cannot remove ‘cgroup_1/cgroup.clone_children’: Operation not permitted /bin/rm: cannot remove ‘cgroup_1/cgroup.event_control’: Operation not permitted /bin/rm: cannot remove ‘cgroup_1/notify_on_release’: Operation not permitted /bin/rm: cannot remove ‘cgroup_1/cgroup.procs’: Operation not permitted /bin/rm: cannot remove ‘cgroup_1/tasks’: Operation not permitted
# libcgroup-tools
:一个用于查看、配置 Cgroup 的工具包。
- 安装:
yum install -y libcgroup-tools
- 命令:
lscgroup # 显示本机的所有 cgroup
lssubsys # 显示已挂载的 subsystem -a # 显示所有 subsystem -m # 增加显示每个 subsystem 的挂载点 -i # 增加显示每个 subsystem 关联的 hierarchy ID
cgdelete [<controllers>:<path>]... # 删除 cgroup
- 例:
[root@CentOS ~]# lscgroup ... cpu,cpuacct:/ # 每行的格式为 <controllers>:<path> ,即 subsystem 的类型、cgroup 在 hierarchy 下的相对路径 cpu,cpuacct:/docker cpu,cpuacct:/docker/baf31e1d1a0b055b7f7d9947ec035d716a2d7788ee47473f85f9f4061633fabe cpu,cpuacct:/docker/ba919a1393fbd560291ec8dd28eedf12b1e6ce4f0c1ab4df04d929074a436661 cpu,cpuacct:/user.slice cpu,cpuacct:/system.slice cpu,cpuacct:/system.slice/iptables.service cpu,cpuacct:/system.slice/run-user-1000.mount cpu,cpuacct:/system.slice/crond.service cpuset:/ cpuset:/docker cpuset:/docker/baf31e1d1a0b055b7f7d9947ec035d716a2d7788ee47473f85f9f4061633fabe cpuset:/docker/ba919a1393fbd560291ec8dd28eedf12b1e6ce4f0c1ab4df04d929074a436661 name=subsystem_1:/
# layer
Docker 容器内采用联合挂载(Union mount)技术挂载多层文件系统(file system layer)。
- mount 命令会让挂载的 layer 覆盖挂载点,而 Union mount 会让挂载的 layer 与挂载点合并:路径不同的文件会混合,路径相同的文件会覆盖。
- 支持 Union mount 的 layer 称为 Union Filesystem ,比如 UnionFS、AUFS、OverlayFS 等。
- 使用 Union mount 合并多层 layer 时,只有最上层的那个 layer 为读写模式(称为 top layer),其它 layer 都为 read-only 模式。
新建一个 Docker 容器时,docker daemon 组装 layer 的流程如下:
- 先根据 Docker 镜像创建一个只读模式的 RootFS 文件系统,包含 /bin、/dev、/home 等 FHS 标准目录。
- 然后挂载一层读写模式的 top layer ,内容为空,不包含任何文件。
- 容器内创建、修改的所有文件,都存储在 top layer 中。
- 当容器读取某个路径的文件时,如果在 top layer 中不存在该文件,则会尝试在 read-only layer 中读取该文件。
- 当容器删除 read-only layer 中的文件时,会在 top layer 中相同路径创建一个空文件,覆盖 read-only layer 中的原文件。这个空文件在容器中看不到,但可以在宿主机
/var/lib/docker/overlay2/<cache-id>/diff/
目录中看到。 - 当容器修改 read-only layer 中的文件时,采用写时复制(Copy On Write ,COW)方式,自动将该文件拷贝到 top layer 中,供容器修改,并覆盖 read-only layer 中的原文件。
# overlay2
容器的存储驱动器默认为 overlay2 。参考 官方文档 (opens new window) 。
一个 Docker image 由一层或多层 layer 组成,每层 layer 分别计算一个 SHA256 哈希值作为 id 标识。
- 注意 layer hash 并不是 image hash 。
- 一个 image 在宿主机上存储为一些零散文件,位于以下目录:
/var/lib/docker/image/ # 存储 image、layer 的配置文件 /var/lib/docker/overlay2/ # 存储 layer 的内容
对于镜像中每个 layer ,Docker 会将该 layer 的配置文件存储到
/var/lib/docker/image/overlay2/layerdb/sha256/<layer_hash>/
目录下,例如:[root@CentOS ~]# ls -lh /var/lib/docker/image/overlay2/layerdb/sha256/592f9d458d3943eca6c1bf2b8f98ce5fb7758a75c4e698592e9e4a73407cff31/ -rw-r--r-- 1 root root 64 May 24 13:37 cache-id # 记录该 layer 的缓存目录名 -rw-r--r-- 1 root root 71 May 24 13:37 diff # 记录该 layer 的哈希值,又称为 diff_id -rw-r--r-- 1 root root 71 May 24 13:37 parent # 记录父级 layer 的哈希值。父级 layer 是指构建镜像时,在当前 layer 之前构建的一层 layer -rw-r--r-- 1 root root 3 May 24 13:37 size # 记录该 layer 的体积 -rw-r--r-- 1 root root 340 May 24 13:37 tar-split.json.gz # 一个配置文件,用于从镜像的 tar 包中分离出该 layer 的二进制流
Docker 还会为该 layer 创建一个随机编号的缓存目录
/var/lib/docker/overlay2/<cache-id>/
。例如:[root@CentOS ~]# ls -lh /var/lib/docker/overlay2/1c9408d89ae7884ae7f5aad46118d5af9247b2735f0d98c73686b06d2bce347a/ -rw------- 1 root root 0 May 24 13:37 committed drwxr-xr-x 3 root root 4.0K May 24 13:37 diff/ # 存储该 layer 包含的所有文件,比如 bin、usr 目录 -rw-r--r-- 1 root root 26 May 24 13:37 link # 记录 diff 目录的软链接名称 -rw-r--r-- 1 root root 434 May 24 13:37 lower # 记录在当前 layer 之前构建的所有层 layer 的 diff 目录的软链接名称 drwx------ 2 root root 4.0K May 24 13:37 work/ # 该目录供 OverlayFS 内部使用,通常为空
- 为了避免挂载路径太长,超过 mount 命令的 page size ,Docker 会为每个 diff 目录创建一个名称较短的软链接文件,存储在
/var/lib/docker/overlay2/l/
目录下。
- 为了避免挂载路径太长,超过 mount 命令的 page size ,Docker 会为每个 diff 目录创建一个名称较短的软链接文件,存储在
新建一个容器时,Docker 会为它创建一个随机编号的缓存目录
/var/lib/docker/overlay2/<cache-id>/
。例如:[root@CentOS ~]# ls -lh /var/lib/docker/overlay2/16984e006f624d24b47ce9caa27ce074ecbf48980af0d084497c4a830557aa91 drwxr-xr-x 5 root root 4.0K Jun 25 02:29 diff/ # 存储该容器的 top layer -rw-r--r-- 1 root root 26 Jun 25 02:29 link -rw-r--r-- 1 root root 463 Jun 25 02:29 lower drwxr-xr-x 1 root root 4.0K Jun 25 02:29 merged/ # 存储该容器的 read-only layer drwx------ 3 root root 4.0K Jun 25 02:29 work/
- 根据镜像创建容器时,Docker 会将所有 layer 的 diff 目录,用 mount 命令联合挂载到宿主机的
/var/lib/docker/overlay2/<cache-id>/merged/
目录。 - merged 目录采用 overlay 文件系统,其下的所有文件都是通过硬链接共享镜像 layer 中的文件,因此新增 merged 目录并不会占用新的磁盘空间。
- 执行命令
df | grep overlay
或mount | grep overlay
可查看所有 merged 目录。
- 根据镜像创建容器时,Docker 会将所有 layer 的 diff 目录,用 mount 命令联合挂载到宿主机的
综上,
/var/lib/docker/overlay2/
目录下的缓存目录分为两种用途:- 镜像层(image layer):供镜像使用,diff 目录的内容静态不变。
- 容器层(container layer):供容器使用,diff 目录的内容可能变化,包含 merged 目录。