冷蟊初退 孤灯野澜 志起鸡鸣 墓不悲秋 技术交流 软件开发 商业合作 加Q:411239339

Day 04 Docker镜像揭秘:从拉取到理解镜像分层

浏览:12次阅读
没有评论

共计 5739 个字符,预计需要花费 15 分钟才能阅读完成。

Day 04 Docker 镜像揭秘:从拉取到理解镜像分层
你是否好奇过:

  • 场景 1 :为什么下载一个 Ubuntu 镜像只需要几十秒,而安装一个真实的 Ubuntu 系统要几十分钟?
  • 场景 2 :为什么多个容器可以共享同一个镜像,却互不影响?
  • 场景 3 :为什么修改容器后重启,所有改动都消失了?

如果这些问题也困扰着你,那么今天要介绍的 Docker 镜像 将为你揭开容器技术的核心秘密。

今天这篇文章,我将用最简单的语言,带你理解 Docker 镜像的本质和分层原理。读完后,你将彻底明白容器为什么如此轻量和高效。


什么是 Docker 镜像?

一句话解释

Docker 镜像是一个 只读的文件系统模板,包含了运行应用所需的所有内容:代码、运行时、库、环境变量和配置文件。

打个比方 📸

让我用一个日常例子来解释:

Docker 镜像就像手机系统的 " 备份文件 "

  • 镜像 = 手机的完整备份(包含系统、应用、设置)
  • 容器 = 从备份恢复出来的手机实例
  • 镜像分层 = 备份时只保存变化的部分,节省空间

你可以从同一个备份(镜像)恢复出多个手机(容器),每个手机可以独立使用,互不干扰。更重要的是,备份文件本身是只读的,不会因为手机使用而改变。

对比理解 ⚖️

传统虚拟机镜像 Docker 镜像
包含完整操作系统(几 GB) 只包含应用和依赖(几十 MB)
启动需要几分钟 启动只需几秒
每个镜像独立存储 共享公共层,节省空间
难以版本管理 每层都有版本记录
分发困难 可轻松分享和下载

核心特点

  1. 只读性:镜像一旦构建完成就不可修改,保证了一致性
  2. 分层结构:像千层蛋糕一样,每层都是独立的文件系统
  3. 复用性:多个镜像可以共享相同的底层,节省存储空间

Docker 镜像是如何工作的?

简化模型

让我们看看镜像和容器的关系:

Docker 镜像(只读)┌─────────────────────┐
│  应用层 (App)       │ ← 最上层:你的应用
├─────────────────────┤
│  依赖层 (Deps)      │ ← 中间层:运行时依赖
├─────────────────────┤
│  基础层 (Base OS)   │ ← 底层:基础操作系统
└─────────────────────┘
         ↓ docker run
┌─────────────────────┐
│  可写层             │ ← 容器的修改都在这里
├─────────────────────┤
│  应用层 (只读)      │
│  依赖层 (只读)      │
│  基础层 (只读)      │
└─────────────────────┘
   运行中的容器

工作流程

  1. 拉取镜像:从 Docker Hub 或其他仓库下载镜像到本地
  2. 创建容器:Docker 在镜像的只读层之上添加一个可写层
  3. 运行应用:容器中的所有修改都发生在可写层,不影响原镜像
  4. 停止容器:可写层保留,重启容器时恢复状态
  5. 删除容器:可写层被删除,但镜像保持不变

关键概念

  • 镜像层(Image Layer):只读的文件系统层,可以被多个容器共享
  • 容器层(Container Layer):可写的顶层,存储容器运行时的所有改变
  • 联合文件系统(Union FS):将多个层合并成一个统一的文件系统视图

💡 小贴士:想象镜像是一本印刷好的书(只读),而容器是在书上夹的便签纸(可写)。你可以在便签上做笔记,但不会改变原书的内容。


什么时候用 Docker 镜像?

适用场景 ✅

  1. 应用分发:将应用及其依赖打包成镜像

    • 示例:开发了一个 Web 应用,打包成镜像后,团队成员可以直接运行,无需配置环境
  2. 环境一致性:确保开发、测试、生产环境完全一致

    • 示例:本地测试用的 Node.js 16 镜像,部署到服务器也是同一个镜像
  3. 快速部署:从镜像启动容器只需几秒

    • 示例:需要快速扩容 10 个 Nginx 实例,从同一个镜像启动即可
  4. 版本管理:为应用的不同版本创建不同的镜像

    • 示例:应用 v1.0、v1.1、v2.0 各有一个镜像,可以随时回滚

不适用场景 ❌

  1. 需要持久化数据:镜像是只读的,数据应该存储在卷中
  2. 频繁修改的内容:不要把经常变化的配置硬编码在镜像里
  3. 敏感信息:密码、密钥不应该写入镜像(会被永久记录)

💡 判断标准:如果某个文件或配置在应用运行过程中需要修改,就不应该写入镜像,而应该通过卷挂载或环境变量传入。


5 分钟快速体验

让我们通过实际操作,感受 Docker 镜像的拉取和分层结构。

前置要求

  • 已安装 Docker(参考第 2 天文章)
  • 确保 Docker 服务正在运行

操作步骤

第 1 步:搜索镜像

# 在 Docker Hub 上搜索 nginx 镜像
docker search nginx

预期结果

NAME                              DESCRIPTION                                     STARS     OFFICIAL
nginx                             Official build of Nginx.                        19000     [OK]
linuxserver/nginx                 An Nginx container...                           180
bitnami/nginx                     Bitnami nginx Docker Image                      150

第 2 步:拉取镜像

# 拉取官方 nginx 镜像(默认拉取 latest 标签)docker pull nginx

预期结果

Using default tag: latest
latest: Pulling from library/nginx
a2abf6c4d29d: Pull complete   ← 第 1 层
a9edb18cadd1: Pull complete   ← 第 2 层
589b7251471a: Pull complete   ← 第 3 层
186b1aaa4aa6: Pull complete   ← 第 4 层
b4df32aa5a72: Pull complete   ← 第 5 层
a0bcbecc962e: Pull complete   ← 第 6 层
Digest: sha256:0d17b565c37bcbd895e9d92315a05c1c3c9a29f762b011a10c54a66cd53c9b31
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest

💡 注意:每个 "Pull complete" 代表下载了一层!

第 3 步:查看镜像详情

# 查看本地所有镜像
docker images

预期结果

REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
nginx        latest    605c77e624dd   2 weeks ago   141MB
hello-world  latest    feb5d9fea6a5   2 years ago   13.3kB

第 4 步:查看镜像的分层历史

# 查看 nginx 镜像的构建历史
docker history nginx

预期结果

IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
605c77e624dd   2 weeks ago   /bin/sh -c #(nop)  CMD ["nginx" "-g" "daemon…   0B
      2 weeks ago   /bin/sh -c #(nop)  STOPSIGNAL SIGQUIT           0B
      2 weeks ago   /bin/sh -c #(nop)  EXPOSE 80                    0B
      2 weeks ago   /bin/sh -c #(nop)  ENTRYPOINT ["/docker-entr…   0B
      2 weeks ago   /bin/sh -c set -x     && addgroup --system -…   61.1MB
      2 weeks ago   /bin/sh -c #(nop)  ENV PKG_RELEASE=1~bookworm   0B
      2 weeks ago   /bin/sh -c #(nop)  ENV NV_VERSION=1.24.0        0B
      2 weeks ago   /bin/sh -c #(nop)  LABEL maintainer=NGINX Do…   0B
      2 weeks ago   /bin/sh -c #(nop)  CMD ["bash"]                 0B
      2 weeks ago   /bin/sh -c #(nop) ADD file:d261a6f6921acdd33…   74.8MB

每一行代表一层,SIZE 列显示该层的大小。

第 5 步:体验镜像分层的复用

# 拉取另一个基于 nginx 的镜像
docker pull nginx:alpine

预期结果

alpine: Pulling from library/nginx
4abcf2066143: Already exists   ← 这层已存在,直接复用!8e7ac6c8107e: Pull complete    ← 只下载不同的层
96e8cbaf4e7e: Pull complete
Digest: sha256:a5127daff3d6f4606be3100a252419bfa84fd6ee5cd74d0feaca1a5068f97dcf
Status: Downloaded newer image for nginx:alpine

💡 关键发现:"Already exists" 说明该层在之前下载 nginx:latest 时已经存在,Docker 智能复用了这一层!

运行效果

现在你已经:

  • ✅ 学会了搜索和拉取镜像
  • ✅ 看到了镜像的分层结构
  • ✅ 体验了层的复用机制

🎉 恭喜!你已经掌握了 Docker 镜像的基本操作。


深入理解镜像分层

为什么要分层?

Docker 镜像采用分层结构有三大好处:

  1. 节省存储空间 💾

    • 多个镜像可以共享相同的基础层
    • 例如:10 个基于 Ubuntu 的镜像,Ubuntu 层只存储一份
  2. 加快传输速度 🚀

    • 只下载改变的层,已存在的层直接复用
    • 推送镜像时,也只上传新增的层
  3. 提高构建效率

    • 构建镜像时,未改变的层使用缓存
    • 只需重新构建改变的层及其之后的层

分层的实际例子

假设我们有这样一个 Dockerfile:

FROM ubuntu:20.04                # 层 1:基础系统 (80MB)
RUN apt-get update               # 层 2:更新包列表 (50MB)
RUN apt-get install -y python3   # 层 3:安装 Python (100MB)
COPY app.py /app/                # 层 4:复制应用代码 (1KB)
CMD ["python3", "/app/app.py"]   # 层 5:设置启动命令 (0B)

存储结构

┌─────────────────────┐
│ CMD (0B)            │ ← 层 5:元数据
├─────────────────────┤
│ app.py (1KB)        │ ← 层 4:应用代码
├─────────────────────┤
│ python3 (100MB)     │ ← 层 3:Python 环境
├─────────────────────┤
│ apt-get update(50MB)│ ← 层 2:包索引
├─────────────────────┤
│ ubuntu:20.04 (80MB) │ ← 层 1:基础系统
└─────────────────────┘
总大小:约 230MB

复用场景

  • 如果你构建另一个基于 ubuntu:20.04 的镜像,层 1 直接复用(节省 80MB)
  • 如果只修改了 app.py,重新构建时只需重建层 4 和层 5(节省几分钟)

查看层的详细信息

# 使用 inspect 命令查看镜像的层信息
docker inspect nginx | grep -A 10 "Layers"

预期输出

"Layers": [
    "sha256:e1caac4eb9d2ec3c6c3d0e88e8e3c5c6cf7c3a2e3d6c1e2d3c4b5a6e7d8c9d0e",
    "sha256:a2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3",
    "sha256:b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4"
]

每个 sha256 哈希值代表一个不可变的层。


🤔 常见疑问

Q1:镜像删除后,容器还能运行吗?
A:可以!容器运行时已经将需要的层加载到内存,删除镜像不影响正在运行的容器。但如果容器停止后再启动,就会报错。

Q2:为什么 docker images 显示的大小和实际占用空间不一样?
A:因为多个镜像共享了某些层。使用 docker system df 可以查看实际占用空间。

Q3:如何清理不用的镜像?
A:使用 docker image prune 清理悬空镜像(没有标签的镜像),使用 docker image prune -a 清理所有未使用的镜像。

Q4:镜像的 latest 标签是什么意思?
A:latest 只是一个默认标签,并不一定是最新版本。在生产环境中,应该使用具体的版本号(如 nginx:1.24.0)而不是 latest

Q5:如何查看某个镜像占用了多少空间?
A:使用 docker images 查看单个镜像大小,或使用 docker system df -v 查看详细的空间使用情况。


📚 本文总结

今天我们深入学习了 Docker 镜像,让我们快速回顾一下要点:

  1. 核心概念:Docker 镜像是一个只读的文件系统模板,包含运行应用所需的一切
  2. 分层结构:镜像采用分层设计,每层独立且可复用,节省空间和时间
  3. 工作原理:容器在镜像的只读层之上添加可写层,所有修改都发生在可写层
  4. 实际应用:通过 docker pulldocker imagesdocker history 等命令管理镜像

下一步学习建议

掌握了 Docker 镜像的基础知识后,建议你:

  1. 实践:亲手操作一遍文章中的所有命令,观察分层结构
  2. 深入:关注下一篇文章《Day 5:容器生命周期管理》,学习如何从镜像创建和管理容器
  3. 扩展:尝试拉取不同的镜像(如 redismysql),观察它们的分层结构有何不同

🔗 相关文章推荐

  • 上一篇:Day 3 – 第一个容器:Hello World 背后的秘密
  • 下一篇:Day 5 – 容器生命周期管理:创建、启动、停止、删除
  • 扩展阅读:Docker Hub 官方文档

💬 互动时间

讨论话题:你在工作中遇到过 " 开发环境正常,生产环境出问题 " 的情况吗?现在你会如何用 Docker 镜像解决这个问题?欢迎在评论区分享你的经验!

今日作业

  1. 拉取 3 个不同的镜像(如 nginxredispython
  2. 使用 docker history 查看它们的分层结构
  3. docker system df 查看实际占用空间,验证层复用

如果这篇文章对你有帮助,请:

  • 👍 点个「赞」和「在看」
  • 🔄 分享给正在学习 Docker 的朋友
  • 💬 评论区告诉我你最感兴趣的镜像是哪个

📖 往期推荐

  • Day 1:Docker 是什么?5 分钟看懂容器革命
  • Day 2:Docker 安装指南:Windows/Mac/Linux 全平台
  • Day 3:第一个容器:Hello World 背后的秘密

关于本系列

这是「Docker 30 天实战系列」的第 4 篇文章。本系列将用 30 天时间,带你从零基础到实战部署,系统掌握 Docker 容器技术。

🔔 点击关注,不错过每一篇干货!

Day 04 Docker 镜像揭秘:从拉取到理解镜像分层


正文完
创作不易,扫码加点动力
post-qrcode
 0
果较瘦
版权声明:本站原创文章,由 果较瘦 于2026-03-29发表,共计5739字。
转载说明:除特殊说明外本站文章皆由果较瘦原创发布,转载请注明出处。