人生理想,技术交流加Q:411239339

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

浏览:9次阅读
没有评论

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

你是否好奇过:
– ** 场景 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 步:搜索镜像 **

“`bash
# 在 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 步:拉取镜像 **

“`bash
# 拉取官方 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 步:查看镜像详情 **

“`bash
# 查看本地所有镜像
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 步:查看镜像的分层历史 **

“`bash
# 查看 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 步:体验镜像分层的复用 **

“`bash
# 拉取另一个基于 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:

“`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(节省几分钟)

### 查看层的详细信息

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

** 预期输出 **:
“`json
“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 pull`、`docker images`、`docker history` 等命令管理镜像

### 下一步学习建议

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

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

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

## 💬 互动时间

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

** 今日作业 **:
1. 拉取 3 个不同的镜像(如 `nginx`、`redis`、`python`)
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 容器技术。

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

📦 所有示例代码和资源:https://github.com/docker-30days/examples

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