Docker--Docker镜像原理
docker 是操作系统层的虚拟化,所以 docker 镜像的本质是在模拟操作系统。
联合文件系统(UnionFS)
联合文件系统(UnionFS) 是Docker镜像实现分层存储的核心技术,它通过将多个只读层(Image Layers)和一个可写层(Container Layer)叠加,形成一个虚拟的、统一的文件系统。
核心概念
分层存储:
- Docker镜像由多个只读层组成,每一层对应一个构建步骤(如RUN、COPY指令)。
- 这些层通过UnionFS叠加在一起,形成一个逻辑上的完整文件系统。
只读层与可写层:
- 只读层:镜像的每一层都是只读的,确保了镜像的一致性和不可变性。
- 可写层:当容器启动时,Docker会在镜像的只读层之上添加一个可写的容器层,用于记录容器的文件修改。
共享与复用:
- 多个容器可以共享同一个镜像的只读层,节省存储空间。
- 不同容器之间的可写层相互隔离,互不影响。
工作原理
文件系统的叠加:
- UnionFS将多个只读层和一个可写层按照顺序叠加,形成一个统一的视图。
- 当访问文件时,UnionFS会从顶层开始查找,直到找到目标文件为止。
写时复制(Copy-on-Write, CoW):
-== 如果容器需要修改只读层中的文件,UnionFS会先在可写层中创建一个该文件的副本,然后对副本进行修改==。
- 这种机制避免了直接修改只读层,确保了镜像的不可变性。
透明性:
- 对于用户来说,UnionFS提供的文件系统视图是透明的,用户无需关心文件的实际存储位置。
实现方式
常见实现:
- AUFS:早期Docker使用的UnionFS实现,但已逐渐被其他实现取代。
- OverlayFS:现代Linux内核中广泛使用的UnionFS实现,性能更优。
- Btrfs、ZFS:其他支持UnionFS的文件系统,但使用较少。
OverlayFS的分层结构:
- LowerDir:镜像的只读层,由多个目录组成。
- UpperDir:容器的可写层。
- WorkDir:用于存储OverlayFS的临时数据。
- MergedDir:最终呈现给容器的统一文件系统视图。
在Docker中的应用场景
镜像构建:
- Docker通过UnionFS将多个构建步骤的层叠加,形成一个完整的镜像。
- 每个层都可以被其他镜像共享,减少了存储空间的占用。
容器运行:
- 容器启动时,Docker会在镜像的只读层之上添加一个可写层,用于记录容器的文件修改。
- 这种设计使得容器可以快速启动,并且多个容器可以共享同一个镜像。
镜像分发:
由于镜像的层是共享的,Docker在分发镜像时只需要传输新增的层,大大减少了网络带宽的占用。
UnionFS的优势
存储效率:
- 通过共享层,减少了存储空间的占用。
- 写时复制机制避免了重复的数据复制。
性能优化:
- OverlayFS等现代UnionFS实现具有较高的性能,能够满足容器化应用的需求。
灵活性:
- 支持动态添加和删除层,方便镜像的管理和更新。
Docker分层存储机制
Docker分层存储机制是Docker镜像构建与运行的核心技术,通过将镜像和容器的数据存储在多个独立的层中,实现了高效、灵活的镜像管理和容器运行。
基本概念
镜像层(Image Layer)
- Docker镜像由多个只读层组成,每一层对应一个构建步骤(如RUN、COPY、ADD指令)。
- 这些层通过
联合文件系统(UnionFS)
技术叠加,形成一个逻辑上的完整文件系统。 - 每个层只存储与前一层相比的增量变化,避免重复数据存储。
容器层(Container Layer)
- 当容器启动时,Docker会在镜像的只读层之上添加一个可写的容器层。
- 容器运行时的所有修改(如文件创建、修改、删除)都记录在容器层中,不会影响镜像层。
工作原理
镜像构建
- 构建镜像时,每执行一条Dockerfile指令,都会生成一个新的镜像层。
- 例如,执行RUN apt-get update && apt-get install -y nginx会在基础镜像层之上新增一层,记录安装的Nginx软件包。
容器运行
- 容器启动时,Docker会将镜像的只读层与容器层联合挂载,形成一个可读写的文件系统视图。
- 容器层是临时的,当容器停止或删除时,容器层的修改会丢失(除非通过卷或绑定挂载持久化数据)。
写时复制(Copy-on-Write, CoW)
- 当容器需要修改只读层中的文件时,Docker会将该文件复制到容器层,然后在容器层中进行修改。
- 这种机制保证了镜像层的不可变性,同时提高了容器启动速度。
分层存储的优势
镜像复用与共享
- 多个容器可以共享同一个镜像的只读层,减少存储空间占用。
- 例如,多个基于同一基础镜像构建的应用镜像可以共享基础镜像层。
高效构建与部署
- 镜像构建时,Docker会利用缓存机制。如果某一层的内容没有变化,Docker会直接使用缓存的层,而不需要重新构建。
- 镜像的分层存储使得镜像的传输和存储更加高效。
版本控制与回滚
- 每一层的变化都可以被追踪,开发者可以轻松地回滚到之前的版本,或者在不同版本之间切换。
快速启动 - 由于容器启动时只需添加一个轻量级的可写层,而不是重新创建整个文件系统,因此容器启动速度非常快。
实现细节
层标识符(Layer ID)
- 每个镜像层都有一个唯一的标识符,用于在不同的镜像之间共享。
存储驱动(Storage Driver)
- Docker支持多种存储驱动,如AUFS、OverlayFS、Device Mapper等,这些驱动都实现了分层存储的机制。
例如,OverlayFS是现代Linux系统中广泛使用的存储驱动,性能更优。
层的内容
- 每一层包含了文件系统的变化,例如添加、删除或修改的文件。这些变化以增量方式存储,只有发生变化的部分会被存储。
镜像分层存储实战
先拉取镜像
docker pull nginx:1.21.1
通过 docker image history
查看如下
root@VM-8-12-ubuntu:/data/ahri# docker history nginx:1.21.1
IMAGE CREATED CREATED BY SIZE COMMENT
822b7ec2aaf2 3 years ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B
<missing> 3 years ago /bin/sh -c #(nop) STOPSIGNAL SIGQUIT 0B
<missing> 3 years ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 3 years ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-entr… 0B
<missing> 3 years ago /bin/sh -c #(nop) COPY file:09a214a3e07c919a… 4.61kB
<missing> 3 years ago /bin/sh -c #(nop) COPY file:0fd5fca330dcd6a7… 1.04kB
<missing> 3 years ago /bin/sh -c #(nop) COPY file:0b866ff3fc1ef5b0… 1.96kB
<missing> 3 years ago /bin/sh -c #(nop) COPY file:65504f71f5855ca0… 1.2kB
<missing> 3 years ago /bin/sh -c set -x && addgroup --system -… 63.9MB
<missing> 3 years ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~buster 0B
<missing> 3 years ago /bin/sh -c #(nop) ENV NJS_VERSION=0.6.1 0B
<missing> 3 years ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.21.1 0B
<missing> 3 years ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0B
<missing> 3 years ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 3 years ago /bin/sh -c #(nop) ADD file:4ff85d9f6aa246746… 69.3MB
可以看到 dockerfile 和做出来的镜像是对应的,而且不是每一层都占用空间的。
我们再通过inspect
命令查看该镜像的存储位置。
root@VM-8-12-ubuntu:/data/ahri# docker image inspect nginx:1.21.1
[{"GraphDriver": {"Data": {"LowerDir": "/data/var/lib/docker/overlay2/ec2f5f43a9a6f4e7063fb6ef633103b1bca417f34488a4a48736758f9eb6019f/diff:/data/var/lib/docker/overlay2/e368569b4eb5117dabbb84864913883ecf8f50130097619ce128a7bcdf141092/diff:/data/var/lib/docker/overlay2/4d5612682090b9800340353f0550207d69d15ecf7135353cf09a8c8db252afb1/diff:/data/var/lib/docker/overlay2/0b85066e21c3238a8d0214b82399b41b6fedfbebe48ba8a88ab45d0947089685/diff:/data/var/lib/docker/overlay2/8bdfae00fada474094f86a6fc004d5a2e53d660509fcb3ec987d204b2df5eb20/diff","MergedDir": "/data/var/lib/docker/overlay2/b35408cd1036fd7aa5d4fb5220f06fe9a56606fb2ad1316a8beca766c886e692/merged","UpperDir": "/data/var/lib/docker/overlay2/b35408cd1036fd7aa5d4fb5220f06fe9a56606fb2ad1316a8beca766c886e692/diff","WorkDir": "/data/var/lib/docker/overlay2/b35408cd1036fd7aa5d4fb5220f06fe9a56606fb2ad1316a8beca766c886e692/work"},"Name": "overlay2"},"RootFS": {"Type": "layers","Layers": ["sha256:d000633a56813933cb0ac5ee3246cf7a4c0205db6290018a169d7cb096581046","sha256:63b5f2c0d071d1ac41fe869b0f2321c3adec53d8d51b4a03017d865c38dd41f8","sha256:875b5b50454b905c1046c99ab65e403bf27400bf9c96c157332cda2538698dc2","sha256:ed94af62a494fbea70c27afcedea4c303817196b50e8dd98b5be88cd514aab01","sha256:8e58314e4a4fbd97b70bed2b4c5f4b2911ff7f3e3ee310be89fab1120768d533","sha256:d47e4d19ddecb22dc95d641e9c29192a2d13e8506bc60d1c8f6452685ed63634"]},"Metadata": {"LastTagTime": "0001-01-01T00:00:00Z"}}
]
可以看到 GraphDriver 也就是我们的存储驱动,是 overlay2
的存储驱动;
nginx 的 overlay2
的四个目录也都显示出来了,我们知道 docker 的默认目录是/var/lib/docker
,之所以在/data/var/lib/docker
下面是因为我们规划了磁盘,调整了默认的存储目录。
可以看到 lowerdir
,upperdir
,都位于/data/var/lib/docker/overlay2
下面,因为我们调整过默认存储位置所以对比默认的/var/lib/docker
多了/data
;我们进入到
cd /data/var/lib/docker/overlay
该目录下,查找我们的nginx,看下文件的怎么存储的
root@VM-8-12-ubuntu:/data/var/lib/docker/overlay2# tree -P nginx -f |grep "/sbin/nginx"
│ │ │ │ └── ./0b85066e21c3238a8d0214b82399b41b6fedfbebe48ba8a88ab45d0947089685/diff/usr/sbin/nginx
│ │ │ │ └── ./db3ef3edafc0f2fe808eb68b6f53aec88b9c2e6fd525bb7e7cf9bbfec464992b/diff/usr/sbin/nginx
│ │ │ │ └── ./e43ea820aaac10fe0afac4afac18f8fa929ae618868ae467f59f3be0348ce09c/diff/usr/sbin/nginx
│ │ │ └── ./eb2gtnp096i7l1cddihp05s0m/diff/usr/local/nginx/sbin/nginx
│ │ │ │ └── ./ee39d178f8d876a5f2a725528e6de013ffca56e8214eb8e01287ec62d5b90d04/diff/usr/sbin/nginx
│ │ │ └── ./fejtx6ef605fty3ldwkg4m1q6/diff/usr/local/nginx/sbin/nginx
│ │ │ └── ./lkq1gz8jjbyoal36hm586np6k/diff/usr/local/nginx/sbin/nginx
-P nginx
选项表示只显示匹配模式nginx
的文件或目录名。
从当前目录开始,递归地列出所有匹配 nginx 的文件或目录(通过tree -P nginx -f
)。
然后,通过grep
进一步过滤,只显示路径中包含/sbin/nginx
的行。
搜索后可以看到我们找到了多个nginx 文件,因为我们本地有多个 nginx 镜像所以搜到了多个 nginx 文件,通过
lowerdir
的值我们可以确定有一个是和我们 nginx:1.12.1 的匹配上的
同样的方式我们通过 Dockerfile 发现,nginx 还存储了个 docker-entrypoint.sh
,我们搜索这个文件,我们发现这个文件也被放到了 diff
目录下面,和我们的 lowerdir 中
一个 layer
是对应的。
root@VM-8-12-ubuntu:/data/var/lib/docker/overlay2# tree -P "docker-entrypoint.sh" -f | grep "docker-entrypoint.sh"
│ │ └── ./2b130b497b862c6acff53f31ebdbc5ecf772345c2b9ce4326f620bcef741507d/diff/usr/local/bin/docker-entrypoint.sh
│ │ └── ./4d5612682090b9800340353f0550207d69d15ecf7135353cf09a8c8db252afb1/diff/docker-entrypoint.sh
│ │ └── ./5b1bddba0c572552b60b6b6652bc91a87ac06b9211ba5624dff834375755cf9e/diff/docker-entrypoint.sh
│ │ └── ./64bc799bb08a0ad3430763f0e542b335290e94672e745aa94a852e4304ef4e3f/diff/usr/local/bin/docker-entrypoint.sh
│ │ └── ./78ba0a0382912a6afddfae1e6284cf44613f06053d091450ae5b81843cf8fd29/diff/usr/local/bin/docker-entrypoint.sh
│ │ └── ./895480d1f77b5a0db270020acb3884a3e6c7060e8b0aa549f2cf6010bfb00218/diff/docker-entrypoint.sh
│ │ └── ./a0040d7c6e1a802554a81abc3647e12a173e718387b65c8818fff523da1bb543/diff/usr/local/bin/docker-entrypoint.sh
│ │ └── ./a015c0762e6d24830c7631bec8e3c7959860090f0ea78b8a1dd2a3b5e2d5fe7f/diff/docker-entrypoint.sh
│ │ └── ./abd0373582a3c3c4cd39e9148cfb2d40eb7d61f74fb677a03eadfb9f8e231b41/diff/usr/local/bin/docker-entrypoint.sh
│ │ └── ./b8969b128b086fc04ce55d73892dedc65421ff3da8753ceab61fe958220eeb3c/diff/usr/local/bin/docker-entrypoint.sh
│ │ └── ./faa473d46c0b34676cec3c287b3ce9350b40657fa5f5df14840e086021fa2b8e/diff/usr/local/bin/docker-entrypoint.sh
接下来我们进入 diff 的上一级目录查看
root@VM-8-12-ubuntu:/data/var/lib/docker/overlay2# cd 4d5612682090b9800340353f0550207d69d15ecf7135353cf09a8c8db252afb1
root@VM-8-12-ubuntu:/data/var/lib/docker/overlay2/4d5612682090b9800340353f0550207d69d15ecf7135353cf09a8c8db252afb1# ls
committed diff link lower work
可以看到 link
文件,里面是每一个 diff 目录
的短名称,或者说软链接
root@VM-8-12-ubuntu:/data/var/lib/docker/overlay2/4d5612682090b9800340353f0550207d69d15ecf7135353cf09a8c8db252afb1# cat ./link
J6U3CHDJ5RRYP6ONH2VJLP34P2root@VM-8-12-ubuntu:/data/var/lib/docker/overlay2/4d5612682090b9800340353f0550207d69d15ecf7135353cf09a8c8db252afb1#
通过遍历 l 目录我们会发现,整个 docker 的镜像的 diff 目录都被做了对应的软链接,或者说起了个短名称。
每一个 diff 是一个层级的内容,层级的关系是存放到了 lower 文件中,里面存放着父级的层级。
root@VM-8-12-ubuntu:/data/var/lib/docker/overlay2# cd 4d5612682090b9800340353f0550207d69d15ecf7135353cf09a8c8db252afb1
root@VM-8-12-ubuntu:/data/var/lib/docker/overlay2/4d5612682090b9800340353f0550207d69d15ecf7135353cf09a8c8db252afb1# cat lower
l/HUIS7U7MEMOI47VSVORK45VAB4:l/BDTHLGWDJQYNWQQP4AH2NAT3BEroot@VM-8-12-ubuntu:/data/var/lib/docker/overlay2/4d5612682090b9800340353f0550207d69d15ecf7135353cf09a8c8db252afb1#
最后我们查看下 mergeddir
,发现 merged dir
是不存在的,当我们启动为容器的时候才是有有效的。
root@VM-8-12-ubuntu:/data/var/lib/docker/overlay2# ll b35408cd1036fd7aa5d4fb5220f06fe9a56606fb2ad1316a8beca766c886e692/merged
ls: cannot access 'b35408cd1036fd7aa5d4fb5220f06fe9a56606fb2ad1316a8beca766c886e692/merged': No such file or directory
root@VM-8-12-ubuntu:/data/var/lib/docker/overlay2# docker run -d --name mylayer nginx:1.21.1
f1a5126c83a4a010a115b3afb340618302e68834ba95b7e6b3afe4c60e3432ea
root@VM-8-12-ubuntu:/data/var/lib/docker/overlay2# docker inspect mylayer
[{"Id": "f1a5126c83a4a010a115b3afb340618302e68834ba95b7e6b3afe4c60e3432ea","Created": "2025-04-15T12:33:55.683157888Z","Path": "/docker-entrypoint.sh","Args": ["nginx","-g","daemon off;"],"State": {"Status": "running","Running": true,"Paused": false,"Restarting": false,"OOMKilled": false,"Dead": false,"Pid": 3747944,"ExitCode": 0,"Error": "","StartedAt": "2025-04-15T12:33:56.829866983Z","FinishedAt": "0001-01-01T00:00:00Z"},"Image": "sha256:822b7ec2aaf2122b8f80f9c7f45ca62ea3379bf33af4e042b67aafbf6eac1941","ResolvConfPath": "/data/var/lib/docker/containers/f1a5126c83a4a010a115b3afb340618302e68834ba95b7e6b3afe4c60e3432ea/resolv.conf","HostnamePath": "/data/var/lib/docker/containers/f1a5126c83a4a010a115b3afb340618302e68834ba95b7e6b3afe4c60e3432ea/hostname","HostsPath": "/data/var/lib/docker/containers/f1a5126c83a4a010a115b3afb340618302e68834ba95b7e6b3afe4c60e3432ea/hosts","LogPath": "/data/var/lib/docker/containers/f1a5126c83a4a010a115b3afb340618302e68834ba95b7e6b3afe4c60e3432ea/f1a5126c83a4a010a115b3afb340618302e68834ba95b7e6b3afe4c60e3432ea-json.log","Name": "/mylayer","RestartCount": 0,"Driver": "overlay2","Platform": "linux","MountLabel": "","ProcessLabel": "","AppArmorProfile": "docker-default","ExecIDs": null,"HostConfig": {"Binds": null,"ContainerIDFile": "","LogConfig": {"Type": "json-file","Config": {}},"NetworkMode": "bridge","PortBindings": {},"RestartPolicy": {"Name": "no","MaximumRetryCount": 0},"AutoRemove": false,"VolumeDriver": "","VolumesFrom": null,"ConsoleSize": [22,134],"CapAdd": null,"CapDrop": null,"CgroupnsMode": "host","Dns": [],"DnsOptions": [],"DnsSearch": [],"ExtraHosts": null,"GroupAdd": null,"IpcMode": "private","Cgroup": "","Links": null,"OomScoreAdj": 0,"PidMode": "","Privileged": false,"PublishAllPorts": false,"ReadonlyRootfs": false,"SecurityOpt": null,"UTSMode": "","UsernsMode": "","ShmSize": 67108864,"Runtime": "runc","Isolation": "","CpuShares": 0,"Memory": 0,"NanoCpus": 0,"CgroupParent": "","BlkioWeight": 0,"BlkioWeightDevice": [],"BlkioDeviceReadBps": [],"BlkioDeviceWriteBps": [],"BlkioDeviceReadIOps": [],"BlkioDeviceWriteIOps": [],"CpuPeriod": 0,"CpuQuota": 0,"CpuRealtimePeriod": 0,"CpuRealtimeRuntime": 0,"CpusetCpus": "","CpusetMems": "","Devices": [],"DeviceCgroupRules": null,"DeviceRequests": null,"MemoryReservation": 0,"MemorySwap": 0,"MemorySwappiness": null,"OomKillDisable": false,"PidsLimit": null,"Ulimits": [],"CpuCount": 0,"CpuPercent": 0,"IOMaximumIOps": 0,"IOMaximumBandwidth": 0,"MaskedPaths": ["/proc/asound","/proc/acpi","/proc/kcore","/proc/keys","/proc/latency_stats","/proc/timer_list","/proc/timer_stats","/proc/sched_debug","/proc/scsi","/sys/firmware","/sys/devices/virtual/powercap"],"ReadonlyPaths": ["/proc/bus","/proc/fs","/proc/irq","/proc/sys","/proc/sysrq-trigger"]},"GraphDriver": {"Data": {"LowerDir": "/data/var/lib/docker/overlay2/3dfbd9c0692bc89a1d337ab0638741bd740ffa9b3aadf73f67d159eeb3fec6ae-init/diff:/data/var/lib/docker/overlay2/b35408cd1036fd7aa5d4fb5220f06fe9a56606fb2ad1316a8beca766c886e692/diff:/data/var/lib/docker/overlay2/ec2f5f43a9a6f4e7063fb6ef633103b1bca417f34488a4a48736758f9eb6019f/diff:/data/var/lib/docker/overlay2/e368569b4eb5117dabbb84864913883ecf8f50130097619ce128a7bcdf141092/diff:/data/var/lib/docker/overlay2/4d5612682090b9800340353f0550207d69d15ecf7135353cf09a8c8db252afb1/diff:/data/var/lib/docker/overlay2/0b85066e21c3238a8d0214b82399b41b6fedfbebe48ba8a88ab45d0947089685/diff:/data/var/lib/docker/overlay2/8bdfae00fada474094f86a6fc004d5a2e53d660509fcb3ec987d204b2df5eb20/diff","MergedDir": "/data/var/lib/docker/overlay2/3dfbd9c0692bc89a1d337ab0638741bd740ffa9b3aadf73f67d159eeb3fec6ae/merged","UpperDir": "/data/var/lib/docker/overlay2/3dfbd9c0692bc89a1d337ab0638741bd740ffa9b3aadf73f67d159eeb3fec6ae/diff","WorkDir": "/data/var/lib/docker/overlay2/3dfbd9c0692bc89a1d337ab0638741bd740ffa9b3aadf73f67d159eeb3fec6ae/work"},"Name": "overlay2"},"Mounts": [],"Config": {"Hostname": "f1a5126c83a4","Domainname": "","User": "","AttachStdin": false,"AttachStdout": false,"AttachStderr": false,"ExposedPorts": {"80/tcp": {}},"Tty": false,"OpenStdin": false,"StdinOnce": false,"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NGINX_VERSION=1.21.1","NJS_VERSION=0.6.1","PKG_RELEASE=1~buster"],"Cmd": ["nginx","-g","daemon off;"],"Image": "nginx:1.21.1","Volumes": null,"WorkingDir": "","Entrypoint": ["/docker-entrypoint.sh"],"OnBuild": null,"Labels": {"maintainer": "NGINX Docker Maintainers <docker-maint@nginx.com>"},"StopSignal": "SIGQUIT"},"NetworkSettings": {"Bridge": "","SandboxID": "f2228ea45a64eb766b13fa2e23f85d61598da6ea4bd986e06f10717f69f19a08","SandboxKey": "/var/run/docker/netns/f2228ea45a64","Ports": {"80/tcp": null},"HairpinMode": false,"LinkLocalIPv6Address": "","LinkLocalIPv6PrefixLen": 0,"SecondaryIPAddresses": null,"SecondaryIPv6Addresses": null,"EndpointID": "09f0d767349c97d93bcf7c4b4eeecea2df7f68e92de6f92f6e3580618667b82a","Gateway": "172.17.0.1","GlobalIPv6Address": "","GlobalIPv6PrefixLen": 0,"IPAddress": "172.17.0.2","IPPrefixLen": 16,"IPv6Gateway": "","MacAddress": "02:42:ac:11:00:02","Networks": {"bridge": {"IPAMConfig": null,"Links": null,"Aliases": null,"MacAddress": "02:42:ac:11:00:02","DriverOpts": null,"NetworkID": "4fa1564bc0380eab5968a99151790435aff1c91960ca086c48a9ad2a267382f3","EndpointID": "09f0d767349c97d93bcf7c4b4eeecea2df7f68e92de6f92f6e3580618667b82a","Gateway": "172.17.0.1","IPAddress": "172.17.0.2","IPPrefixLen": 16,"IPv6Gateway": "","GlobalIPv6Address": "","GlobalIPv6PrefixLen": 0,"DNSNames": null}}}}
]
我们通过镜像实际存储位置可以看到镜像在存储的时候,通过分层来实现,并通过link 和 lower 完成层与层之间链接关系配置,diff 存放了我们的内容,并且没有什么加密。
overlay 文件系统工作实战
我们首先创建一个目录用来挂载我们的文件系统
mkdir -p /data/myworkdir/fs
创建文件系统的工作目录
root@VM-8-12-ubuntu:/data# cd /data/myworkdir/fs
root@VM-8-12-ubuntu:/data/myworkdir/fs# ls
root@VM-8-12-ubuntu:/data/myworkdir/fs# mkdir upper lower merged work
准备一些文件
root@VM-8-12-ubuntu:/data/myworkdir/fs# echo "in lower" > lower/in_lower.txt
root@VM-8-12-ubuntu:/data/myworkdir/fs# echo "in upper" > upper/in_upper.txt
root@VM-8-12-ubuntu:/data/myworkdir/fs# echo "In both. from lower" > lower/in_both.txt
root@VM-8-12-ubuntu:/data/myworkdir/fs# echo "In both. from upper" > upper/in_both.txt
挂载 overlay 目录
mount -t overlay overlay -o lowerdir=./lower,upperdir=./upper,workdir=./work ./merged
通过 df -h 可以看到我们完成了挂载
root@VM-8-12-ubuntu:/data/myworkdir/fs# df -h
Filesystem Size Used Avail Use% Mounted on
udev 937M 0 937M 0% /dev
tmpfs 198M 836K 197M 1% /run
/dev/vda2 50G 29G 19G 61% /
tmpfs 986M 24K 986M 1% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 986M 0 986M 0% /sys/fs/cgroup
tmpfs 198M 0 198M 0% /run/user/1000
overlay 50G 29G 19G 61% /data/var/lib/docker/overlay2/3dfbd9c0692bc89a1d337ab0638741bd740ffa9b3aadf73f67d159eeb3fec6ae/merged
overlay 50G 29G 19G 61% /data/myworkdir/fs/merged
此时看下目录结构,然后发现 merged 目录自动生成了 3 个文件,可以看到both,lower,upper 都在。merged 目录其实就是用户看到的目录,用户的实际文件操作在这里进行。
root@VM-8-12-ubuntu:/data/myworkdir/fs# tree -a
.
├── lower
│ ├── in_both.txt
│ └── in_lower.txt
├── merged
│ ├── in_both.txt
│ ├── in_lower.txt
│ └── in_upper.txt
├── upper
│ ├── in_both.txt
│ └── in_upper.txt
└── work└── work
merged
目录下编辑一下in_lower.txt
,upper
目录下就会马上出现一个 in_lower.txt
,而且内容就是编辑后的。而 lower 目录下的 in_lower.txt
内容不变
root@VM-8-12-ubuntu:/data/myworkdir/fs/merged# vi in_lower.txt
root@VM-8-12-ubuntu:/data/myworkdir/fs/merged# cat in_lower.txt
in lower! after edit!
root@VM-8-12-ubuntu:/data/myworkdir/fs/merged# cd ..
root@VM-8-12-ubuntu:/data/myworkdir/fs# tree -a
.
├── lower
│ ├── in_both.txt
│ └── in_lower.txt
├── merged
│ ├── in_both.txt
│ ├── in_lower.txt
│ └── in_upper.txt
├── upper
│ ├── in_both.txt
│ ├── in_lower.txt
│ └── in_upper.txt
└── work└── work5 directories, 8 files
root@VM-8-12-ubuntu:/data/myworkdir/fs# cat upper/in_lower.txt
in lower! after edit!
如果我们删除 in_lower.txt
,lower
目录里的"in_lower.txt"文件不会有变化,只是在upper/
目录中增加了一个特殊文件来告诉 OverlayFS,"in_lower.txt'
这个文件不能出现在 merged/
里了,类似 AuFS 的 whiteout
root@VM-8-12-ubuntu:/data/myworkdir/fs# rm -f merged/in_lower.txt
root@VM-8-12-ubuntu:/data/myworkdir/fs# tree -a
.
├── lower
│ ├── in_both.txt
│ └── in_lower.txt
├── merged
│ ├── in_both.txt
│ └── in_upper.txt
├── upper
│ ├── in_both.txt
│ ├── in_lower.txt
│ └── in_upper.txt
└── work└── work5 directories, 7 files
root@VM-8-12-ubuntu:/data/myworkdir/fs# ll upper/
total 16
drwxr-xr-x 2 root root 4096 Apr 15 20:49 ./
drwxr-xr-x 6 root root 4096 Apr 15 20:39 ../
-rw-r--r-- 1 root root 20 Apr 15 20:40 in_both.txt
c--------- 1 root root 0, 0 Apr 15 20:49 in_lower.txt
-rw-r--r-- 1 root root 9 Apr 15 20:39 in_upper.txt
注意到 upper 下 in_lower.txt 的文件类型没有 ,而是 c 不是-或者 d
可以看到这种文件系统对于底层来说不影响,共享比较容易,但是如果编辑,删除频繁的话,性能还是比较差的。要不停的拷贝或者标记。
相关文章:
Docker--Docker镜像原理
docker 是操作系统层的虚拟化,所以 docker 镜像的本质是在模拟操作系统。 联合文件系统(UnionFS) 联合文件系统(UnionFS) 是Docker镜像实现分层存储的核心技术,它通过将多个只读层(Image Laye…...
HL7消息编辑器的使用手册
REDISANT 提供互联网与物联网开发测试套件 # 互联网与中间件: Redis AssistantZooKeeper AssistantKafka AssistantRocketMQ AssistantRabbitMQ AssistantPulsar AssistantHBase AssistantNoSql AssistantEtcd AssistantGarnet Assistant 工业与物联网࿱…...
技术与情感交织的一生 (六)
目录 食色性也 Z 姐 Pizza “修罗场” 之战 大二 下 EP 混乱 危机 撤退 离别 初创 重逢 食色性也 美食、美器、美女。追求美好的事物是人的天性。八部众里,天众界:因修行,有美食而无美女;阿修罗界:因产力…...
AI搜索引擎的局限性
# 揭秘AI搜索引擎的局限性与深度爬取技巧 > 摘要:本文深入分析了基于关键词的AI搜索引擎局限性,探讨了深网内容难以被发现的原因,并提供了一系列实用技巧来提高信息获取的全面性。无论是开发者、研究人员还是普通用户,了解这些…...
IPD项目管理的“黄金三角“在2025年是否需要重构?
——技术革命下的组织进化与实践创新 一、时空背景:IPD黄金三角的底层逻辑与时代挑战 IPD(集成产品开发)管理体系自1998年引入中国以来,其"黄金三角"——跨职能团队协作、结构化流程体系、决策评审机制——始终是企业…...
Jarpress 开源项目重构公告
项目背景 经过长达三个月的技术攻坚,我们正式宣布完成对九年历史开源项目的全面重构升级!原项目基于JFina框架开发,现采用SpringBootMyBatis技术栈重构,正式更名为Jarpress。 架构升级 采用最小组件依赖实现,减少系…...
Redshift 2025.4.1 版本更新:多平台兼容性与功能修复
2025 年 4 月 10 日,Redshift 发布 2025.4.1 版本(2025.04),聚焦宿主软件兼容性提升与核心功能修复,具体更新如下: 各平台适配与优化 Maya/3ds Max/Blender:新增对 Maya 2026、3ds Max 2026、…...
使用crxjs插件编写浏览器扩展插件遇到的问题 Waiting for the extension service worker...
目前最新的vitejs/plugin-vue和crxjs/vite-plugin不兼容,在crxjs官网有写 修改插件版本如下: "devDependencies": {"crxjs/vite-plugin": "^1.0.14","vitejs/plugin-vue": "^2.3.4","vite"…...
数据库学习通期末复习一
🌟 各位看官好,我是maomi_9526! 🌍 种一棵树最好是十年前,其次是现在! 🚀 今天来学习C语言的相关知识。 👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更…...
数据分析实战案例:使用 Pandas 和 Matplotlib 进行居民用水
原创 IT小本本 IT小本本 2025年04月15日 18:31 北京 本文将使用 Matplotlib 及 Seaborn 进行数据可视化。探索如何清理数据、计算月度用水量并生成有价值的统计图表,以便更好地理解居民的用水情况。 数据处理与清理 读取 Excel 文件 首先,我们使用 pan…...
生态环境影响评价全解析
生态环境影响评价的原则、方法、工作程序、指标选择、参数计算、模型模拟、报告编制 一 :生态环境影响评价的基本程序 生态环境影响评价的涵义、生态影响的类型;生态环境影响评价的原则、流程、等级确定及工作范围。 图1 空间尺度上长江对中华鲟的累积…...
【Netty篇】Netty的线程模型
目录 一、Netty 线程模型是啥?二、Netty 线程模型有啥作用?三、Netty 线程模型解决了什么问题?四、如何使用 Netty 线程模型?五、Netty 线程模型的优缺点?六、总结 🌟我的其他文章也讲解的比较有趣…...
PyTorch实现权重衰退:从零实现与简洁实现
一、权重衰退原理 权重衰退(L2正则化)通过向损失函数添加权重的L2范数惩罚项,防止模型过拟合。其损失函数形式为: 二、从零开始实现 1.1 导入库与数据生成 %matplotlib inline import torch from torch import nn from d2l imp…...
Webflux声明式http客户端:Spring6原生HttpExchange实现,彻底摒弃feign
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编…...
python的strip()函数用法; 字符串切片操作
python的strip()函数用法 目录 python的strip()函数用法代码整体功能概述代码详细解释1. `answer["output_text"]`2. `.strip()`3. `final_answer = ...`字符串切片操作:answer[start_index + len("Helpful Answer:"):].strip()整体功能概述代码详细解释1…...
多模态大语言模型arxiv论文略读(二十一)
EgoPlan-Bench: Benchmarking Multimodal Large Language Models for Human-Level Planning ➡️ 论文标题:EgoPlan-Bench: Benchmarking Multimodal Large Language Models for Human-Level Planning ➡️ 论文作者:Yi Chen, Yuying Ge, Yixiao Ge, Mi…...
MCP学习资料
Anthropic 官方:https://modelcontextprotocol.io/introduction 中文站:https://mcpcn.com/docs/examples/...
《Training Language Models to Self-Correct via Reinforcement Learning》全文翻译
《Training Language Models to Self-Correct via Reinforcement Learning》 通过强化学习训练语言模型实现自我修正 Aviral Kumar ∗ , 1 { }^{\\*, 1} ∗,1, Vincent Zhuang ∗ , 1 { }^{\\*, 1} ∗,1, Rishabh Agarwal ∗ , 1 { }^{\\*}, 1 ∗,1, Yi Su ∗ , 1 { }^…...
Rust 之五 所有权、.. 和 _ 语法、引用和切片、Vec<T>、HashMap<K, V>
概述 Rust 的基本语法对于从事底层 C/C 开发的人来说多少有些难以理解,虽然官方有详细的文档来介绍,不过内容是相当的多,看起来也费劲。本文通过将每个知识点简化为 一个 DEMO 每种特性各用一句话描述的形式来简化学习过程,提高学…...
如何运行Vue 3 + Tauri + Rust 前端项目
Vue 3 Tauri Rust 前端项目运行需要安装以下工具和依赖: 1. 基本开发工具 Node.js (建议 LTS 版本) - 用于运行前端构建工具 包含 npm 或 yarn 包管理器下载地址 Rust 工具链 - Tauri 基于 Rust 构建 通过 rustup 安装安装命令: curl --proto https --tlsv1.2 -…...
Nature图形复现—Origin绘制顶刊水准的多组柱状图
多组柱状图(也称分组柱状图或簇状柱状图)是一种数据可视化图形,用于同时展示多个组别在不同子类别下的数据对比。其核心特点是通过并列的柱子将不同组别的数据排列在同一子类别下,便于直观比较。 本期教程以2022年发表于Nature的文…...
空格键会提交表单吗?HTML与JavaScript中的行为解析
在网页开发中,理解用户交互细节对于提供流畅的用户体验至关重要。一个常见的问题是:空格键是否会触发表单提交?本文将通过一个简单的示例解释这一行为,并探讨如何使用HTML和JavaScript来定制这种交互。 示例概览 考虑以下HTML代…...
详解@JsonFormat和@DateTimeFormat注解:处理日期格式化的利器
在Java开发中,尤其是Spring和Spring Boot项目中,日期时间类型(如Date、LocalDateTime等)的格式化问题经常困扰开发者。例如,前端传递的日期字符串如何转换为后端对象?后端返回的日期对象如何按指定格式序列化?这时候,@JsonFormat和@DateTimeFormat两个注解可以轻松解决…...
python-各种文件(txt,xls,csv,sql,二进制文件)读写操作、文件类型转换、数据分析代码讲解
1.文件txt读写标准用法 1.1写入文件 要读取文件,首先得使用 open() 函数打开文件。 file open(file_path, moder, encodingNone) file_path:文件的路径,可以是绝对路径或者相对路径。mode:文件打开模式,r 代表以…...
Python(16)Python文件操作终极指南:安全读写与高效处理实践
目录 背景介绍一、文件操作基础架构1. 文件打开模式详解 二、文件读取全攻略1. 基础读取方法2. 大文件处理方案3. 定位与截断 三、文件写入进阶技巧1. 基础写入操作2. 缓冲控制与实时写入 四、with上下文原理剖析1. 上下文管理协议2. 多文件同时操作 五、综合实战案例1. 加密文…...
Maven相关名词及相关配置
1、相关名词 1 Project: 任何你想build的事物,maven都可以认为他们是工程,这些工程被定义为工程对象模型(POM:Project Object Model)一个工程可以依赖其他的工程,一个工程也可以有多个子工程构成。 2 POM: 就是xml文件…...
【自动化测试】如何获取cookie,跳过登录的简单操作
前言 🌟🌟本期讲解关于自动化测试函数相关知识介绍~~~ 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🔥 你的点赞就是小编不断更新的最大动力 🎆那么废话…...
登录校验:保障系统安全访问的关键技术解析
摘要:本文围绕Tlias智能学习辅助系统的登录校验功能展开,深入剖析了实现登录校验的必要性,介绍了会话技术和统一拦截技术等关键实现思路,并对Cookie、Session和令牌技术三种会话跟踪方案进行了详细对比,旨在为系统的安…...
数据库案例1--视图和索引
以下是一个关于数据库视图和索引的高级使用教程,结合实际案例进行讲解。我们将使用一个电商系统的数据库作为示例,展示如何创建和优化视图,以及如何通过索引提高查询性能。 案例背景 假设我们有一个电商系统,包含以下表…...
load_summarize_chain ,load_qa_chain 是什么
load_summarize_chain ,load_qa_chain 是什么 目录 load_summarize_chain ,load_qa_chain 是什么`load_summarize_chain``load_qa_chain`其他构建链的方式SequentialChain, TransformChainload_summarize_chain 和 load_qa_chain 都是 LangChain 库中的实用工具函数,用于快速构…...
SQL2API 核心理念:如何重构数据服务交付范式
在企业数据治理的漫长链条中,"数据服务交付" 始终是决定数据价值转化效率的关键一环。传统数据接口开发需经历需求沟通、SQL 编写、后端编码、接口测试、权限配置等多个环节,平均开发周期长达 7-10 天,且技术门槛高,导致…...
Proteus 仿真51单片机-串口收发小窥
51单片机 51单片机是一种经典的8位微控制器,广泛应用于各种嵌入式系统开发中。它具有结构简单、可靠性高、价格低廉等特点,深受工程师和电子爱好者的喜爱。 51单片机的核心是一个8位的CPU,能够执行多种指令,完成数据处理和逻辑运…...
信号与系统期中复习(第一章)
1、连续信号叠加的周期 2、离散信号的周期判定 离散信号只有当周期为整数的时候,才是周期信号 3、信号的时间变换 4、取样性质相关计算 5、系统的描述 6、线性系统的性质 7、信号的时间变换 8、基本概念 判断时不变系统的方法: 若f(t&#…...
bash的特性-常用的通配符
在Linux或Unix系统中,Bash作为最常用的命令行解释器之一,提供了多种通配符(wildcards)来帮助用户更高效地进行文件操作。这些通配符可以用来匹配多个文件名或路径名,极大地简化了批量处理任务。本文将详细介绍Bash中常…...
Android12 自定义系统服务
在Android中可以通过两种方式创建系统服务: 由SystemServer启动的系统服务,使用SystemServer线程资源,适合轻量级的服务,比如各种XMS服务;占用独立进程,在系统启动时,由init进程拉起,比如SurfaceFlinger;本文采用的是第一种方式。 自定义AssistantManagerService 参…...
安防监控视频管理平台EasyCVR助力建筑工地施工4G/5G远程视频监管方案
一、项目背景 随着城市建设的快速发展,房地产建筑工地的数量、规模与施工复杂性都在增加,高空作业、机械操作频繁,人员流动大,交叉作业多,安全风险剧增。施工企业和政府管理部门在施工现场管理上都面临难题。政府部门…...
如何针对游戏、金融行业定制CC攻击防护规则?
针对游戏和金融行业的高并发、实时交互及高价值特性,CC攻击防护需结合业务场景定制规则。以下是分行业的定制化防护方案: 一、游戏行业CC攻击防护规则 1. 核心防护目标 保障低延迟(毫秒级响应)防止作弊工具伪造…...
【redis】初识redis
初识redis Redis 是一种基于键值对(key-value) 的 NoSQL 的数据库,它与很多键值数据库不同, Redis 中的值可以是 string(字符串) 、hash(哈希)、list(链表)、…...
MJ PDF汉化版:功能强大,阅读无忧
在数字化时代,PDF 文件已成为我们日常生活和工作中不可或缺的一部分。无论是学习资料、工作报告还是电子书,PDF 格式因其兼容性和稳定性而被广泛使用。然而,对于许多中文用户来说,使用英文界面的 PDF 阅读器可能会带来不便。 今天…...
线代第二章矩阵第二课:矩阵的加法、减法、数乘
一、矩阵的加法、减法 加法 减法 二、矩阵的数乘 规律: (1)K(AB)KAKB (2)(KL)AKALA (3)k(LA)(kL)AL(kA) (4)1*A A ; (-1)*A -A 观看笔记来源: 《线性代数…...
Python实例题:Python自动获取海量IP工具
目录 Python实例题 题目 实现思路 代码实现 代码解释 get_proxy_list 函数: check_proxy_validity 函数: save_valid_ips 函数: 主程序: 运行思路 注意事项 Python实例题 题目 Python自动获取海量IP工具 实现思路 …...
Vue el-from的el-form-item v-for循环表单如何校验rules(一)
实际业务需求场景: 新增或编辑页面(基础信息表单,一个数据列表的表单),数据列表里面的表单数是动态添加的。数据可新增、可删除,在表单保存前,常常需要做表单必填项的校验,校验通过以…...
AI 边缘计算盒子:开启智能物联新时代
一、什么是 AI 边缘计算盒子 AI 边缘计算盒子是一种集成了高性能芯片、AI 算法和数据处理能力的硬件设备。它部署在数据源的边缘侧,如工厂、商场、交通路口等,能够在本地进行数据采集、预处理、分析和决策,而无需将所有数据上传到云端。这种…...
【AGI】MCP生态的“飞轮效应”
【AGI】MCP生态的“飞轮效应” (一)打通AI代理外部交互的“最后一公里”1. AI代理的核心挑战:可靠的外部服务交互2. MCP的解决方案:结构化交互协议3. 案例对比:Figma设计修改任务4. 行业影响:从实验性技术到…...
【无标题】win7和win11双系统共存
一、背景: .本人自有戴尔E6440型老款笔记本电脑一台。500g固态硬盘,12g内存,硬盘共分了两个分区。由于本人想实现Win11系统和win七双系统共存,和可以分别加载,今晚上折腾了几个小时终于搞定了!现把经验总结…...
国内开源医疗模型研究报告
引言 随着人工智能技术的快速发展,医疗AI领域正经历前所未有的变革。开源医疗模型作为这一领域的核心技术基础设施,不仅推动了医疗智能化进程,也为医疗工作者提供了强大的辅助工具。本报告将深入探讨国内优秀的开源医疗模型,分析…...
【工具变量】1907年大清邮政舆图数据集(高清图+PDF)
大清邮政舆图是清朝时期为邮政管理而制作的一部详细地图,主要用于邮政线路的规划与管理。该舆图不仅标示了全国各地的邮政线路,还包括了各地的地理位置、行政区划和重要的交通枢纽。大清邮政舆图在中国历史上具有重要的地理和邮政历史价值,是…...
leetcode 121. Best Time to Buy and Sell Stock
题目描述 本题属于动态规划类问题。 dp数组的含义 dp[i][0]表示从第0天到第i天为止,处于持有股票的状态下,账户里的最大金额。 dp[i][1]表示从第0天到第i天为止,处于不持有股票的状态下,账户里的最大金额。 按照这个定义dp[n-…...
UWB定位技术面临的主要挑战
UWB定位技术面临的主要挑战(品铂科技视角) 一、复杂工业场景下的信号稳定性挑战 品铂科技QM35825芯片虽通过4天线射频架构和接收分集技术将金属密集环境下的多径误差降低至传统方案的1/8,但在多层混凝土厂房或动态金属设备场景…...
获取 arm-none-eabi-ld 默认使用的链接脚本
使用如下命令 ./arm-none-eabi-ld --verbose > "arm-none-eabi-ld-default.ld"将输出重定向到一个 .ld 文件中。得到的文件内容如下 GNU ld (Arm GNU Toolchain 14.2.Rel1 (Build arm-14.52)) 2.43.1.20241119Supported emulations:armelf using internal linke…...