[Docker] 将容器打包成镜像、镜像分层机制详解

commit 命令

# 将容器打包成镜像的命令,:TAG可有可无
docker commit -m="commit信息" -a="作者名" 容器ID 你的镜像名:TAG

创建一个容器

# 以Mariadb为例,我们启动一个mariadb镜像,然后进入这个镜像做一些修改
docker run -it mariadb bash

上面的命令是创建一个 mariadb 镜像的容器并进入这个容器,我们在要在这个容器里新建一个文件夹,然后把我们修改过的这个容器打包成一个新的镜像
image
新建一个test文件夹
image
现在我们已经准备好了要打包的容器啦

打包镜像

因为需要容器ID,我们先查看一下容器ID

# 如果你的容器正在运行中,用这个命令
docker ps

# 如果你的容器当前没有运行,那就用这个命令来查看容器信息
docker ps -a

image
第一行就是我们要打包的容器,执行 commit 命令:

docker commit -m="test commit" -a="kirizi" 30f034aea26a my_image01:1.0

image
执行成功返回了镜像ID,再用 docker images命令看看我们的所有镜像:
image
可以看到第一行就是我们刚刚创建的my_image01:1.0,并且注意看最后一列 SIZE列,我们的my_image01mariadb的镜像大小几乎是一样的。

# 查看镜像信息
docker inspect 镜像ID

我们使用docker inspect来看看我们的镜像和原始镜像的区别在哪儿
image
image
image
image
这是我们的镜像的信息,一些值得注意的信息

# 这个image id是我们用来创建这个容器的mariadb的镜像id,Volumes是
"Image": "a748acbaccae",

# docker的文件分层机制,可以看到我们的镜像文件分了9层
"RootFS": {
	"Type": "layers",
	"Layers": [
		"sha256:6515074984c6f8bb1b8a9962c8fb5f310fc85e70b04c88442a3939c026dbfad3",
		"sha256:ef35264eddcdd186fd3a3a994135ee9848f945d75c2ed81fb3db2adf6c3d2fa7",
		"sha256:daec9799caa31439c45c0eda941688a825b2c16abc63cace00d6f6d15c540ce3",
		"sha256:fd44d9fa9cd41d4a154323aa232b22e650b45ad92085a249f9febcd97b2af209",
		"sha256:e77dd0f2e017c73e064e20c166f30e2dd11ed9534a773c848c9bb73a7d6c4004",
		"sha256:cdbb3a99fe5526710d8c51ae42e6eb1a106fa53fb4494c3a9ea7326a88460002",
		"sha256:9a3285a0ae185ce0cc550989d474f5ec59f687fb5f9f3af48dd9fecc942c0900",
		"sha256:9d646cb6b224d9bf1400b776ea4c2684371815902a48abe77e6fdd5058040fbe",
		"sha256:6e28cb318b18cec6896c847a8291f1dc0c8653ee2560a6ee19ff3ad1995737ff"
	]
},

再看看原始的 mariadb image的信息
image
其他地方都跟my_image01差不多,就不贴上来了,主要看看这个 RootFS 字段,可以发现我们自己的镜像有9层,而这个只有8层,并且这8层和my_image01的前8层是一样的,这就是 docker 的镜像分层存储机制,也就是联合文件系统。

联合文件系统

联合文件系统(union file system),直接用百度去搜这个关键词搜出来的都是个 docker 相关的东西,但其实联合文件系统不是 docker 创造出来的,它是一种 linux 文件系统。
联合文件系统工作在其他文件系统的上一层,它聚合了多个文件系统里的文件到同一个根目录下,比起文件系统,它更像是一个挂载机制。
image
在上图我们可以看到由 XFSext3这两个文件系统控制的不同的路径都被挂载到了 /mnt 目录下,比较流行的联合文件系统有 UnionFS, AUFS, OverlayFS等,docker默认使用的文件系统是overlay2,可以使用docker info命令查看当前的文件系统:
image

OverlayFS为例,我们做几个实验看看联合文件系统是怎么工作的:

联合文件系统实践

前置准备

# 我这里是起一个ubuntu docker容器来进行这个实验,大家如果觉得麻烦也可以直接用自己的宿主机进行
# 用docker进行实验的话要记得--privileged=True加这个命令,不然mount命令会因为没有权限而执行失败
docker run --name ubuntu01 -it --privileged=True ubuntu:18.04 bash

# 创建一个文件夹用来存放我们实验用的各种文件
mkdir test_unionfs
cd test_unionfs

# 创建几个基础文件夹
mkdir layer1 layer2 layer3 union_layer

# 创建虚拟磁盘分区
dd if=/dev/zero of=fs1 bs=1024 count=1024
dd if=/dev/zero of=fs2 bs=1024 count=1024
dd if=/dev/zero of=fs3 bs=1024 count=1024

# 创建几个不同的文件系统
mkfs -t ext2 fs1
mkfs -t ext3 fs2
mkfs -t ext4 fs3

# 挂载分区到基础文件夹
mount fs1 layer1
mount fs2 layer2

# 创建几个文件
touch layer1/file_of_layer1 layer1/file
echo "file from layer1" > layer1/file

touch layer2/file_of_layer2 layer2/file
echo "file from layer2" > layer2/file

# 取消挂载
umount layer1
umount layer2

image

不使用联合文件系统的挂载

# 挂载fs1到union_layer
mount fs1 union_layer
ls union_layer
cat union_layer/file

# 挂载fs2到union_layer
mount fs2 union_layer
ls union_layer
cat union_layer/file

image

可以看到当我们把 fs2 挂载到 union_layer 的时候原本挂载的 fs1 里的东西就看不见了,只能看见新挂载的fs2里的文件(fs1还是挂载在union_layer上的,因为我们没有取消挂载,可以使用df命令查看当前所有挂载的目录,可以看到union_layer是挂载着两个文件系统的),也就是一个挂载点不能同时挂载多个卷,但是联合文件系统却可以做到这一点

使用联合文件系统进行挂载

# 在实验一里我们挂载了文件系统到 union_layer,先取消挂载,因为我们挂载了两个文件系统,所以要执行两次
umount union_layer
umount union_layer

# 挂载文件系统 -o ro 表示使用只读模式,也就是我们无法修改该文件系统中的原始文件,layer1、layer2 模拟docker image 里的的只读层
mount -o ro fs1 layer1
mount -o ro fs2 layer2

# layer3模拟 docker 容器的最顶层,也就是可读写的容器层,所以它需要读写权限
mount fs3 layer3

# 创建工作目录
mkdir layer3/upper layer3/workdir

# 使用 overlay 作为联合文件系统
mount -t overlay -o \
lowerdir=layer1:layer2,\
upperdir=layer3/upper,\
workdir=layer3/workdir \
none union_layer

ls union_layer

image
现在 union_layer里可以同时看到 layer1layer2里的文件了,如果我们在union_layer里修改file file_of_layer1 file_of_layer2这些文件会怎么样呢,看看实验三:

写时复制机制

# 在layer3是无法看见layer1和layer2里的文件的,这里的layer3就是模拟的我们docker里的container layer
cat layer3/file
>"No such file or directory"

# 看看layer1里的file文件里有啥
cat layer1/file
"file from layer1"

# 看看layer2里的file文件里有啥
cat layer2/file
>"file from layer2"

cat union_layer/file
>"file from layer1"

# 当前layer3/upper里是没有东西的,如果我们在挂载了layer1和layer2的union_layer层里进行了文件的修改
# 那么修改之后的文件会存储在layer3/upper,这也就是linux里的写时复制(cow)技术
cat layer3/upper/file
> No such file or directory

echo "file from union_layer" > union_layer/file

# 可以看到现在layer3/upper/file里已经有了我们刚刚写到union_layer/file里的内容了
cat layer3/upper/file
> "file from union_layer"

# image 层 layer1 里的内容不变
cat layer1/file
> "file from layer1"

# 移除挂载层里的文件
rm union_layer/file

# image层里的文件不会受到影响,它是只读的
ls layer1
> file  file_of_layer1  lost+found

ll layer3/upper/file
> c--------- 1 root root 0, 0 Jan  7 15:17 layer3/upper/file

image
image
image

再回到 docker 中,我们来看看在 docker 里是到底怎么通过联合文件系统实现镜像分层的:
image
结合上面的实验再看这张图是不是就很清晰啦,我们实验中的 layer1layer2模拟的是 docker 里的 image layerlayer3模拟的是container layer,挂载的那个 union_layer 模拟的是container mount
container mount层让我们可以同时看到container layerimage layer 里的文件内容,而我们的修改都会存储在container layer,不会影响到image layer
image layer是可以复用的只读层,就像我们文章一开始所打包的那个镜像,我们使用一个镜像创建了容器,并且在这个容器里新建了一个文件,把我们新建了文件的这个容器打包成新的镜像,这个新的镜像只比原来的镜像多了一层,这一层就是图上的container layer

posted @ 2023-01-08 10:37  2235854410  阅读(1334)  评论(0编辑  收藏  举报