本文仅介绍Docker镜像分层原理-UnionFS
完整版Docker教程请移步:Docker 完整版教程笔记
镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置文件。
UnionFS (Union File System)
2004年由纽约州立大学开发,它可以把多个目录内容联合挂载到同一个目录下,而目录的物理位置是分开的。UnionFs可以把只读和可读写文件系统合并在一起,具有写时复制功能,允许只读文件系统的修改可以保存到可写文件系统当中。
Union文件系统是Docker镜像的基础。镜像可以通过分层来进行继承,基于基础镜像。可以制作各种具体的应用镜像。
分层的优点:
- 分层最大的一个优点是共享资源;
- 多个镜像都从相同的base镜像构建而来,那么宿主机只需在磁盘上保存一份base镜像即可;
- 同时内存中也只需要加载一份base镜像,就可以为所有容器服务,而且镜像的每一层都可以被共享。
当我们浏览Docker hub时,能发现大多数镜像都不是从头开始制作,而是从一些base镜像基础上创建,比如centos基础镜像,而新镜像就是从基础镜像上一层层叠加新的逻辑构成的。这种分层设计,一个优点就是资源共享。
这显然不合理!借助Linux的UnionFS,宿主机只需要在磁盘上保存一份base镜像,内存中也只需要加载一份,就能被基于这个镜像的所有容器共享。
根据容器镜像的 写时拷贝(Copy-on-Write) 技术,某个容器对基础镜像的修改会被限制在单个容器内。
容器镜像由多个镜像层组成,所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,比如 /text,上层的 /text 会覆盖下层的 /text,也就是说用户只能访问到上层中的文件 /text,这就是COW技术。
docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统为UnionFS。
bootfs(boot file system) 主要包含bootloader 和 kernel,bootloader主要引导加载kernel,linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层时bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此使内存的使用权已由bootfs转交给内核,此使系统也会卸载bootfs。
rootfs(root file system) ,在bootfs之上。包含的就是典型Linux系统中的/dev、/proc、/bin、/etc等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,CentOS等等
将中间只读的 rootfs 的集合称为 Docker 镜像,Docker 镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。UnionFS 使得镜像的复用、定制变得更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。
Docker 镜像都是只读的,当容器启动时,一个新的写层加载到镜像的顶部,这一层就是我们通常说的容器层,容器之下的都叫镜像层!
# docker image inspect 镜像名
# 查看镜像分层方式
[root@VM-16-4-centos ~]# docker image inspect python
[
{
"Id": "sha256:0f95b1e38607bbf15b19ad0d111f2316e92eb047a35370eac71973c636acb9d2",
"RepoTags": [
"python:latest"
],
"RepoDigests": [
"python@sha256:eeed7cac682f9274d183f8a7533ee1360a26acb3616aa712b2be7896f80d8c5f"
],
"Parent": "",
"Comment": "",
"Created": "2022-06-23T10:44:58.474893642Z",
"Container": "fceecb8656d753a144b3968d1c6c9b8f5453ff307d0688242f642743d9680beb",
"ContainerConfig": {
"Hostname": "fceecb8656d7",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"LANG=C.UTF-8",
"GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D",
"PYTHON_VERSION=3.10.5",
"PYTHON_PIP_VERSION=22.0.4",
"PYTHON_SETUPTOOLS_VERSION=58.1.0",
"PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/6ce3639da143c5d79b44f94b04080abf2531fd6e/public/get-pip.py",
"PYTHON_GET_PIP_SHA256=ba3ab8267d91fd41c58dbce08f76db99f747f716d85ce1865813842bb035524d"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"python3\"]"
],
"Image": "sha256:f968cc011b4d45dae5d5973d55488b86535bf317efb8f81481889503b79d1f8a",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {}
},
"DockerVersion": "20.10.12",
"Author": "",
"Config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"LANG=C.UTF-8",
"GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D",
"PYTHON_VERSION=3.10.5",
"PYTHON_PIP_VERSION=22.0.4",
"PYTHON_SETUPTOOLS_VERSION=58.1.0",
"PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/6ce3639da143c5d79b44f94b04080abf2531fd6e/public/get-pip.py",
"PYTHON_GET_PIP_SHA256=ba3ab8267d91fd41c58dbce08f76db99f747f716d85ce1865813842bb035524d"
],
"Cmd": [
"python3"
],
"Image": "sha256:f968cc011b4d45dae5d5973d55488b86535bf317efb8f81481889503b79d1f8a",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"Architecture": "amd64",
"Os": "linux",
"Size": 919687727,
"VirtualSize": 919687727,
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/f4cd5061845ac5514f5aaa387dd58b63ea37c14d4383e6f01360c5ab1a6357b5/diff:/var/lib/docker/overlay2/a2eeebe65fe6050b40368dfaaf6115998e46544636faf3c820c686d7fb1bde16/diff:/var/lib/docker/overlay2/4866d5cf1beb0816ba148e1700d92ea0c42cf1f7ba1ffd80e53113f95dcc2c8e/diff:/var/lib/docker/overlay2/bc1115c2e1d53bfd674718bdd08ef6a2c82c73667067fed2e05c18f2131e1c3d/diff:/var/lib/docker/overlay2/616914d1a8c57dd7386b550dbcd1ba501d11c09380afffa39fd227dcf3e4123d/diff:/var/lib/docker/overlay2/34ed671ac7b1d2cf8e30cf0f3cf85da97c34b643e217325c8110dea59c36c730/diff:/var/lib/docker/overlay2/b4e5edd486054fdcfbc558c6c2b64ccdd27e62776e6330828beab3674acfdcd6/diff:/var/lib/docker/overlay2/a09d8bfe4e8ff39f2b4b6589d36dc5bb97d3440aa2afc8d86e671a584f460eaa/diff",
"MergedDir": "/var/lib/docker/overlay2/a34dc8c6fd5f524c445da51fff81a50b3e15cf53c1db69aadca32c732b88209b/merged",
"UpperDir": "/var/lib/docker/overlay2/a34dc8c6fd5f524c445da51fff81a50b3e15cf53c1db69aadca32c732b88209b/diff",
"WorkDir": "/var/lib/docker/overlay2/a34dc8c6fd5f524c445da51fff81a50b3e15cf53c1db69aadca32c732b88209b/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:97d5fec864d84417c057008f153140193d2cc924b545b0c6fec10ae891fb26f9",
"sha256:6840c8ff46bd2c0ab4086d311c3e4903639b586c84d4f4ad28d156a2cc749e5f",
"sha256:66183893ba248fa10375cfec3e598d7df626b44c7740156e935ce2fcd5589aa7",
"sha256:5afd661c6106dfe99ed40c2dd18b8f6ffc5d978fa3302037b513c7a6e366609f",
"sha256:33a247b4fc529be9d43b5e70cc7d1beadf12e58c6e74967a4ade33e5e58f936d",
"sha256:ca5c6d5c3d01e1e0463415cd97adc7da3d3d5bb09f9ed01ec9d27cf6eeacb928",
"sha256:a8db90eb5ce025d596d828c84dec139ef539fb2bfb67c47e046fff306cb8b79b",
"sha256:9d17ba627a77ee6184993444d132f0dc9b7527db91af69f5fd9493dc29006957",
"sha256:a84ac09f498fca42148b003e0a51971916a5ef676e676c9522910f1e406e12bb"
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]
总结:
- 这是因为对于精简的 OS,rootfs 可以很小,只需要包合最基本的命令,工具和程序库就可以了,因为底层直接用宿主机的kernel,自己只需要提供 rootfs 就可以了。
- 由此可见对于不同的Linux发行版, bootfs 基本是一致的,rootfs会有差別,因此不同的发行版可以公用 bootfs。