当前位置: 首页 > article >正文

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 下面是因为我们规划了磁盘,调整了默认的存储目录。

可以看到 lowerdirupperdir,都位于/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.txtupper 目录下就会马上出现一个 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.txtlower 目录里的"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 是操作系统层的虚拟化&#xff0c;所以 docker 镜像的本质是在模拟操作系统。 联合文件系统&#xff08;UnionFS&#xff09; 联合文件系统&#xff08;UnionFS&#xff09; 是Docker镜像实现分层存储的核心技术&#xff0c;它通过将多个只读层&#xff08;Image Laye…...

SpringAI+DeepSeek大模型应用开发——2 大模型应用开发架构

目录 2.大模型开发 2.1 模型部署 2.1.1 云服务-开放大模型API 2.1.2 本地部署 搜索模型 运行大模型 2.2 调用大模型 接口说明 提示词角色 ​编辑 会话记忆问题 2.3 大模型应用开发架构 2.3.1 技术架构 纯Prompt模式 FunctionCalling RAG检索增强 Fine-tuning …...

Transformer 架构 - 编码器 (Transformer Architecture - Encoder)

1.Transformer 编码器整体结构 Transformer 编码器的结构相对直观:它由 N 个完全相同的编码器层 (Encoder Layer) 堆叠而成。 图1: Transformer 编码器整体结构示意图 (简化) 输入序列(例如,通过 embedding 层转换后的词向量)首先会加上位置编码,然后传入第一个编码器层…...

2.2/Q2,Charls最新文章解读

文章题目&#xff1a;Association of uric acid to high-density lipoprotein cholesterol ratio with the presence or absence of hypertensive kidney function: results from the China Health and Retirement Longitudinal Study (CHARLS) DOI&#xff1a;10.1186/s12882-…...

下拉框select标签类型

在我们很多页面里有下拉框的选择&#xff0c;这种元素怎么定位呢&#xff1f;下拉框分为两种类型&#xff1a;我们分别针对这两种元素进行定位和操作 select标签 &#xff1a; 通过select类处理。 非select标签 1、针对下拉框元素&#xff0c;如果是Select标签类型&#xff0c;…...

CentOS 7 linux系统从无到有部署项目

环境部署操作手册 一、Maven安装与配置 1. 下载与解压 下载地址&#xff1a;https://maven.apache.org/download.cgi?spm5238cd80.38b417da.0.0.d54c32cbnOpQh2&filedownload.cgi上传并解压解压命令&#xff1a; tar -zxvf apache-maven-3.9.9-bin.tar.gz -C /usr/loc…...

李飞飞团队新作WorldScore:“世界生成”能力迎来统一评测,3D/4D/视频模型同台PK

从古老神话中对世界起源的幻想&#xff0c;到如今科学家们在实验室里对虚拟世界的构建&#xff0c;人类探索世界生成奥秘的脚步从未停歇。如今&#xff0c;随着人工智能和计算机图形学的深度融合&#xff0c;我们已站在一个全新的起点&#xff0c;能够以前所未有的精度和效率去…...

如何在米尔-STM32MP257开发板上部署环境监测系统

本文将介绍基于米尔电子MYD-LD25X开发板&#xff08;米尔基于STM35MP257开发板&#xff09;的环境监测系统方案测试。 摘自优秀创作者-lugl4313820 一、前言 环境监测是当前很多场景需要的项目&#xff0c;刚好我正在论坛参与的一个项目&#xff1a;Thingy:91X 蜂窝物联网原型…...

MySQL之SQL优化

目录 1.插入数据 2.大批量插入数据 3.order by优化 4.group by优化 5.limit优化 6.count优化 count用法 7.update优化 1.插入数据 如果我们需要一次性往数据库表中插入多条记录&#xff0c;可以从以下三个方面进行优化 第一个:批量插入数据 Insert into tb_test va…...

python_level1.2

目录 一、变量 例如&#xff1a;小正方形——>大正方形 【1】第一次使用这个变量&#xff0c;所以说&#xff1a;定义一个变量length&#xff1b; 【2】&#xff1a;是赋值符号&#xff0c;不是等于符号。&#xff08;只有赋值&#xff0c;该变量才会被创建&#xff09;…...

Linux、Kylin OS挂载磁盘,开机自动加载

0.实验环境&#xff1a; 1.确定挂载目录&#xff0c;如果没有使用mkdir 进行创建&#xff1a; mkdir /data 2.查看磁盘 lsblk #列出所有可用的块设备df -T #查看磁盘文件系统类型 3.格式化成xfs文件系统 (这里以xfs为例&#xff0c;ext4类似) mkfs.xfs /dev/vdb 4.挂载到…...

FPGA-VGA

目录 前言 一、VGA是什么&#xff1f; 二、物理接口 三、VGA显示原理 四、VGA时序标准 五、VGA显示参数 六、模块设计 七、波形图设计 八、彩条波形数据 前言 VGA的FPGA驱动 一、VGA是什么&#xff1f; VGA&#xff08;Video Graphics Array&#xff09;是IBM于1987年推出的…...

java的lambda和stream流操作

Lambda 表达式 ≈ 匿名函数 &#xff08;Lambda接口&#xff09;函数式接口&#xff1a;传入Lambda表达作为函数式接口的参数 函数式接口 只能有一个抽象方法的接口 Lambda 表达式必须赋值给一个函数式接口&#xff0c;比如 Java 8 自带的&#xff1a; 接口名 作用 Functio…...

【嵌入式】【阿里云服务器】【树莓派】学习守护进程编程、gdb调试原理和内网穿透信息

目录 一. 守护进程的含义及编程实现的主要过程 1.1守护进程 1.2编程实现的主要过程 二、在树莓派中通过三种方式创建守护进程 2.1nohup命令创建 2.2fork()函数创建 2.3daemon()函数创建 三、在阿里云中通过三种方式创建守护进程 3.1nohup命令创建 3.2fork()函数创建 …...

数据结构学习笔记 :树与二叉树详解

目录 树的基本概念二叉树的定义与特性二叉树的存储结构 3.1 顺序存储 3.2 链式存储二叉树遍历特殊二叉树类型总结与应用场景 一、树的基本概念 核心定义 树&#xff1a;由根节点和若干子树构成的层次结构。叶子节点&#xff08;终端节点&#xff09;&#xff1a;没有子节点的…...

前沿篇|CAN XL 与 TSN 深度解读

引言 1. CAN XL 标准演进与设计目标 2. CAN XL 物理层与帧格式详解 3. 时间敏感网络 (TSN) 关键技术解析 4. CAN XL + TSN 在自动驾驶领域的典型应用...

七、LangChain Tool类参数对接机制解析:基于Pydantic的类型安全与流程实现

LangChain 的 Tool 类(包括 BaseTool 和 StructuredTool)通过 参数校验、输入解析、函数调用 的流程,将外部函数与 Agent 的逻辑对接。以下是其内部逻辑的详细解析: 1. 工具与函数对接的核心机制 (1) 工具的定义方式 LangChain 提供了两种主要方式定义工具: 继承 BaseTo…...

Spring-AI-alibaba 结构化输出

1、将模型响应转换为 ActorsFilms 对象实例&#xff1a; ActorsFilms package com.alibaba.cloud.ai.example.chat.openai.entity;import java.util.List;public record ActorsFilms(String actor, List<String> movies) { } GetMapping("/toBean")public Ac…...

AI大模型科普:从零开始理解AI的“超级大脑“,以及如何用好提示词?

大家好&#xff0c;小机又来分享AI了。 今天分享一些新奇的东西&#xff0c; 你有没有试过和ChatGPT聊天时&#xff0c;心里偷偷犯嘀咕&#xff1a;"这AI怎么跟真人一样对答如流&#xff1f;它真的会思考吗&#xff1f;" 或者刷到技术文章里满屏的"Token"…...

STM32单片机入门学习——第40节: [11-5] 硬件SPI读写W25Q64

写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难&#xff0c;但我还是想去做&#xff01; 本文写于&#xff1a;2025.04.18 STM32开发板学习——第一节&#xff1a; [1-1]课程简介第40节: [11-5] 硬件SPI读…...

【Java学习笔记】关键字汇总

Java 关键字汇总 用于定义数据类型的关键字&#xff1a; classinterfaceenumbyteshortintlongfloatdoublecharbooleanvoid 用于定义数据值的关键字&#xff1a; truefalsenull 用于定义流程控制的关键字&#xff1a; ifelseswitchcasedefaultwhiledoforbreakcontinueretu…...

langgraph框架之初识

1.什么是langgraph&#xff1f; LangGraph 是一个用于构建可控代理的底层编排框架。在AI中&#xff0c;代理也就是执行动作的智能体&#xff0c;也就是agent。使用这个框架可以构建一个可以自由控制的智能执行体&#xff0c;它可以帮我们做许多事情&#xff0c;如下&#xff1…...

如何将 .txt 文件转换成 .md 文件

一、因为有些软件上传文件的时候需要 .md 文件&#xff0c;首先在文件所在的目录中&#xff0c;点击“查看”&#xff0c;然后勾选上“文件扩展名”&#xff0c;这个时候该目录下的所有文件都会显示其文件类型了。 二、这时直接对目标的 .txt 文件进行重命名&#xff0c;把后缀…...

pdfjs库使用记录1

import React, { useEffect, useState, useRef } from react; import * as pdfjsLib from pdfjs-dist; // 设置 worker 路径 pdfjsLib.GlobalWorkerOptions.workerSrc /pdf.worker.min.js; const PDFViewer ({ url }) > { const [pdf, setPdf] useState(null); const […...

Qt 创建QWidget的界面库(DLL)

【1】新建一个qt库项目 【2】在项目目录图标上右击&#xff0c;选择Add New... 【3】选择模版&#xff1a;Qt->Qt设计师界面类&#xff0c;选择Widget&#xff0c;填写界面类的名称、.h .cpp .ui名称 【4】创建C调用接口&#xff08;默认是创建C调用接口&#xff09; #ifnd…...

Django REST framework 并结合 `mixin` 的示例

下面为你提供一个使用 Django REST framework 并结合 mixin 的示例,该示例将实现一个简单的图书管理 API。 项目需求 我们要创建一个图书管理系统的 API,支持对图书信息的创建、读取、更新和删除操作。 实现步骤 1. 项目初始化 首先,确保你已经安装了 Django 和 Django…...

linux查看及修改用户过期时间

修改用户有效期 密码到期时间 sudo chage -E 2025-12-31 username sudo chage -M 180 username sudo chage -d $(date %F) username 查询用户密码到期时间 for user in $(cat /etc/passwd |cut -d: -f1); do echo $user; chage -l $user | grep "Password expires"; …...

Vue.directive自定义v-指令

翻阅文章有感&#xff0c;记录学习 vue前端菜单权限控制_vue权限管理菜单思路-CSDN博客 一、定义&#xff1a;Vue.directive是Vue框架中给开发者用于注册自定义指令和返回已注册指令的API 二、基本语法&#xff1a; // 注册 Vue.directive(my-directive, {bind: function () …...

AI Agent 元年,于 2025 开启

私人博客传送门 AI Agent 元年&#xff0c;于 2025 开启 | 魔筝炼药师...

Django 自带开发服务器

$ python manage.py runserver $ python manage.py runserver 666 # 用 666 端口 $ python manage.py runserver 0.0.0.0:8000 # 让局域网内其他客户端也可访问 $ python manage.py runserver --skip-checks # 跳过检查自动检查 $ python manage.py runserver --…...