(第3章)Docker核心原理解读

1.Docker关键内核知识总结

  • Docker通过namespace实现资源隔离
  • Docker通过cgroups实现了资源限制
  • Docker通过写时复制(copy-on-write)实现高效的文件操作

2.namespace资源隔离

  • 一个容器隔离的6种系统调用如下:
    在这里插入图片描述
1)UTS(UNIX Time-sharing System) namepace提供了主机名和域名的隔离,这样每个Docker容器就可以拥有独立的主机名和
域名了,在网络上可被视作一个独立的节点,在非宿主机上的一个进程,指的是hostname

(2)IPC namespace:申请IPC对象就申请了一个全局唯一的32bitID,实际包含:系统IPC标识符和实现POSIX消息队列的文件系统,
在同一个IPC namespace下的进程彼此可见,不同IPC namespace下的进程互相不可见。eg:PostgreSQL实现了该机制,Docker的
IPC namespace实现了容器与宿主机、容器与容器之间的IPC隔离,指的是hostname

(3)PID namespace:对进程PID重新标号,即:两个不同namespace下的进程可以有相同的PID。
包括:
PID namespace中的init进程:当一个Docker容器中运行多个进程,最先启动的命令进程应该是具有资源监控与回收等管理能力的,如bash
信号与init进程,当一个容器内存在多个进程时,容器内的init进程可以对信号进行捕获;
挂载proc文件系统:如果只想看到PID namespace本身应该应该看到的进程,需要重新挂载/proc
mount -t proc proc /proc
ps a
unshare()和setns():创建了PID namespace后,调用者不会进入新的PID namespace,而是随后创建的子进程进入
注:每个PID namespace中的第一个进程PID 1,都会像传统Linux中的init进程一样拥有特权,起特殊作用。

(4)mount namespace
挂载传播:定义了挂载对象之间的关系
挂载关系:共享关系和从属关系
挂载状态:共享挂载(share),从属挂载slave,共享/存储挂载shared and slave,私有挂载private,不可绑定挂载unbindable
eg:如图3-1

在这里插入图片描述

5)network namespace:
一个网络设备最多存在于一个network namespace中,可以通过创建veth pair在不同的network namespace间创建通道,以达到通信的目的。
veth pair:虚拟网络设备对:有2端,类似管道
容器的经典做法是:创建一个veth pair,一端访置在新的namespace中,通常命名为eth0,一段放在原先的namespace中连接物理网络设备,
再通过把多个设备接入网桥或者进行路由转发,来实现通信的目的。eg:3-2

在这里插入图片描述

6)user namespace:
只要用户在启动Docker daemon的时候,指定了--user-remap,那么当用户运行容器时,容器内部的root用户并不等于宿主机的root用户,
而是映射到宿主机上的普通用户。
namespace实际上就是按层次关联起来,每个namespace都发源于最初的root namespac,并与之建立映射,如图3-3
Docker同时使用user namespace和Capability,这在很大程度上加强了容器的安全性。

在这里插入图片描述

  • Linux内核实现namespace的一个目的是:实现轻量级的虚拟化(容器)服务,在同一个namespace下的进程可以感知彼此的变化,而对外界的进程一无所知,以达到独立和隔离的目的。
  • 进行namespace API的4种类方式
    (1)通过clone()在创建新进程的同时创建namespace
    (2)查看/proc/[pid]/ns文件,可以看到指向不同namesoace号的文件,eg:[4026531839]就是namesoace号,若两个进程指向的namespace编号相同,就说明他们在一个namespace下。
    在Docker中,通过文件描述符定位和加入一个存在的namespace是最基本的方式。
    (3)通过setns()加入一个已经存在的namespace
    (4)通过unshare()在原先进程上进行namespace隔离
(2)的eg
ll /proc/$$/ns

等价于
touch ~/uts
mount --bind /proc/27514/ns/uts ~/uts

3.cgroups资源限制

  • cgroups:限制被namespace隔离起来的资源,还可以为资源设置权重,计算使用量,控制任务(进程或线程)启停等

  • 在Linux中,内核本身的调度和管理并不对进程和线程进行区分,只根据clone创建时传入的参数的不同,来从概念上区别进程和线程,统一称之为任务

  • cgroups:control groups
    (1)把任务放到一个组里面统一加以控制,eg:子任务创建之初与其父任务处于同一个cgroups的控制组
    (2)cgroups可以限制、记录任务组使用的物理资源(CPU,Memory,IO等),为容器实现虚拟化提供了基本保证,是构建Docker等一系列虚拟化管理工具的基石。
    (3)本质上,cgroups是内核附加在程序上的一系列钩子hook,通过程序运行时,对资源的调度,来触发相应的钩子以达到资源追踪和限制的目的。

  • cgroups作用:实现不同用户层面的资源的管理
    (1)四大功能:资源限制,优先级分配,资源统计,任务控制

  • cgroups术语表
    (1)task任务:任务表示一个进程或线程
    (2)cgroup控制组:cgroup中的资源控制都是以cgroup为单位实现的。一个任务可以加入某个cgroup,也可以从某个cgroup迁移到另外一个cgroup
    (3)subsystem子系统:资源调度控制器。eg:CPU子系统可以控制CPU时间分配
    (4)hierarchy层级:层级由一系列cgroup,以一个树状结构排练而成,每个层级通过绑定对应的子系统进程资源控制。

  • 传统的Unix任务管理和cgroups的组织结构的区别
    (1)Unix实际先启动init任务作为根节点,再由init节点创建子任务作为子节点,而每个子节点又可以创建新的子节点,从而形成一个树状结构。cgroups也构成类似的树状结构,子节点从父节点继承属性
    (2)多个cgroup构成的层级并非单跟结构,可以允许存在多个。在Docker中,每个子系统独自构成一个层级,这样易于管理。

  • cgroups、任务、子系统、层级四者之间的规则关系
    (1)规则1:CPU和Memory的子系统可以附加到一个层级
    在这里插入图片描述
    (2)规则2:CPU子系统附加到层级A的同时不能再附加到层级B,因为层级B已经附加到了内存子系统。
    如果层级B没有附加过内存子系统,那么CPU子系统同时附加到两个层级是允许的。
    在这里插入图片描述
    (3)规则3:httpd任务已经加入到层级A中的/cg1,而不能加入同一个层级中的/cg2,但是可以加入层级B中的/cg3
    在这里插入图片描述
    (4)规则4:当httpd刚fork出另外一个httpd时,两者在同一个层级中的同一个cgroup中,但是随后ID=4840的httpd需要移动到其它cgroup也是可以的,因为父子任务间已经独立。
    在这里插入图片描述

  • 子系统
    (1)子系统实际上就是cgroups的资源控制系统,每种子系统独立地控制一种资源。
    (2)Docker的9个子系统:blkio,cpu,cpuacct,cpuset,devices,freezer,memory,perf_event,net_cls
    (3)基本操作如下:

ubuntu16.04

Linux的cgroup表现为一个文件系统,需要mount
root@ubuntu:/home/jiwangreal# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)

cpu子系统的控制组下的文件
root@ubuntu:/home/jiwangreal# ls /sys/fs/cgroup/cpu
cgroup.clone_children  cpuacct.stat       cpuacct.usage_percpu       cpuacct.usage_sys   cpu.cfs_quota_us  init.scope         system.slice
cgroup.procs           cpuacct.usage      cpuacct.usage_percpu_sys   cpuacct.usage_user  cpu.shares        notify_on_release  tasks
cgroup.sane_behavior   cpuacct.usage_all  cpuacct.usage_percpu_user  cpu.cfs_period_us   cpu.stat          release_agent      user.slice/sys/fs/cgroup/cpu目录下创建控制组
root@ubuntu:/home/jiwangreal# cd /sys/fs/cgroup/cpu
root@ubuntu:/sys/fs/cgroup/cpu# mkdir cg1
root@ubuntu:/sys/fs/cgroup/cpu# ll cg1
total 0
drwxr-xr-x 2 root root 0 Feb  5 15:58 ./
dr-xr-xr-x 6 root root 0 Feb  5 15:58 ../
-rw-r--r-- 1 root root 0 Feb  5 15:58 cgroup.clone_children
-rw-r--r-- 1 root root 0 Feb  5 15:58 cgroup.procs
-r--r--r-- 1 root root 0 Feb  5 15:58 cpuacct.stat
-rw-r--r-- 1 root root 0 Feb  5 15:58 cpuacct.usage
-r--r--r-- 1 root root 0 Feb  5 15:58 cpuacct.usage_all
-r--r--r-- 1 root root 0 Feb  5 15:58 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 Feb  5 15:58 cpuacct.usage_percpu_sys
-r--r--r-- 1 root root 0 Feb  5 15:58 cpuacct.usage_percpu_user
-r--r--r-- 1 root root 0 Feb  5 15:58 cpuacct.usage_sys
-r--r--r-- 1 root root 0 Feb  5 15:58 cpuacct.usage_user
-rw-r--r-- 1 root root 0 Feb  5 15:58 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 Feb  5 15:58 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 Feb  5 15:58 cpu.shares
-r--r--r-- 1 root root 0 Feb  5 15:58 cpu.stat
-rw-r--r-- 1 root root 0 Feb  5 15:58 notify_on_release
-rw-r--r-- 1 root root 0 Feb  5 15:58 tasks

限制PID=18828进程的cpu使用配额
root@ubuntu:/sys/fs/cgroup/cpu# echo 18828 >> /sys/fs/cgroup/cpu/cg1/tasks
将cpu限制为最高使用20%
root@ubuntu:/sys/fs/cgroup/cpu# echo 20000>/sys/fs/cgroup/cpu/cg1/cpu.cfs_quota_us 

(4)在Docker中,docker daemon会在单独挂载了每一个子系统的控制组目录下创建一个名为docker的控制组,然后在docker控制组里面,再为每个容器创建一个以容器ID为名称的容器控制组,这个容器里的所有进程号都会写到控制组tasks中,并且在控制文件(eg:cpu.cfs_quota_us)中写入预设的限制参数值

eg:/sys/fs/cgroup/cpu/下创建一个名为docker的控制组
一个cgroup创建完成,不管绑定了何种子系统,其目录下都会生成以下的几个文件,用来描述cgroup的相应信息

root@ubuntu:/sys/fs/cgroup/cpu# tree /sys/fs/cgroup/cpu/docker/
/sys/fs/cgroup/cpu/docker/
├── 7df87b1892980b546f7ae7036cc5454efe48ae3a3e83f30a33bf72929edb3462
│   ├── cgroup.clone_children
│   ├── cgroup.procs
│   ├── cpuacct.stat
│   ├── cpuacct.usage
│   ├── cpuacct.usage_all
│   ├── cpuacct.usage_percpu
│   ├── cpuacct.usage_percpu_sys
│   ├── cpuacct.usage_percpu_user
│   ├── cpuacct.usage_sys
│   ├── cpuacct.usage_user
│   ├── cpu.cfs_period_us
│   ├── cpu.cfs_quota_us
│   ├── cpu.shares
│   ├── cpu.stat
│   ├── notify_on_release
│   └── tasks       所有在该cgroup中任务的TID,即:所有的进程或线程ID
├── cgroup.clone_children
├── cgroup.procs		线程组中第一个进程的PID
├── cpuacct.stat
├── cpuacct.usage
├── cpuacct.usage_all
├── cpuacct.usage_percpu
├── cpuacct.usage_percpu_sys
├── cpuacct.usage_percpu_user
├── cpuacct.usage_sys
├── cpuacct.usage_user
├── cpu.cfs_period_us
├── cpu.cfs_quota_us
├── cpu.shares
├── cpu.stat
├── notify_on_release		表示是否在cgroup中最后一个任务退出时,通知运行release agent
└── tasks

1 directory, 32 files

  • cgroups是如何对容器中的进程产生作用的?
    (1)cgroups的实现本质上是给任务挂上钩子,当任务运行的过程中涉及某种资源时,就会触发钩子上所附带的子系统进行监测,依据资源类别的不同,使用对应的技术进行资源限制和优先级分配
    (2)cgroups判断资源超限及超处配额之后的措施:cgroups提供了统一的接口对资源进行控制和统计,Docker默认是开启OOMControl的(内存超限控制)
    (3)cgroup与任务之间的关联关系:cgroup与任务之间是多对多的关系,他们并不直接关联,而是通过一个中间结构把双向的关联信息记录起来。
    (4)Docker在使用cgroup时的注意事项:目前无法将一个新的子系统绑定到激活的层级上,或者从一个激活的层级中解除某个子系统的绑定;只有递归式地卸载层级中的所有cgroup,那个层级才会被真正的卸载。

4.Docker架构

在这里插入图片描述

  • Docker client与Docker daemon建立通信,并将请求发送给后者

  • Docker daemon是Docker架构中的主要用户接口,他提供了API server用于接收来自Docker client的请求

  • Docker通过driver模块来实现对Docker容器执行环境的创建和管理,eg:要为容器创建数据卷colume时,则通过volume模块调用某个具体的volumedriver,来创建一个数据卷并负责后序的挂载操作。

  • 当运行容器的命令执行完毕后,一个实际的容器就处于运行状态,该容器拥有独立的文件系统和相对安全且隔离的运行环境。

  • Docker daemon负责将docker client的用户请求 转译成系统调用,进而创建和管理容器。包括3类接口:
    (1)容器执行驱动execdriver,对namespace,cgroups等所需的系统操作的二次封装
    (2)volumedriver存储驱动,对volume数据卷存储操作的最终执行者,负责volume的增删改查
    (3)镜像存储驱动grapdriver,所有与容器镜像相关操作的最终执行者。

  • network
    (1)通过networkdriver模块以及libcontainer库完成
    (2)网络驱动负责完成具体的操作,包括:创建容器通信所需的网络,容器的network namespace,这个网络所需的虚拟网卡,分配通信所需的IP,服务访问的端口和容器与宿主机之间的端口映射,设置hosts,resove.conf,iptables等

  • docker命令的2种模式: client模式和daemon模式
    (1)client模式:解析flag信息,创建client实例,执行具体的命令(具体过程交给cli/cli.go)
    (2)daemon模式:通过一个server模块(api/server/server.go)接收来自client的请求(该server会通过与daemon对象绑定来接收并处理完成具体的请求,类似于一个API接收器绑定了一个业务逻辑处理器),然后根据请求类型,交由具体的方法去执行;Docker daemon需要在Docker根目录(/var/lib/docker)下创建并初始化一系列跟容器文件系统密切相关的目录和文件。
    在这里插入图片描述

  • Docker daemon网络
    (1)libnetwork实现了host、null、bridge和overlay的驱动。bridge driver不提供跨主机通信的能力,overlay driver提供多主机环境。
    (2)初始化execdriver。execdriver是Docker中用来管理Docker容器的驱动。Docker execdriver运行的root路径默认是:/var/run/docker,Docker运行时的root路径是:/var/lib/docker
    (3)daemon对象:创建daemon对象实例
    (4)恢复已有的Docker容器:当Docker daemon启动时,会去查看在daemon.repository,即/var/lib/docker/containers中的内容
    (5)Docker daemon进程启动的3步:

1)启动一个API server,他工作在用户通过-H指定的socket上面
(2)Docker使用NewDaemon方法创建一个daemon对象来保存信息和处理业务逻辑
(2)最后将API Server和daemon对象绑定起来,接收并处理client的请求
  • 一个运行的daemon是如何相应并处理来自client的请求呢?
    (1)发起请求:client通过反射机制找到CmdRun方法,CmdRun解析用户提供的容器参数,发出创建容器和启动容器的两个POST请求
    (2)创建容器:这里并不创建一个Linux容器 ,它只需要解析用户通过client提交的POST表单,然后使用这些参数在daemon中新建以恶container对象出来
    (3)启动容器:API Servie接收到start请求后,会告诉Docker daemon进行container启动容器操作,Docker daemon调用containerMonitor方法将daemon设置为自己的supervisor,即:告诉daemon进程,请使用本container相关参数作为参数,执行对应execdriver的Run方法
    (4)最后一步,最后执行Run操作的命令,所有与OS打交道的任务全都交给ExecDriver.Run,从而创建namespace,配置cgroup,挂载rootfs
    总结的话就是:execdriver的Run方法通过Docker daemon提交的command信息创建了一份可以供libcontainer解读的容器配置container。

5.libcontainer

  • libcontainer主要分为三大块内容:(1)容器的创建及初始化,(2)容器生命周期管理,(3)进程管理,调用者是Docker的execdriver

  • Docker通过对namesoaces,cgroups,capablities以及文件系统的管理和分配来隔离出一个执行环境,即Docker容器

  • 在Docker daemon提交过来的command中,包含namespace,cgroups以及未来容器中将要运行的进程的重要信息,其中Network,Ipc,Pid等字段描述了隔离容器所需的namespace。等到上述容器配置模板container中 的所有项都按照command提供的内容填好之,一份该容器专属的容器配置container就生成好了。
    container可以理解为libcantaineer与Docker daemon之间进行信息交换的标准格式。

  • libcontainer的工作方式
    主要内容是:Process,Container以及Factory这3个逻辑实体实现的原理,而execdriver或者其它调用者只需要按次序:使用Factory创建逻辑容器Container,启动逻辑容器Container,和用逻辑容器创建物理容器,即可完成Docker容器的创建

  • Docker daemon与容器之间的通信方式
    (1)容器进程启动后需要做初始化工作,使用了namesoace隔离后的两个进程间的通信
    (2)将负责创建容器的进程称之为父进程,将容器进程称之为子进程。 通过pipe(int fd[2]),在fd[1]写入数据,从fd[0]端读取数据,创建的子进程会内嵌这个打开的文件描述符,通信完成的标志在于EOF信号的传递。
    注意:Docker会加入network namespace,所以初始化时网络栈是完全隔离的,所以不能采取sockets通信。
    父进程ParentProcess:负责从物理容器外部处理容器启动工作,与Container对象直接进行交互;
    子进程Process:永无物理容器内进程的配置和IO的管理
    如下图:在libcontainer中,ParentProcess进程与容器进程(cmd,即dockerinit进程)的通信方式如下
    在这里插入图片描述

6.Docker镜像

  • Docker镜像的文件内容及一些运行Docker容器的配置文件组成了Docker容器的静态文件系统运行环境——rootfs
    Docker镜像是Docker容器的静态视角,Docker容器是Docker镜像的运行状态。

  • rootfs是Docker容器在启动时内部进程可见的文件系统,即:Docker容器的根目录,其包含一个OS运行所需的文件系统。

  • 在Docker架构中,Docker daemon为Docker容器挂载rootfs时,将rootfs设置为只读模式。利用联合挂载技术,在已有的只读rootfs上,再挂载一个读写层。

  • Docker镜像的主要特点
    (1)分层:分层是Docker镜像如此轻量的重要原因。
    当需要修改容器镜像内的某个文件时,只对处于最上方的读写层进行变动,不覆盖下层已有文件系统的内容,已有文件在只读层依然存在,但是被读写层的新版文件所隐藏
    (2)写时复制
    未更改文件内容时,所有容器共享同一份数据,只有在Docker容器运行过程中,文件系统发生变化的时候,才把变化的文件内容写到可读写层,并隐藏只读写层中的老版本文件
    (3)内容寻址
    利用基于内容哈希来索引镜像层,在一定程度上减少了ID的冲突,并且增强了镜像层的共享。
    (4)联合挂载
    该技术可以在一个挂载点同时挂载多个文件系统,将挂载点的原目录与被挂载内容进行整合,使得最终可见的文件系统将会包含整合之后的各层的文件和目录。eg:当docker commit这个修改过的容器文件系统为一个新的镜像时,保存的内容仅仅为最上层读写文件系统中被更新过的文件,如下图所示
    在这里插入图片描述

  • Docker镜像的存储组织方式
    可读写部分(read-write layer及及volumes),init-layer,只读层(read-only layer)3部分工通组成了一个容器所需的下层文件系统,通过联合挂载表现为一层。 如下图
    在这里插入图片描述

  • Docker镜像关键概念
    (1)registry:用以保存Docker镜像,还包括镜像层次结构和关于镜像的元数据
    (2)repository:由具有某个功能的Docker镜像的所有迭代版本构成的镜像组
    registry是repository的集合,repository是镜像的集合
    (3)mainfest描述文件:镜像pull到本地Docker宿主机时,转化为本地的镜像配置文件config
    (4)image:镜像的架构(eg:amd64),镜像的默认配置信息,构建镜像的容器配置信息,包含所有镜像层信息的rootfs
    layer:镜像由镜像层组成,存访镜像层的diff_id,size,cache-id和parent等内容,实际的文件内容由存储驱动进行管理
    (5)Dockerfile:通过docker build命令构建自己的Docker镜像所使用的定义文件

  • Docker镜像的构建操作
    (1)docker commit是将容器提交为一个镜像,从容器更新或者构建镜像
    (2)docker build是在一个镜像的基础上构建镜像

  • Docker镜像的分发方法
    (1)直接对容器进行持久化和使用镜像进行持久化的区别:
    docker export用于持久化容器,docker push和docker save用于持久化镜像;
    将容器导出后再导入(export-imported)后的容器会丢失所有的历史,而保存后再加载(saved-loaded)的镜像则没有丢失历史和层,后者可以通过docker tag命令实现历史层回滚!
    (2)pull镜像:Docker client端在pull镜像时,如果用户没有指定tag,则clientt会默认使用latest作为tag
    (3)push镜像
    (4)docker export命令导出容器
    (5)docker save命令保存镜像

7.Docker存储管理

  • Docker镜像将镜像元数据与镜像文件的存储完全隔离开了
    repository与image这两类元数据并无物理上的镜像文件与之对应,而layer这种元数据则存在于物理上的镜像层文件与之对应
  • (1)repository元数据:
    repository在本地的持久化文件放置于:
每个人存访的位置细节上,可能不太一样
root@ubuntu:/var/lib/docker/image/overlay2# pwd
/var/lib/docker/image/overlay2
root@ubuntu:/var/lib/docker/image/overlay2# cat repositories.json |python -mjson.tool
{
    "Repositories": {
        "busybox": {
            "busybox:latest": "sha256:6d5fcfe5ff170471fcc3c8b47631d6d71202a1fd44cf3c147e50c8de21cf0648",
            "busybox@sha256:6915be4043561d64e0ab0f8f098dc2ac48e077fe23f488ac24b665166898115a": "sha256:6d5fcfe5ff170471fcc3c8b47631d6d71202a1fd44cf3c147e50c8de21cf0648"
        },
        "django": {
            "django:latest": "sha256:eb40dcf64078249a33f68fdd8d80624cb81b524c24f50b95fff5c2b40bdc3fdc",
            "django@sha256:5bfd3f442952463f5bc97188b7f43cfcd6c2f631a017ee2a6fca3cb8992501e8": "sha256:eb40dcf64078249a33f68fdd8d80624cb81b524c24f50b95fff5c2b40bdc3fdc"
        },
        "haproxy": {
            "haproxy:latest": "sha256:9b5ff4593b4e4cbc4506e7ca65f1ffd78aecd97cbf15cb32eda4a0baa1ebf451",
            "haproxy@sha256:6f921726b32ac07177885c8ed180d07d0da322aecc11b1ade75c4aa31ee686a8": "sha256:9b5ff4593b4e4cbc4506e7ca65f1ffd78aecd97cbf15cb32eda4a0baa1ebf451"
        },
        "hello-world": {
            "hello-world:latest": "sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e",
            "hello-world@sha256:9572f7cdcee8591948c2963463447a53466950b3fc15a247fcad1917ca215a2f": "sha256:fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e"
        },
        "httpd": {
            "httpd:latest": "sha256:c2aa7e16edd855da8827aa0ccf976d1d50f0827c08622c16e0750aa1591717e5",
            "httpd@sha256:769018135ba22d3a7a2b91cb89b8de711562cdf51ad6621b2b9b13e95f3798de": "sha256:c2aa7e16edd855da8827aa0ccf976d1d50f0827c08622c16e0750aa1591717e5"
        },
        "redis": {
            "redis:latest": "sha256:44d36d2c2374b240abcf5da2130abf49938b8fb49446df6eec028718520332ef",
            "redis@sha256:7b84b346c01e5a8d204a5bb30d4521bcc3a8535bbf90c660b8595fad248eae82": "sha256:44d36d2c2374b240abcf5da2130abf49938b8fb49446df6eec028718520332ef"
        },
        "ubuntu": {
            "ubuntu:latest": "sha256:ccc6e87d482b79dd1645affd958479139486e47191dfe7a997c862d89cd8b4c0",
            "ubuntu@sha256:8d31dad0c58f552e890d68bbfb735588b6b820a46e459672d96e585871acc110": "sha256:ccc6e87d482b79dd1645affd958479139486e47191dfe7a997c862d89cd8b4c0"
        }
    }
}

  • (2)image 元数据
    imageStore则是管理镜像ID与镜像元数据之间的映射关系以及元数据的持久化操作
每个人存访的位置细节上,可能不太一样
root@ubuntu:/var/lib/docker/image/overlay2/imagedb/metadata/sha256# 
  • (3)layer元数据
    用户在Docker宿主机上下载了某个镜像层之后,Docker会在宿主机上基于镜像层文件包和image元数据,构建本地的layer元数据,包括:diff,parent,size等;
    当Docker将在宿主机上产生的镜像层上传到registry时,与新镜像层相关的宿主机上的元数据不会与镜像层一块打包上传。
持久化文件的位置:
全都是mountID
root@ubuntu:/var/lib/docker/image/overlay2/layerdb/mounts# ll
total 48
drwxr-xr-x 12 root root 4096 Feb  5 16:08 ./
drwx------  5 root root 4096 Jan 23 21:11 ../
drwxr-xr-x  2 root root 4096 Jan 23 21:11 0ebb039b2e2857e093bce2ebed4953436473d04d7a29a0ecdc2e30eca8ca6544/
drwxr-xr-x  2 root root 4096 Feb  4 15:38 171b215cd75aafadc82c61f83ed44290e87830fb4cf03cad2a1e3908a05d29d1/
drwxr-xr-x  2 root root 4096 Feb  5 16:08 7df87b1892980b546f7ae7036cc5454efe48ae3a3e83f30a33bf72929edb3462/
drwxr-xr-x  2 root root 4096 Feb  4 15:37 8eb9fd0012b3ab2475fa37a1aa081252544d5941403301dd8f68c9a722f6e551/
drwxr-xr-x  2 root root 4096 Feb  4 15:35 c3c46778a8acd1eb6378459f26c229f53bb58415d5cc5ca6528232b299eb2dfe/
drwxr-xr-x  2 root root 4096 Jan 23 21:14 ca318115d2728b1e5a0d351c4a9291a266d382057c4606d78f77808d5cdab139/
drwxr-xr-x  2 root root 4096 Feb  4 15:37 d5ff3ca4e01fd66019def444b81ccdd8ee69e1a19254ee7f86ba14ca8b436aa4/
drwxr-xr-x  2 root root 4096 Feb  5 16:07 f6282cc03404fbf2aa1e7db0330096d4302854bd81bfdd18bbafafc76eb5ea28/
drwxr-xr-x  2 root root 4096 Feb  4 15:36 f9c829a376f56a8b529cfa56a2e5951d5c8eaca7c29994a29f4965152e2d288d/
drwxr-xr-x  2 root root 4096 Feb  4 15:36 ff8329b53facdc58674e0af62e1785bd4b63c3ec6096bfe72eaa1bde15102767/
  • Docker存储驱动
    (1)存储驱动依据OS底层的支持,提供了针对某种文件系统的初始化操作以及对镜像层的增删改查和差异比较等操作
    (2)Docker中管理文件系统的驱动为graphdriver,定义了Driver和ProtoDriver两个接口
    docker daemon -s some_driver_name
  • (1)aufs存储驱动:这些目录的挂载是分层次的,通常最上层是可读可写的,下层是只读层
    Docker容器的文件系统有3层:可读写层(将来被commit的内容),init层和只读层。但是这不影响我们传统认识的可读写层+只读写层所组成的容器文件系统,因为init层对于用户来说是完全透明的。
    这些目录的挂载是分层次的,通常来说最上层是可读写层,下面的层是只读层。所以,aufs 的每一层都是一个普通的文件系统。
参考:
https://www.cnblogs.com/sparkdev/p/9121188.html
https://arkingc.github.io/2017/04/13/2017-04-13-docker-filesystem-aufs/1)ubuntu的存储驱动默认是overlay2,首先修改成aufs
参考:https://blog.csdn.net/shida_csdn/article/details/80784132
root@ubuntu:/home/jiwangreal# vim /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd --storage-driver=aufs -H fd:// --containerd=/run/containerd/containerd.sock

root@ubuntu:/home/jiwangreal# systemctl daemon-reload &&systemctl restart docker
root@ubuntu:/home/jiwangreal# docker info|grep aufs
WARNING: No swap limit support
WARNING: the aufs storage-driver is deprecated, and will be removed in a future release.
 Storage Driver: aufs
  Root Dir: /var/lib/docker/aufs

(2)repository 元数据
repository 是由具有某个功能的 docker 镜像的所有迭代版本构成的镜像库。
Repository 在本地的持久化文件存放于 /var/lib/docker/image/<graph_driver>/repositories.json 中,下面
显示了 docker 使用 aufs 存储驱动时 repositories.json 文件的路径:
root@ubuntu:/var/lib/docker/image/aufs# ls
distribution  imagedb  layerdb  repositories.json
root@ubuntu:/var/lib/docker/image/aufs# cat repositories.json|python -mjson.tool
{
    "Repositories": {
        "ubuntu": {
            "ubuntu:latest": "sha256:ccc6e87d482b79dd1645affd958479139486e47191dfe7a997c862d89cd8b4c0",
            "ubuntu@sha256:8d31dad0c58f552e890d68bbfb735588b6b820a46e459672d96e585871acc110": "sha256:ccc6e87d482b79dd1645affd958479139486e47191dfe7a997c862d89cd8b4c0"
        }
    }
}
文件中存储了所有本地镜像的 repository 的名字,比如 ubuntu ,还有每个 repository 
下的镜像的名字、标签及其对应的镜像 ID。
当前 docker 默认采用 SHA256 算法根据镜像元数据配置文件计算出镜像 ID。
上面的两条记录本质上是一样的,第二条记录和第一条记录指向同一个镜像 ID。
其中ubuntu@sha256:8d31dad0c58f552e890d68bbfb735588b6b820a46e459672d96e585871acc110被称为
镜像的摘要,在拉取镜像时可以看到它:
{"Repositories":{}}root@ubuntu:/var/lib/docker/image/aufs# docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
5c939e3a4d10: Pull complete 
c63719cdbe7a: Pull complete 
19a861ea6baf: Pull complete 
651c9d2d6c4f: Pull complete 
Digest: sha256:8d31dad0c58f552e890d68bbfb735588b6b820a46e459672d96e585871acc110
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest

(3)image 元数据
image 元数据包括了镜像架构(如 amd64)、操作系统(如 linux)、镜像默认配置、
构建该镜像的容器 ID 和配置、创建时间、创建该镜像的 docker 版本、构建镜像的历史信息以及 
rootfs 组成。

其中构建镜像的历史信息和 rootfs 组成部分除了具有描述镜像的作用外,
还将镜像和构成该镜像的镜像层关联了起来。

Docker 会根据历史信息和 rootfs 中的 diff_ids 计算出构成该镜像的镜像层的存储索引 chainID,
这也是 docker 1.10 镜像存储中基于内容寻址的核心技术。

镜像 ID 与镜像元数据之间的映射关系以及元数据被保存在文件 /var/lib/docker/image/<graph_driver>/imagedb/content/sha256/<image_id> 中。
root@ubuntu:/var/lib/docker/image/aufs/imagedb/content/sha256# ll
total 12
drwx------ 2 root root 4096 Feb  7 09:27 ./
drwx------ 3 root root 4096 Feb  7 09:22 ../
-rw------- 1 root root 3407 Feb  7 09:27 ccc6e87d482b79dd1645affd958479139486e47191dfe7a997c862d89cd8b4c0
上面的内容就是镜像的ID

root@ubuntu:/var/lib/docker/image/aufs/imagedb/content/sha256# cat ccc6e87d482b79dd1645affd958479139486e47191dfe7a997c862d89cd8b4c0 |python -mjson.tool
。。。。
    "os": "linux",
    "rootfs": {
        "diff_ids": [
            "sha256:43c67172d1d182ca5460fc962f8f053f33028e0a3a1d423e05d91b532429e73d",
            "sha256:21ec61b65b20ec53a1b7f069fd04df5acb0e75434bd3603c88467c8bfc80d9c6",
            "sha256:1d0dfb259f6a31f95efcba61f0a3afa318448890610c7d9a64dc4e95f9add843",
            "sha256:f55aa0bd26b801374773c103bed4479865d0e37435b848cb39d164ccb2c3ba51"
        ],
        "type": "layers"
    }
}

它包含所有镜像层信息的 rootfs(见上图的 rootfs 部分),docker 利用 rootfs 中的 diff_id 
计算出内容寻址的索引(chainID) 来获取 layer 相关信息,进而获取每一个镜像层的文件内容。

注意,每个 diff_id 对应一个镜像层。上面的 diff_id 的排列也是有顺序的,
从上到下依次表示镜像层的最低层到最顶层:


(4)layer 元数据
镜像层只包含一个具体的镜像层文件包。用户在 docker 宿主机上下载了某个镜像层之后,
docker 会在宿主机上基于镜像层文件包和 image 元数据构建本地的 layer 元数据,
包括 diff、parent、size 等。

而当 docker 将在宿主机上产生的新的镜像层上传到 registry 时,
与新镜像层相关的宿主机上的元数据也不会与镜像层一块打包上传。

Docker 中定义了 Layer 和 RWLayer 两种接口,分别用来定义只读层和可读写层的一些操作,
又定义了 roLayer 和 mountedLayer,分别实现了上述两种接口。
其中,roLayer 用于描述不可改变的镜像层,mountedLayer 用于描述可读写的容器层。

具体来说,roLayer 存储的内容主要有索引该镜像层的 chainID、该镜像层的校验码 diffID、
父镜像层 parent、graphdriver 存储当前镜像层文件的 cacheID、该镜像层的 size 等内容。

这些元数据被保存在 /var/lib/docker/image/<graph_driver>/layerdb/sha256/<chainID>/ 文件夹下,目录下的目录名称都是镜像层的存储索引 chainID
root@ubuntu:/var/lib/docker/image/aufs/layerdb/sha256# ll
total 24
drwxr-xr-x 6 root root 4096 Feb  7 09:27 ./
drwx------ 4 root root 4096 Feb  7 09:27 ../
drwx------ 2 root root 4096 Feb  7 09:27 340bed96497252624f5e4b0f42accfe7edbb7a01047e2bb5a8142b2464008e73/
drwx------ 2 root root 4096 Feb  7 09:27 43c67172d1d182ca5460fc962f8f053f33028e0a3a1d423e05d91b532429e73d/
drwx------ 2 root root 4096 Feb  7 09:27 6357c335cdfcc3a120e288bbd203bf4c861a14245ce5094634ee097e5217085b/
drwx------ 2 root root 4096 Feb  7 09:27 d1b7fedd4314279a7c28d01177ff56bcff65300f6d41655394bf5d8b788567f6/

存储索引 chainID 目录下的内容为:
root@ubuntu:/var/lib/docker/image/aufs/layerdb/sha256# ll 340bed96497252624f5e4b0f42accfe7edbb7a01047e2bb5a8142b2464008e73/
total 28
drwx------ 2 root root 4096 Feb  7 09:27 ./
drwxr-xr-x 6 root root 4096 Feb  7 09:27 ../
-rw-r--r-- 1 root root   64 Feb  7 09:27 cache-id
-rw-r--r-- 1 root root   71 Feb  7 09:27 diff
-rw-r--r-- 1 root root   71 Feb  7 09:27 parent
-rw-r--r-- 1 root root    3 Feb  7 09:27 size
-rw-r--r-- 1 root root 1467 Feb  7 09:27 tar-split.json.gz
其中 diffID 和 size 可以通过镜像层包计算出来(diff 文件的内容即 diffID,
其内容就是 image 元数据中对应层的 diff_id)。

chainID 和父镜像层 parent 需要从所属 image 元数据中计算得到。
而 cacheID 是在当前 docker 宿主机上随机生成的一个 uuid,在当前的宿主机上,
cacheID 与该镜像层一一对应,用于标识并索引 graphdriver 中的镜像层文件:

mountedLayer 存储的内容主要为:
索引某个容器的可读写层(也叫容器层)的 ID(也对应容器层的 ID)、
容器 init 层在 graphdriver 中的ID(initID)、
读写层在 graphdriver 中的 ID(mountID) 以及容器层的父层镜像的 chainID(parent)。
root@ubuntu:/var/lib/docker/image/aufs/layerdb/mounts/1434657d0976bbdc6a15d6ca3f1d4edc18f887213e5497818e687465dd09a8cf# ll
total 20
drwxr-xr-x 2 root root 4096 Feb  7 09:56 ./
drwxr-xr-x 3 root root 4096 Feb  7 09:56 ../
-rw-r--r-- 1 root root   69 Feb  7 09:56 init-id
-rw-r--r-- 1 root root   64 Feb  7 09:56 mount-id
-rw-r--r-- 1 root root   71 Feb  7 09:56 parent

(5)那么镜像文件在本地存放在哪里呢?
drwx------  5 root root 4096 Feb  7 09:22 ./
drwx--x--x 15 root root 4096 Feb  7 09:22 ../
drwx------  8 root root 4096 Feb  7 09:56 diff/
drwx------  2 root root 4096 Feb  7 09:56 layers/
drwx------  8 root root 4096 Feb  7 09:56 mnt/
其中 mnt 为 aufs 的挂载目录,
diff 为实际的数据来源,包括只读层和可读写层,
所有这些层最终一起被挂载在 mnt 下面的目录上,layers 下为与每层依赖有关的层描述文件。

最初,mnt 和 layers 都是空目录,文件数据都在 diff 目录下。
一个 docker 容器创建与启动的过程中,会在 /var/lib/docker/aufs 下面新建出对应的文件和目录。

由于 docker 镜像管理部分与存储驱动在设计上完全分离了,
镜像层或者容器层在存储驱动中拥有一个新的标识 ID,在镜像层(roLayer)中称为 cacheID,
容器层(mountedLayer)中为 mountID。

在 Linux 环境下,mountID 是随机生成的并保存在 mountedLayer 的元数据 mountID 中,持久化在 image/aufs/layserdb/mounts/<container_id>/mount-id 中

下面以 mountID 为例,创建一个新读写层的结果如下:
root@ubuntu:/var/lib/docker/aufs/layers# ll
total 28
drwx------ 2 root root 4096 Feb  7 09:56 ./
drwx------ 5 root root 4096 Feb  7 09:22 ../
-rw-r--r-- 1 root root  330 Feb  7 09:56 06394379d741c96068edd55e598c1f31bc9b89c97d78d6d5da74bdce50faf77c
-rw-r--r-- 1 root root  260 Feb  7 09:56 06394379d741c96068edd55e598c1f31bc9b89c97d78d6d5da74bdce50faf77c-init
-rw-r--r-- 1 root root  130 Feb  7 09:27 16f9270d0924771f8e389ff5f30738285d63ff46e4015b2dc1cfe592fbf9d814
-rw-r--r-- 1 root root   65 Feb  7 09:27 4139a0b6d660f5602780be33fdbeb3fd9ba4e867949a7b38c1f135ad803b6740
-rw-r--r-- 1 root root  195 Feb  7 09:27 59bc27fd2691f4cfdec635003f8fb71fc06d1c786d976d4283bb0a78e42315df
-rw-r--r-- 1 root root    0 Feb  7 09:27 65fa2339190e9406ce6dfeec14f0900663a4aa9a79ba7203bdfda5ac71526f9c

上面结果中 06394379d741c96068edd55e598c1f31bc9b89c97d78d6d5da74bdce50faf77c 为 mountID。
随后 GraphDriver 会将 diff 中属于容器镜像的所有层目录以只读方式挂载到 mnt 下,
然后在 diff 中生成一个以当前容器对应的 <mountID>-init 命名的文件夹作为最后一层只读层
root@ubuntu:/var/lib/docker/aufs/diff# ll
total 32
drwx------  8 root root 4096 Feb  7 09:56 ./
drwx------  5 root root 4096 Feb  7 09:22 ../
drwxr-xr-x  4 root root 4096 Feb  7 09:56 06394379d741c96068edd55e598c1f31bc9b89c97d78d6d5da74bdce50faf77c/
drwxr-xr-x  6 root root 4096 Feb  7 09:56 06394379d741c96068edd55e598c1f31bc9b89c97d78d6d5da74bdce50faf77c-init/
drwxr-xr-x  6 root root 4096 Feb  7 09:27 16f9270d0924771f8e389ff5f30738285d63ff46e4015b2dc1cfe592fbf9d814/
drwxr-xr-x  3 root root 4096 Feb  7 09:27 4139a0b6d660f5602780be33fdbeb3fd9ba4e867949a7b38c1f135ad803b6740/
drwxr-xr-x  3 root root 4096 Feb  7 09:27 59bc27fd2691f4cfdec635003f8fb71fc06d1c786d976d4283bb0a78e42315df/
drwxr-xr-x 21 root root 4096 Feb  7 09:27 65fa2339190e9406ce6dfeec14f0900663a4aa9a79ba7203bdfda5ac71526f9c/

接下来会在 diff 中生成一个以容器对应 mountID 为名的可读写目录,也挂载到 mnt 目录下。
所以,将来用户在容器中新建文件就会出现在 mnt 下一 mountID 为名的目录下,
而该层对应的实际内容则保存在 diff 目录下。

至此我们需要明确,所有文件的实际内容均保存在 diff 目录下,
包括可读写层也会以 mountID 为名出现在 diff 目录下,最终会整合到一起联合挂载到 mnt 目录下
以 mountID 为名的文件夹下。

(6)接下来我们统一观察 mnt 对应的 mountID 下的变化。
先创建一个容器:
root@ubuntu:/var/lib/docker/aufs/diff# docker container create -it --name mycon ubuntu bash
0a4b299bb8e8a27dad1f5d5ae54bcec44a4c65987561fe65d528993780192770
root@ubuntu:/var/lib/docker/aufs/diff# 
root@ubuntu:/var/lib/docker/aufs/diff# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
0a4b299bb8e8        ubuntu              "bash"              5 seconds ago       Created                                 mycon

比如我们得到的容器 ID 为:0a4b299bb8e8a27dad1f5d5ae54bcec44a4c65987561fe65d528993780192770,
此时容器的状态为 "Created"。

然后在 /var/lib/docker/image/aufs/layerdb/mounts 目录中,查看 0a4b299bb8e8a27dad1f5d5ae54bcec44a4c65987561fe65d528993780192770

目录下 mount-id 文件的内容如下:
root@ubuntu:/var/lib/docker/image/aufs/layerdb/mounts/0a4b299bb8e8a27dad1f5d5ae54bcec44a4c65987561fe65d528993780192770# cat mount-id 
443c2a7e1e731ae1347d00a76385bfb71881b4f6635440caa55c1063884c8d4broot@ubuntu:/var/lib/docker/image/aufs/layerdb/mounts/0a4b299bb8e8a27dad1f5d5ae54bcec44a4c65987561fe65d528993780192770# 

这就是容器层对应的 mountID。接下来查看容器运行前对应的 mnt 目录:
root@ubuntu:/var/lib/docker/aufs/mnt# du -h . --max-depth=1|grep 443c2a
4.0K	./443c2a7e1e731ae1347d00a76385bfb71881b4f6635440caa55c1063884c8d4b
4.0K	./443c2a7e1e731ae1347d00a76385bfb71881b4f6635440caa55c1063884c8d4b-init
此时 mountID 对应的文件夹下是空的


接着,启动一个容器
root@ubuntu:/var/lib/docker/aufs/mnt# docker container start -i mycon
root@0a4b299bb8e8:/# read escape sequence
root@ubuntu:/var/lib/docker/aufs/mnt# 
root@ubuntu:/var/lib/docker/aufs/mnt# du -h . --max-depth=1|grep 443c2a
70M	./443c2a7e1e731ae1347d00a76385bfb71881b4f6635440caa55c1063884c8d4b
4.0K	./443c2a7e1e731ae1347d00a76385bfb71881b4f6635440caa55c1063884c8d4b-init

容器层变大了,进入到文件夹中可以看到挂载好的文件系统:
root@0a4b299bb8e8:/# ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

接着,在容器中创建文件
下面我们进入到容器中,创建一个 1G 大小的文件:
root@0a4b299bb8e8:/# dd if=/dev/zero of=test.txt bs=1M count=1024
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 28.384 s, 37.8 MB/s
root@0a4b299bb8e8:/# du -h test.txt 
1.1G	test.txt

此时再来查看 mnt 下对应目录的大小:
root@ubuntu:/var/lib/docker/aufs/mnt# du -h . --max-depth=1|grep 443c2a
1.1G	./443c2a7e1e731ae1347d00a76385bfb71881b4f6635440caa55c1063884c8d4b
4.0K	./443c2a7e1e731ae1347d00a76385bfb71881b4f6635440caa55c1063884c8d4b-init


最后,停止容器
root@ubuntu:/var/lib/docker/aufs/mnt# docker container stop mycon
mycon

停止容器后,/var/lib/docker/aufs/mnt 目录下对应的 mountID 目录被卸载(umount),
此时该目录为空。
root@ubuntu:/var/lib/docker/aufs/mnt# du -h . --max-depth=1|grep 443c2a
4.0K	./443c2a7e1e731ae1347d00a76385bfb71881b4f6635440caa55c1063884c8d4b
4.0K	./443c2a7e1e731ae1347d00a76385bfb71881b4f6635440caa55c1063884c8d4b-init

但是 /var/lib/docker/aufs/diff 目录下对应的目录和文件都还存在。
root@ubuntu:/var/lib/docker/aufs/diff# du -h . --max-depth=1|grep 443c2a
1.1G	./443c2a7e1e731ae1347d00a76385bfb71881b4f6635440caa55c1063884c8d4b
28K	./443c2a7e1e731ae1347d00a76385bfb71881b4f6635440caa55c1063884c8d4b-init

注意:下面是aufs的image元数据中的所有镜像层的rootfs,其diff_id可能对不上,重在理解
在这里插入图片描述
理解 docker aufs 驱动的主要存储目录和作用:
最后,当我们用 docker container commit 命令把容器提交成镜像后,就会在 diff 目录下生成一个新的 cacheID 命名的文件夹,它存放了最新的差异变化文件,这时一个新的镜像层就诞生了。
而原来的以 mountID 为名的文件夹会继续存在,直至对应容器被删除。
在这里插入图片描述

  • (2)Device Mapper
    这是一种从逻辑设备到物理设备的映射框架机制;
    Device Mapper本质功能就是:依据映射关系描述IO处理规则,当映射设备接收到IO请求时,该IO请求会依据映射表逐级转发,直到该请求最终传到最底层的物理设备上。
    在这里插入图片描述

在这里插入图片描述

1)devicemapper镜像层结构解释
devicemapper存储驱动是使用Device Mapper的精简配置和快照功能实现镜像的分层。
资源池:使用了2个块设备:一个用于存储数据,另一个用于存储元数据,构建资源池用以创建其它存储镜像的块设备
数据区为生成其他块设备提供资源;
元信息存储了虚拟设备和物理设备的映射关系;
所有的容器层和镜像层都有自己的块设备,均是通过从其父镜像层创建快照的方式来创建,父镜像层的层从基础设备创建快照。

由于需要在块设备中建卷等操作,我用的虚拟机没法建立卷,因为已经挂载的卷我不敢动
(2)与aufs一样,
在/var/lib/docker/devicemapper下有:
/mnt为设备的额挂载目录
metadata下存储了每个设备驱动层的元数据信息

在/var/lib/docker/image/devicemappe目录下
存镜像和逻辑镜像层的元数据

  • (3)OverlayFS:允许用户将一个文件系统与另一个文件系统重叠overlay,在上层的文件系统中记录更改,而下层的文件系统保持不变。Docker中的overlay驱动如下图
在上述图中可以看到三个层结构,即:lowerdir、uperdir、merged,
其中lowerdir是只读的image layer,其实就是rootfs,对比我们上述演示的目录A和B,
我们知道image layer可以分很多层,所以对应的lowerdir是可以有多个目录。

而upperdir则是在lowerdir之上的一层,这层是读写层,在启动一个容器时候会进行创建,
所有的对容器数据更改都发生在这里层,对比示例中的C。

最后merged目录是容器的挂载点,也就是给用户暴露的统一视角,对比示例中的/tmp/test。
而这些目录层都保存在了/var/lib/docker/overlay2/或者/var/lib/docker/overlay/
(如果使用overlay)

在这里插入图片描述

OverlayFS文件系统的相关测试如下
(1)查询内核中是否存在overlay模块
root@ubuntu:/# lsmod |grep overlay
overlay                77824  0

参考:https://www.cnblogs.com/wdliu/p/10483252.html
overlayfs通过三个目录:lower目录、upper目录、以及work目录实现,
其中lower目录可以是多个,work目录为工作基础目录,挂载后内容会被清空,
且在使用过程中其内容用户不可见,最后联合挂载完成给用户呈现的统一视图称为为merged目录
创建三个目录A、B、C,以及worker目录:
root@ubuntu:/tmp/demo# mkdir -p /tmp/test A/aa B C worker
root@ubuntu:/tmp/demo# echo "from A">A/a.test
root@ubuntu:/tmp/demo# echo "from B">B/b.test
root@ubuntu:/tmp/demo# echo "from C">C/c.test
root@ubuntu:/tmp/demo# tree .
.
├── A
│   ├── aa
│   └── a.test
├── B
│   └── b.test
├── C
│   └── c.test
└── worker

5 directories, 3 files

使用mount命令挂载
root@ubuntu:/tmp/demo# mount -t overlay overlay -o lowerdir=A:B,upperdir=C,workdir=work /tmp/test/

然后我们再去查看/tmp/test目录,你会发现目录A、B、C被合并到了一起,并且相同文件名的文
件会进行“覆盖”,这里覆盖并不是真正的覆盖,而是当合并时候目录中两个文件名称都相同时,
merged层目录会显示离它最近层的文件:
root@ubuntu:/tmp/demo#cd /tmp/test
root@ubuntu:/tmp/test#tree .
.
├── aa
├──	a.text
├── b.text
├── c.text

root@ubuntu:/tmp/test# cat a.text
from A

(2)容器的演示
root@ubuntu:/etc/docker# vim /lib/systemd/system/docker.service
修改的内容:
ExecStart=/usr/bin/dockerd --storage-driver=overlay2 -H fd:// --containerd=/run/containerd/containerd.sock
root@ubuntu:/etc/docker# systemctl daemon-reload &&systemctl restart docker



root@ubuntu:/var/lib/docker/image/overlay2/layerdb/mounts# docker info|grep overlay
WARNING: No swap limit support
 Storage Driver: overlay2
  Network: bridge host ipvlan macvlan null overlay

root@ubuntu:/tmp/demo# docker start 729f0d3454a8

查看其overlay挂载点,可以发现其挂载的merged目录、lowerdir、upperdir以及workdir:
root@ubuntu:/tmp/demo# mount |grep overlay2
overlay on /var/lib/docker/overlay2/b290f8a0d1e53dc7e7d495b347a30f1289e351ccd330fd5da0d794eefb4a7926/merged type overlay (rw,relatime,
lowerdir=/var/lib/docker/overlay2/l/OVHT2GRNO2EZLZG33UPVZJONJR:/var/lib/docker/overlay2/l/M6LDBD7J7RZBSYKQ52RALHYCFR:/var/lib/docker/overlay2/l/JZ3U2U6V2ODPCH3HYTR4RH3BF7:/var/lib/docker/overlay2/l/MLRL6AS5MXCMHRM3OC35Y5LRAV:/var/lib/docker/overlay2/l/YUWOUIQMUCDU4D5MYNGWW473GL,
upperdir=/var/lib/docker/overlay2/b290f8a0d1e53dc7e7d495b347a30f1289e351ccd330fd5da0d794eefb4a7926/diff,
workdir=/var/lib/docker/overlay2/b290f8a0d1e53dc7e7d495b347a30f1289e351ccd330fd5da0d794eefb4a7926/work)3)当容器中发生数据修改时候overlayfs存储驱动又是如何进行工作的?
读:
如果文件在容器层(upperdir),直接读取文件;
如果文件不在容器层(upperdir),则从镜像层(lowerdir)读取;

修改:
首次写入: 如果在upperdir中不存在,overlay和overlay2执行copy_up操作,
把文件从lowdir拷贝到upperdir,由于overlayfs是文件级别的(即使文件只有很少的一点修改,
也会产生的copy_up的行为),后续对同一文件的在此写入操作将对已经复制到容器的文件的副本进行操作。
这也就是常常说的写时复制(copy-on-write)
删除文件和目录: 当文件在容器被删除时,在容器层(upperdir)创建whiteout文件,
镜像层(lowerdir)的文件是不会被删除的,因为他们是只读的,但without文件会阻止他们显示,
当目录在容器内被删除时,在容器层(upperdir)一个不透明的目录,
这个和上面whiteout原理一样,阻止用户继续访问,即便镜像层仍然存在。 

注意事项
(a)copy_up操作只发生在文件首次写入,以后都是只修改副本,
(b)overlayfs只适用两层目录,,相比于比AUFS,查找搜索都更快。
(c)容器层的文件删除只是一个“障眼法”,是靠whiteout文件将其遮挡,image层并没有删除,
这也就是为什么使用docker commit 提交保存的镜像会越来越大,无论在容器层怎么删除数据,
image层都不会改变。 

(4)overlay2镜像存储结构
pull的镜像的所有层都在这里
root@ubuntu:/tmp/demo# ll /var/lib/docker/overlay2/
total 184
drwx------ 46 root root 4096 Feb  6 16:54 ./
drwx--x--x 14 root root 4096 Feb  6 16:54 ../
drwx------  4 root root 4096 Feb  4 23:18 123c6e8619f78d854a9508d83209b239d0cd45a8df9d4dfb1a597b0c03e7b5af/
drwx------  4 root root 4096 Feb  4 15:38 123c6e8619f78d854a9508d83209b239d0cd45a8df9d4dfb1a597b0c03e7b5af-init/
....

这里面多了一个l目录包含了所有层的软连接,短链接使用短名称,避免mount时候参数达到页面大小限制
root@ubuntu:/tmp/demo# ll /var/lib/docker/overlay2/l
total 180
drwx------  2 root root 4096 Feb  6 16:21 ./
drwx------ 46 root root 4096 Feb  6 16:54 ../
lrwxrwxrwx  1 root root   72 Feb  1 16:40 456JAXBHVS3JHXRZFZ4LXP4OOZ -> ../ac90d6ede0b864ef666050b1e8980d41c5fb8daa9a01459bb2494d3fa549a9b1/diff/
lrwxrwxrwx  1 root root   72 Feb  4 15:37 4CSNIDNNPSX2NO7OQ6TUCC6VSW -> ../6edfae29cca3fd2d621d18658cc22a9e02697b86c66af60de05538b307449216/diff/
lrwxrwxrwx  1 root root   72 Feb  3 17:28 5YY33SJ3A3JNXPT2HHINC5NCRR -> ../4ea285ea7a639ae2c7f2957d42fa42e99cb043c1f9c40c6116e4b39e991763a4/diff/
lrwxrwxrwx  1 root root   72 Feb  3 17:21 65ZYEDM4K772KYCMM7FM2UCUVY -> ../ea812cc72aafe62062855a1ed4627276dd543a3d792a1824be3e6374f93756f4/diff/
lrwxrwxrwx  1 root root   72 Feb  1 17:28 67V34XWNBPMSZHLPENWF2V24HZ -> ../b0ef105b999d7c79b14597bd36954768b04dbe93f6aa100f8a0c7ae333ce3224/diff/
lrwxrwxrwx  1 root root   72 Feb  4 15:36 6BOFETT6YGPKBKQROMSRS5W4KK -> ../1a6d97004e6e9087f0e8516072afe2db07709818d1eb009ed1a48fd8025d668e/diff/
lrwxrwxrwx  1 root root   72 Feb  5 16:07 6NBISRY3PPDIWABZI3YIPPSOI3 -> ../aa72293e2126cffe4742775f212c489512d540f39e01b5b3a330ea2c1658a4fe/diff/
lrwxrwxrwx  1 root root   72 Feb  4 15:37 6SLLLZPLJYRI6LI3VPDKHZZTJQ -> ../c69b5f0de3677526dbb0c635e157cac4b2612e4d9b39156e673f6c68ab845008/diff/
lrwxrwxrwx  1 root root   72 Feb  1 17:28 7VBMC2I47YQVC3BJTUTL7YQBGI -> ../42fa3c0843a115b4b84aa13cf7e7c8ce6a14361e21aa4e34d793723f0d90e021/diff/

处于底层的镜像目录包含了一个diff和一个link文件,
diff目录存放了当前层的镜像内容,
link文件则是与之对应的短名称,
lower文件用于记录父层的短名称,
work目录用于联合挂载指定的工作目录
而这些目录和镜像的关系是怎么组织在的一起呢?答案是通过元数据关联。
元数据分为image元数据和layer元数据。 
root@ubuntu:/tmp/demo# ls /var/lib/docker/overlay2/123c6e8619f78d854a9508d83209b239d0cd45a8df9d4dfb1a597b0c03e7b5af/
diff  link  lower  work

(5)image元数据
名称是以镜像ID命名的文件,镜像ID可通过docker images查看,这些文件以json的形
式保存了该镜像的rootfs信息、镜像创建时间、构建历史信息、所用容器、包括启动的
Entrypoint和CMD等等
root@ubuntu:/var/lib/docker/image/overlay2/imagedb/content/sha256# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
redis               latest              44d36d2c2374        4 days ago          98.2MB
haproxy             latest              9b5ff4593b4e        4 days ago          92.3MB
ubuntu              latest              ccc6e87d482b        3 weeks ago         64.2MB
httpd               latest              c2aa7e16edd8        5 weeks ago         165MB
busybox             latest              6d5fcfe5ff17        5 weeks ago         1.22MB
hello-world         latest              fce289e99eb9        13 months ago       1.84kB
django              latest              eb40dcf64078        3 years ago         436MB

镜像元数据存储的位置:
root@ubuntu:/var/lib/docker/image/overlay2/imagedb/content/sha256# ll
total 52
drwx------ 2 root root 4096 Feb  3 17:46 ./
drwx------ 3 root root 4096 Jan 23 19:10 ../
-rw------- 1 root root 6864 Feb  3 17:28 44d36d2c2374b240abcf5da2130abf49938b8fb49446df6eec028718520332ef
-rw------- 1 root root 1497 Feb  1 16:40 6d5fcfe5ff170471fcc3c8b47631d6d71202a1fd44cf3c147e50c8de21cf0648
-rw------- 1 root root 4990 Feb  3 17:46 9b5ff4593b4e4cbc4506e7ca65f1ffd78aecd97cbf15cb32eda4a0baa1ebf451
-rw------- 1 root root 7351 Feb  1 17:28 c2aa7e16edd855da8827aa0ccf976d1d50f0827c08622c16e0750aa1591717e5
-rw------- 1 root root 3407 Jan 23 21:20 ccc6e87d482b79dd1645affd958479139486e47191dfe7a997c862d89cd8b4c0
-rw------- 1 root root 6263 Feb  3 17:21 eb40dcf64078249a33f68fdd8d80624cb81b524c24f50b95fff5c2b40bdc3fdc
-rw------- 1 root root 1510 Jan 23 21:02 fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e

eg:
root@ubuntu:/var/lib/docker/image/overlay2/imagedb/content/sha256# cat 44d36d2c2374b240abcf5da2130abf49938b8fb49446df6eec028718520332ef |python -mjson.tool
。。。。
    ],
    "os": "linux",
    "rootfs": {
        "diff_ids": [
            "sha256:488dfecc21b1bc607e09368d2791cb784cf8c4ec5c05d2952b045b3e0f8cc01e",
            "sha256:6d35c327901c03885dc2646367382a9cf90b77e07fb5bc1347905eed9a9f464b",
            "sha256:35e1c7a160662fc0b9a97d6cd893978b6a531d81bede805e47fcae1a06ebe751",
            "sha256:9f839e56c43407961d93dcb97372ffbe5abf7b313f0db9d4328770b1e5caaeab",
            "sha256:d7344f36256ca9a11964e67a4b16fb51e147c69bf4f7b9f3e150f5d13c64f781",
            "sha256:0233556febffa2d711e3f9d34f3daaab58063987b910232728bbea71ba5906bb"
        ],
        "type": "layers"
    }
}
上面的 diff_id 对应的的是一个镜像层,其排列也是有顺序的,从上到下依次表示镜像层的最低层到最顶层,
这也是rootfs的内容

diff_id如何关联进行层?
具体说来,docker 利用 rootfs 中的每个diff_id 和历史信息计算出与之对应的内容寻址的
索引(chainID) ,而chaiID则关联了layer层,进而关联到每一个镜像层的镜像文件。

(6)layer元数据
layer 对应镜像层的概念,
镜像层只包含一个具体的镜像层文件包。
用户在 docker 宿主机上下载了某个镜像层之后,docker 会在宿主机上基于镜像层文件包和
image 元数据构建本地的 layer 元数据,包括 diff、parent、size 等。
而当 docker 将在宿主机上产生的新的镜像层上传到 registry 时,
与新镜像层相关的宿主机上的元数据也不会与镜像层一块打包上传。

Docker 中定义了 Layer 和 RWLayer 两种接口,分别用来定义只读层和可读写层的一些操作,
又定义了 roLayer 和 mountedLayer,分别实现了上述两种接口。
其中,roLayer 用于描述不可改变的镜像层,mountedLayer 用于描述可读写的容器层。
具体来说,roLayer 存储的内容主要有索引该镜像层的 chainID、该镜像层的校验码 diffID、
父镜像层 parent、storage_driver 存储当前镜像层文件的 cacheID、该镜像层的 size 等内容。

这些元数据被保存在
root@ubuntu:/var/lib/docker/image/overlay2/layerdb/sha256# ll
total 108
drwxr-xr-x 27 root root 4096 Feb  3 17:32 ./
drwx------  5 root root 4096 Jan 23 21:11 ../
drwx------  2 root root 4096 Feb  1 17:28 06020df9067f8f2547f53867de8e489fed315d964c9f17990c3e5e6a29838d98/
drwx------  2 root root 4096 Feb  3 17:21 16e6b55f22cca357d6d1c156ffde240ebd570d707409b959bdd5791ebe8d6968/
drwx------  2 root root 4096 Feb  1 16:40 195be5f8be1df6709dafbba7ce48f2eee785ab7775b88e0c115d8205407265c5/
drwx------  2 root root 4096 Feb  3 17:28 31a81e216e79c54ea97523674895bf12d76e1b8cbb20e2ca1ced60925744a161/

每个chainID目录下会存在三个文件cache-id、diff、zize:

cache-id文件:
docker随机生成的uuid,内容是保存镜像层的目录索引,
也就是/var/lib/docker/overlay2/中的目录,这就是为什么通过chainID能找到对应的layer目录。
以chainID为06020df9067f8f2547f53867de8e489fed315d964c9f17990c3e5e6a29838d98所对应的目录:
b0ef105b999d7c79b14597bd36954768b04dbe93f6aa100f8a0c7ae333ce3224,
保存在:
/var/lib/docker/overlay2/bd36954768b04dbe93f6aa100f8a0c7ae333ce3224

root@ubuntu:/var/lib/docker/image/overlay2/layerdb/sha256# cat 06020df9067f8f2547f53867de8e489fed315d964c9f17990c3e5e6a29838d98/cache-id 
b0ef105b999d7c79b14597bd36954768b04dbe93f6aa100f8a0c7ae333ce3224root@ubuntu:/var/lib/docker/image/overlay2/layerdb/sha256# 


diff文件:
保存了镜像元数据中的diff_id(与元数据中的diff_ids中的uuid对应)
root@ubuntu:/var/lib/docker/image/overlay2/layerdb/sha256# cat 06020df9067f8f2547f53867de8e489fed315d964c9f17990c3e5e6a29838d98/diff
sha256:525297c1b6d2bad3afab694c55c2ba5e11bdb567e1c270494f662ed8bd55b9c9root@ubuntu:/var/lib/docker/image/overlay2/layerdb/sha256# 

size文件:
保存了镜像层的大小
sha256:525297c1b6d2bad3afab694c55c2ba5e11bdb567e1c270494f662ed8bd55b9c9root@ubuntu:/var/lib/docker/image/overlay2/layerdb/sha256# cat 06020df9067f8f2547f53867de8e489fed315d964c9f17990c3e5e6a29838d98/size 
0root@ubuntu:/var/lib/docker/image/overlay2/layerdb/sha256#7)mountedLayer 信息存储的可读init层以及容器挂载点信息包括:
容器 init 层ID(init-id)、
联合挂载使用的ID(mount-id)以及容器层的父层镜像的 chainID(parent)。
相关文件位于:
root@ubuntu:/var/lib/docker/image/overlay2/layerdb/sha256# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
729f0d3454a8        ubuntu              "/bin/bash"         3 hours ago         Up 2 hours                              wangji1


root@ubuntu:/var/lib/docker/image/overlay2/layerdb# cd mounts/
root@ubuntu:/var/lib/docker/image/overlay2/layerdb/mounts# ll
total 44
drwxr-xr-x 11 root root 4096 Feb  6 16:21 ./
drwx------  5 root root 4096 Jan 23 21:11 ../
drwxr-xr-x  2 root root 4096 Feb  4 15:38 171b215cd75aafadc82c61f83ed44290e87830fb4cf03cad2a1e3908a05d29d1/
drwxr-xr-x  2 root root 4096 Feb  6 16:21 729f0d3454a8f66d20fee88994b167603041f5a350c68aa21f8817d3eb94f9fd/
drwxr-xr-x  2 root root 4096 Feb  6 16:09 8eb9fd0012b3ab2475fa37a1aa081252544d5941403301dd8f68c9a722f6e551/
drwxr-xr-x  2 root root 4096 Feb  4 15:35 

 查看其对应的mountedLayer三个文件:
root@ubuntu:/var/lib/docker/image/overlay2/layerdb/mounts# ls 171b215cd75aafadc82c61f83ed44290e87830fb4cf03cad2a1e3908a05d29d1/
init-id  mount-id  parent

可以看到initID是在mountID后加了一个-init,
同时initID就是存储在/var/lib/docker/overlay2/的目录名称:
root@ubuntu:/var/lib/docker/image/overlay2/layerdb/mounts# ll /var/lib/docker/overlay2/
total 184
drwx------ 46 root root 4096 Feb  6 16:54 ./
drwx--x--x 14 root root 4096 Feb  6 16:54 ../
drwx------  4 root root 4096 Feb  4 23:18 123c6e8619f78d854a9508d83209b239d0cd45a8df9d4dfb1a597b0c03e7b5af/
drwx------  4 root root 4096 Feb  4 15:38 123c6e8619f78d854a9508d83209b239d0cd45a8df9d4dfb1a597b0c03e7b5af-init/
drwx------  4 root root 4096 Feb  4 23:18 1a6d97004e6e9087f0e8516072afe2db07709818d1eb009ed1a48fd8025d668e/
drwx------  4 root root 4096 Feb  4 15:36 1a6d97004e6e9087f0e8516072afe2db07709818d1eb009ed1a48fd8025d668e-init/

查看mountID还可以直接通过mount命令查看对应挂载的mountID,
对应着/var/lib/docker/overlay2/目录,这也是overlayfs呈现的merged目录
oot@ubuntu:/var/lib/docker/image/overlay2/layerdb/mounts# mount|grep overlay
overlay on /var/lib/docker/overlay2/b290f8a0d1e53dc7e7d495b347a30f1289e351ccd330fd5da0d794eefb4a7926/merged ty

在容器中创建了一文件,
root@ubuntu:/var/lib/docker/image/overlay2/layerdb/mounts# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
729f0d3454a8        ubuntu              "/bin/bash"         3 hours ago         Up 2 hours                              wangji1
root@ubuntu:/var/lib/docker/image/overlay2/layerdb/mounts# docker attach 729f0d3454a8
root@729f0d3454a8:/# touch /tmp/wangji.txt
root@729f0d3454a8:/# echo "wangji docker">/tmp/wangji.txt

此时到宿主的merged目录就能看到对应的文件:
root@ubuntu:/var/lib/docker/image/overlay2# ll /var/lib/docker/overlay2/
root@ubuntu:/var/lib/docker/overlay2# cd b290f8a0d1e53dc7e7d495b347a30f1289e351ccd330fd5da0d794eefb4a7926/
root@ubuntu:/var/lib/docker/overlay2/b290f8a0d1e53dc7e7d495b347a30f1289e351ccd330fd5da0d794eefb4a7926# ll
total 28
drwx------  5 root root 4096 Feb  6 17:46 ./
drwx------ 46 root root 4096 Feb  6 16:54 ../
drwxr-xr-x  3 root root 4096 Feb  6 16:21 diff/
-rw-r--r--  1 root root   26 Feb  6 16:21 link
-rw-r--r--  1 root root  144 Feb  6 16:21 lower
drwxr-xr-x  1 root root 4096 Feb  6 16:21 merged/
drwx------  3 root root 4096 Feb  6 17:46 work/
root@ubuntu:/var/lib/docker/overlay2/b290f8a0d1e53dc7e7d495b347a30f1289e351ccd330fd5da0d794eefb4a7926# cd diff/
root@ubuntu:/var/lib/docker/overlay2/b290f8a0d1e53dc7e7d495b347a30f1289e351ccd330fd5da0  
root@ubuntu:/var/lib/docker/overlay2/b290f8a0d1e53dc7e7d495b347a30f1289e351ccd330fd5da0d794eefb4a7926/diff# cat tmp/wangji.txt
wangji docker

(8)关于init层
nit层是以一个uuid+-init结尾表示,夹在只读层和读写层之间,作用是专门存放/etc/hosts、/etc/resolv.conf等信息,需要这一层的原因是当容器启动时候,
这些本该属于image层的文件或目录,比如hostname,用户需要修改,但是image层又不允许修改,
所以启动时候通过单独挂载一层init层,通过修改init层中的文件达到修改这些文件目的。
而这些修改往往只读当前容器生效,而在docker commit提交为镜像时候,并不会将init层提交。
该层文件存放的目录为/var/lib/docker/overlay2/<init_id>/diff 

总结:
一个容器完整的层应由三个部分组成,如下图:

镜像层:也称为rootfs,提供容器启动的文件系统 
init层: 用于修改容器中一些文件如/etc/hostname、/etc/resolv.conf等
容器层:使用联合挂载统一给用户提供的可读写目录。 

在这里插入图片描述

发布了556 篇原创文章 · 获赞 140 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/u011436427/article/details/104178423