欢迎光临
我们一直在努力

Docker容器层揭秘:修改为何消失?

昨天我遇到了一个奇怪的问题,让我对Docker的理解发生了根本性的改变。

事情是这样的:我正在做一个Python机器学习项目,按照教程用Docker打包应用。一切都顺利——镜像构建成功,容器运行正常。但当我尝试查看容器里的文件时,发现了一件不可思议的事情。

问题浮现

我构建的镜像是基于python:3.12-slim,然后在里面安装了numpy、pandas等库,还复制了我的机器学习代码。但当我运行docker run my-ml-app ls /app时,看到了我的文件;而当我进入容器修改文件后,重新运行相同的命令,之前的修改竟然“消失”了!

这太反直觉了。我明明修改了文件,为什么下次运行看到的还是原始版本?难道Docker有什么魔法吗?

第一反应:是不是我操作错了?

我开始怀疑自己。是不是我没有正确保存?或者容器重启后状态丢失了?我重复实验了几次:

  • 进入容器,创建一个新文件

  • 退出容器

  • 再次运行容器,查看文件是否存在

  • 结果每次都一样——文件不见了。这让我困惑不已。如果容器不能持久化数据,那它还有什么用?

    深入调查

    我开始查看Docker文档,偶然发现了“容器层”和“镜像层”的概念。原来,容器运行时,Docker会在镜像层之上创建一个可写层。所有修改都发生在这个可写层,而原始镜像层是只读的!

    这解释了为什么我的修改“丢失”了——因为每次运行新容器时,都会创建一个新的可写层。但为什么会有这种设计?这看起来多此一举啊。

    实践验证

    为了验证这个理论,我设计了一个实验:

    bash

    # 创建一个测试镜像
    echo "Hello from base layer" > file.txt
    cat > Dockerfile << EOF
    FROM alpine
    COPY file.txt /
    EOF
    docker build -t test-layer .

    # 运行容器并修改文件
    docker run -it test-layer sh
    # 在容器内:echo "Modified content" > /file.txt
    # 退出容器

    # 再次运行查看
    docker run test-layer cat /file.txt
    # 输出:Hello from base layer

    实验结果证实了我的理解:镜像层是只读的,容器层的修改不会影响镜像。但这带来了新的疑问——如果我想保存修改怎么办?

    发现持久化方案

    继续研究,我找到了两种解决方案:

  • docker commit:把容器层保存为新镜像

  • 数据卷(Volume):将主机目录挂载到容器

  • 我测试了第一种方法:

    bash

    # 运行容器并修改
    docker run -it –name test-container test-layer sh
    # 修改文件后退出

    # 提交为新镜像
    docker commit test-container modified-image

    # 运行新镜像查看
    docker run modified-image cat /file.txt
    # 输出:Modified content

    成功了!但这方法有个问题:如果我在容器里安装了很多软件,做出了很多修改,提交的镜像会包含所有中间文件,导致镜像臃肿。

    遇见UnionFS

    这时我遇到了UnionFS(联合文件系统)这个概念。UnionFS允许将多个目录(称为“分支”)透明地叠加在一起,形成一个统一的视图。在Docker的语境中:

    • 每个Dockerfile指令创建一个新的层

    • 基础镜像是最底层

    • 后续的RUN、COPY等指令创建叠加层

    • 运行容器时,在最上面添加可写层

    我决定亲手验证UnionFS的工作原理。在Linux系统上,我做了这个实验:

    bash

    # 创建测试目录
    mkdir unionfs-test && cd unionfs-test
    mkdir lower upper work merged

    # 在lower层创建文件
    echo "I am from lower layer" > lower/file.txt
    echo "Only in lower" > lower/lower-only.txt

    # 挂载UnionFS(使用overlay)
    mount -t overlay overlay -o lowerdir=lower,upperdir=upper,workdir=work merged

    # 查看合并后的视图
    cat merged/file.txt # 输出:I am from lower layer
    ls merged/ # 能看到所有文件

    # 在merged中修改文件
    echo "Modified in merged view" > merged/file.txt

    # 检查各层变化
    cat lower/file.txt # 还是原始内容
    cat upper/file.txt # 是修改后的内容!
    ls upper/ # 发现file.txt

    这个实验让我恍然大悟!UnionFS的写时复制(Copy-on-Write)机制意味着:

  • 读取文件时,从上往下查找,找到即返回

  • 修改文件时,如果文件在只读层,会先复制到可写层再修改

  • 删除文件时,在可写层创建“白化”(whiteout)标记

  • Docker存储驱动的选择

    进一步研究发现,Docker支持多种存储驱动:aufs、overlay2、devicemapper等。overlay2是目前默认的,因为它:

  • 性能更好

  • 支持最多128个下层

  • 与Linux内核集成更紧密

  • 我检查了我的Docker配置:

    bash

    docker info | grep "Storage Driver"
    # 输出:Storage Driver: overlay2

    解决实际问题

    回到最初的问题——如何在我的机器学习项目中持久化数据?我现在明白了最佳实践:

  • 训练数据:使用数据卷挂载

  • bash

    docker run -v /host/data:/app/data my-ml-app

  • 模型文件:输出到挂载卷或推送到模型仓库

  • 代码修改:应该修改Dockerfile重建镜像,而不是在容器内直接改

  • 心得与反思

    这次探索让我深刻理解了几个关键点:

  • 不可变基础设施:容器镜像应该是不可变的,这确保了环境一致性

  • 关注点分离:代码和配置应该与数据分离

  • 分层的好处:共享基础层节省存储,分层构建加速CI/CD

  • 最让我震撼的是,这种看似“麻烦”的设计,实际上是经过深思熟虑的。容器层和镜像层的分离,强制我们思考什么是应该打包进镜像的(代码、依赖),什么是应该外置的(数据、配置)。

    我也意识到,学习技术不能停留在表面。如果不理解UnionFS,我就无法真正理解Docker的文件系统行为,可能会误用或滥用容器技术。

    给初学者的建议

    如果你刚开始学习Docker,我建议:

  • 先理解镜像和容器的区别

  • 动手实验UnionFS的挂载和修改

  • 学习使用数据卷进行持久化

  • 思考你的应用哪些部分应该进镜像,哪些应该外置

  • 这条路我走过,虽然有些曲折,但每一步的发现都让我对容器技术有了更深的理解。容器不仅仅是“轻量级虚拟机”,它的设计哲学和实现机制,都值得我们深入探究。

    现在当我使用Docker时,我不再只是输入命令,而是在脑海中能看到那些透明的文件系统层,一层层叠加,最终构成我的容器视图。这种感觉,就像是获得了透视容器内部的超能力。

    赞(0)
    未经允许不得转载:171主机测评 » Docker容器层揭秘:修改为何消失?
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址