理解docker镜像,容器和存储驱动
一. 镜像
镜像作为docker中最基本的概念,有以下几个特性:
- 分层,每个镜像都由一个或多个镜像层组成
- 可通过在某个镜像加上一定的镜像层得到新镜像(此过程可通过编写dockerfile或在容器中commit实现)
- 每个都分层拥有唯一ID
- 镜像在存储和使用时共享相同的镜像层(根据ID),所以在pull镜像时,已有的镜像层会自动跳过下载
- 每个镜像层都是只读,即使启动成容器,也无法对其真正的修改,修改只会作用于最上层的容器层
二. 镜像 vs容器
- 镜像加上一层可读写层(容器层)组成该镜像对应容器的文件系统
- 在容器中通过commit可将最上层的可写层固化成镜像层,形成新镜像。此时,生成的新镜像与旧镜像共享底层镜像层,所以在宿主机上只不过是多了个新的镜像层
三. Copy-On-Write,COW
写时拷贝,多个程序共享同一份文件,读操作时对这份文件无影响,当程序需要进行写操作时才把要写的部分拷贝一份给对应程序。COW只是一种策略,有多种方法去实现。根据这种思想可以大大减少存储冗余。docker实现了COW策略,所以容器会共享相同的镜像层,如下图(同一个镜像启动成多个容器时)
生成新镜像的方法有,从容器中commit,或者使用dockerfile。dockerfile其实本质是也是启动容器去commit,只是这些操作都是docker自动化去完成。
dockerfile中每一个命令就对应生成一个镜像,即每个命令对应一个镜像层,如下图
为什么要这么细粒度的划分层是因为,细粒度的镜像层被共用到的几率越大。举个极端的例子,如果把基于一个ubuntu镜像上所做的操作都归到一层的话,那么这一层共用到的几率几乎为零,因为定制化程度太高,每个人所需的环境不同。
四. docker storage driver
docker将容器的一些底层运行环境进行抽象,从复杂的实现中分离出了driver组件,让用户得以专注于业务相关的容器使用。用户可以通过选定不同的driver组件来对docker中容器大环境的定制,具体的实现操作则交由driver去进行。
driver分为三类:
- exec-driver,实现对容器运行环境的隔离和资源的限制,可选方案:lxc或native(libcontainer);
- network-driver,实现容器网络相关的操作;
- graph-driver,实现镜像存储相关操作,可选方案:devicemapper,aufs,btrfs,zfs等。
storage driver,docker中的一个组件,也称graphdriver。
功能:
- 镜像的存储管理,包括从hub上pull的镜像和通过dockerfile生成的新镜像等;
- 容器启动时,为容器准备文件系统
对于graphdriver选择何种方案去实现,并不影响docker功能方面的使用。每种方案都能实现镜像的分层结构和COW策略,但不同的方案对于docker的存储速度和稳定性是有影响的。目前相对比较成熟的有,aufs和devicemapper(WAE使用中)
下图为各个方案的特点:
官网对于选何种graphdriver的建议:https://docs.docker.com/engine/userguide/storagedriver/selectadriver/
五. devicemapper
基于linux内核中的DeviceMapper框架实现,该框架实现了物理存储设备与虚拟存储设备的映射,或虚拟设备与虚拟设备之间的映射。这使得用户能够用该框架来实现磁盘的自由、动态的划分。LVM2,software RAIDs和dm-cryptdisk encryption等都是基于该框架的实现。更为重要的是,Device Mapper框架中提供两个功能,thin-provisioning和snapshot。
thin-provisioning,类似于虚拟内存,提供给使用者的空间只有在使用者进行写操作时才真正进行分配。假设使用者有一块100g的thin块设备,使用了20g,实际系统提供的大小就是20g,只有当使用者进行存储操作时才分配更多的空间,直到100g。
snapshot,快照,是一种COW策略的实现,假设从A设备做快照得到B设备时,并未对A进行完整拷贝,而是当对B设备进行写操作时,才将需要改变的那部分做属于B设备的拷贝。
devicemapper利用了该框架中的thin-provisioning和snapshot实现了镜像的分层结构和存储优化。在用devicemapper作为graphdriver的docker中,每个镜像和容器都对应一个设备,通过对镜像做snapshot操作得到容器,所以容器中拥有镜像的内容且操作这些内容不影响镜像本身,因为容器和镜像对应不同的设备。
devicemapper有两种模式可选,loop-lvm和direct-lvm。devicemapper的大致思路是,先通过虚拟化技术得到一个thin-pool设备(可理解成一个资源池),接着在thin-pool上建立一个基础设备,此后docker上所有镜像和容器都是基于此设备的snapshot。两个模式的区别就在与建立thin-pool的方法不同。
loop-lvm:
- 创建两个稀疏文件data和metadata
- 将这两个文件映射成两个块设备(loopback块设备)
- 将两个设备通过内核中Device Mapper映射成thin-pool
注意!该模式不推荐用于生产环境,因为该模式在宿主机跑高密度容器数量的话,性能下降急剧,生产环境中一台宿主机不可能只跑单个容器
direct-lvm:
(利用了基于Device Mapper的LVM)
- 将空余块设备(可以是分区)创建成physical volume(pv)
- 在由这些PV组成volume group(vg)
- 从vg中建立两个logical volume(lv),data和matedata
- 将data和matedata映射成thin-pool
*这些步骤可由已有脚本docker-storage-setup自动执行,docker启动前会自动执行该脚本,脚本位置/usr/bin/docker-storage-setup,只要在对应配置文件中提供用于创建VG的PV(对应配置文件中DEVS),或者提供已有的VG,便可让脚本自动执行上述步骤。具体可通过service docker-storage-setup status查看,对应执行脚本内有使用说明。也可手动建立(Configure direct-lvm mode forproduction)
以下通过一些命令区别两种模式:
loop-lvm:
docker详情
列出块设备
direct-lvm模式:
docker详情
列出块设备
列出所有logical volume的信息
六. 目录相关
宿主机上容器、镜像等存储相关的主目录:/var/lib/docker
[root@bogon docker]# tree ./
./
├── containers <---容器相关
│ ├──f1a04d3537edd5741f39ff262277362b35bbf17776fcafa31e67412f97e4d9f9
│ │ ├── config.json
│ │ ├──f1a04d3537edd5741f39ff262277362b35bbf17776fcafa31e67412f97e4d9f9-json.log
│ │ ├── hostconfig.json
│ │ ├── hostname
│ │ ├── hosts
│ │ ├── resolv.conf
│ │ ├── resolv.conf.hash
│ │ └── secrets
├── devicemapper <---存储驱动相关
│ ├── metadata <---镜像、容器对应块设备的元数据
│ │ ├──0288ae931294ce04f5d69c60146faca7d9be8de4004421d650f4227fa60bd92b
│ │ ├──05c2ea2d81bceeddc2881beb63d2ee26537593de9f47b4613c0d5afb1353f7df
│ │ ├──f1a04d3537edd5741f39ff262277362b35bbf17776fcafa31e67412f97e4d9f9
│ │ ├──f1a04d3537edd5741f39ff262277362b35bbf17776fcafa31e67412f97e4d9f9-init
│ │ ├──f6d195bba85ba6b9c075186ee62fe56f5d3b27e29ad9a31da8753820f3e2586b
│ │ ├──f6d195bba85ba6b9c075186ee62fe56f5d3b27e29ad9a31da8753820f3e2586b-init
│ │ ├── base
│ │ ├── deviceset-metadata
│ │ └── transaction-metadata
│ └── mnt <---容器对应设备的挂载目录,docker会自动mount和umount,所以手动打开为空,可自行mount,对应设备在/dev/mapper/
│ ├──0288ae931294ce04f5d69c60146faca7d9be8de4004421d650f4227fa60bd92b
│ ├──05c2ea2d81bceeddc2881beb63d2ee26537593de9f47b4613c0d5afb1353f7df
├── graph <---镜像目录,存储镜像层元数据
│ ├──0288ae931294ce04f5d69c60146faca7d9be8de4004421d650f4227fa60bd92b
│ │ ├── checksum <---镜像校验和,用于判断镜像是否损坏
│ │ ├── json <---描述镜像基础信息
│ │ ├── layersize <---镜像层大小
│ │ └── tar-data.json.gz <---描述镜像层内文件在磁盘中的位置
│ ├──05c2ea2d81bceeddc2881beb63d2ee26537593de9f47b4613c0d5afb1353f7df
│ │ ├── checksum
│ │ ├── json
│ │ ├── layersize
│ │ └── tar-data.json.gz
│ └── _tmp
├── linkgraph.db
├── repositories-devicemapper
├── tmp
├── trust
└── volumes