大家好!今天,我们将亲眼看看Docker容器是如何实际存储和管理数据的,探索其“魔法”般的结构。
许多人使用容器,但常常好奇实际文件在哪里、如何存在。Docker使用一种名为OverlayFS (Overlay Filesystem)的技术,将多个层组合起来,使其看起来像一个单一的文件系统。
今天,我们不谈复杂的理论,而是直接敲击命令,从宿主操作系统的角度彻底检查容器的内部结构。准备好了吗?🚀

1. 准备实验对象:运行容器 🏃♂️
首先,让我们运行一个要分析的容器。我们将使用轻量且快速的nginx:alpine镜像。
# 运行Nginx容器(后台模式)
docker run -d -p 80:80 --name nx nginx:alpine
现在Nginx正在隔离环境中运行。但从Linux内核的角度来看,这只是一个进程而已。
2. Overlay的秘密地图:检查Mountinfo 🗺️
现在是最关键的一步!让我们通过/proc文件系统,检查这个进程所看到的的文件系统是如何实际组装起来的。
# 从相应的挂载信息中查询overlayfs设置值
mount | grep rootfs
输出示例:
overlay on /var/lib/docker/rootfs/overlayfs/fea3c44e1dee873838bee303f32d739164f9a34e6a38b3c99ccc3c6e88f7e702 type overlay (rw,relatime,lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/9/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/8/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/7/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/6/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/5/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/4/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/3/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/2/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/1/fs,upperdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/10/fs,workdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/10/work,nouserxattr)
执行此命令会得到一个相当长的字符串,但你只需要找到其中的4个关键关键词。
- 🔒 LowerDir(镜像层):这是只读的。我们下载的nginx:alpine镜像由多层组成,这些层就属于这里。它们绝不会被修改。
- ✏️ UpperDir(容器层):这是可读写的区域。当你在容器内创建或修改文件时,实际上只存储在这里。
- ⚙️ WorkDir:这是OverlayFS内部用于文件合并操作的临时空间。
- 👀 Merged(合并视图):这是紧跟在’overlay on’后面的、以/var/lib/docker/rootfs/overlayfs/开头的路径。它是LowerDir和UpperDir合并后,我们所看到的最终结果。容器所看到的/(根)目录就是它。
3. 从宿主到容器内部的“瞬间移动” 🚪
我们能否在不连接到容器(docker exec)的情况下,从宿主查看容器的文件?是的,可以!Linux内核在/proc目录中设置了一个后门。
# 方法1:通过容器ID检查Merged目录
ls -al /var/lib/docker/rootfs/overlayfs/$(docker inspect -f '{{.Id}}' nx)
# 方法2:利用PID直接进入容器的RootFS(推荐!)
ls -al /proc/$(docker inspect -f '{{.State.Pid}}' nx)/root/
/proc/[PID]/root/路径就像一个连接到该进程所看到的根目录的符号链接。进入这里,你会看到与使用`docker exec`进入时完全相同的文件。是不是很神奇?✨
4. 数据写入实验与验证 🧪
宿主所看到的视图和容器所看到的视图真的连接在一起吗?为了测试,我们将在宿主上创建一个文件。
# 在宿主上创建文件到容器的Root路径
echo test1234 > /proc/$(docker inspect -f '{{.State.Pid}}' nx)/root/test.txt
现在,如果你在容器内部检查,test.txt文件就会神奇地出现。这个实验意义重大:容器文件系统并非一个独立的封闭空间,而是宿主文件系统中特定路径的组合展示。
5. 我的文件实际在哪里?💾
我们刚刚创建的test.txt文件必须物理地存在于宿主硬盘的某个位置。它究竟存储在哪里了呢?
# 查找test.txt文件的实际物理位置
find /var/lib/docker/rootfs/ -name test.txt
搜索结果将显示类似/var/lib/docker/overlay2/[哈希值]/diff/test.txt的路径。
这个路径与我们在第3步中确认的UpperDir路径一致!也就是说,我们直接验证了OverlayFS的工作原理:“镜像(LowerDir)保持不变,所有更改都单独存储在UpperDir中”。
🎯 结论与总结
通过今天的实践,我们了解了以下事实:
- 容器不是一个魔术盒,而是一个Linux进程。
- 容器的文件系统是只读镜像(Lower)之上叠加可写层(Upper)后合并(Merged)而成的。
- 删除容器时,UpperDir也会被删除,因此其中写入的数据也会随之丢失。(这就是为什么需要卷!)
理解这些原理将帮助您更好地理解Docker镜像构建速度和容器启动速度为何如此之快,以及如何有效地管理存储。
祝您今天容器航行愉快!⛵️
发表回复