Add more content

This commit is contained in:
Baohua Yang
2026-01-30 16:48:39 -08:00
parent c58f61dbed
commit fec2e506d9
39 changed files with 8202 additions and 1063 deletions

View File

@@ -1,15 +1,222 @@
# Docker 镜像
我们都知道操作系统分为 **内核** **用户空间**对于 `Linux` 而言内核启动后会挂载 `root` 文件系统为其提供用户空间支持 **Docker 镜像**`Image`就相当于是一个 `root` 文件系统比如官方镜像 `ubuntu:24.04` 就包含了完整的一套 Ubuntu 24.04 最小系统的 `root` 文件系统
## 一句话理解镜像
**Docker 镜像** 是一个特殊的文件系统除了提供容器运行时所需的程序资源配置等文件外还包含了一些为运行时准备的一些配置参数如匿名卷环境变量用户等镜像 **不包含** 任何动态数据其内容在构建之后也不会被改变
> **Docker 镜像是一个只读的模板包含了运行应用所需的一切代码运行时环境变量和配置文件**
## 分层存储
如果用一个类比**镜像就像是一张光盘或 ISO 文件**你可以用同一张光盘在不同电脑上安装系统而光盘本身不会被修改同样一个镜像可以创建多个容器而镜像本身保持不变
因为镜像包含操作系统完整的 `root` 文件系统其体积往往是庞大的因此在 Docker 设计时就充分利用 [Union FS](https://en.wikipedia.org/wiki/Union_mount) 的技术,将其设计为分层存储的架构。所以严格来说,镜像并非是像一个 `ISO` 那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。
## 镜像与操作系统的关系
镜像构建时会一层层构建前一层是后一层的基础每一层构建完就不会再发生改变后一层上的任何改变只发生在自己这一层比如删除前一层文件的操作实际不是真的删除前一层的文件而是仅在当前层标记为该文件已删除在最终容器运行的时候虽然不会看到这个文件但是实际上该文件会一直跟随镜像因此在构建镜像的时候需要额外小心每一层尽量只包含该层需要添加的东西任何额外的东西应该在该层构建结束前清理掉
我们都知道操作系统分为**内核****用户空间**
分层存储的特征还使得镜像的复用定制变的更为容易甚至可以用之前构建好的镜像作为基础层然后进一步添加新的层以定制自己所需的内容构建新的镜像
```
┌─────────────────────────────────────────────────────────────┐
│ 用户空间 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 应用程序、工具、库、配置文件... │ │
│ │ (这部分被打包成 Docker 镜像) │ │
│ └─────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Linux 内核 │
│ (容器共享宿主机的内核) │
└─────────────────────────────────────────────────────────────┘
```
关于镜像构建将会在后续相关章节中做进一步的讲解
对于 Linux 而言内核启动后会挂载 `root` 文件系统来提供用户空间支持**Docker 镜像**本质上就是一个 `root` 文件系统
例如官方镜像 `ubuntu:24.04` 包含了一套完整的 Ubuntu 24.04 最小系统的 root 文件系统**不包含 Linux 内核**因为容器共享宿主机的内核
## 镜像包含什么
Docker 镜像是一个特殊的文件系统包含
| 内容类型 | 示例 |
|---------|------|
| **程序文件** | 应用二进制文件Python/Node 解释器 |
| **库文件** | libcOpenSSL各种依赖库 |
| **配置文件** | nginx.confmy.cnf |
| **环境变量** | PATHLANG 等预设值 |
| **元数据** | 启动命令暴露端口数据卷定义 |
**关键特性**
- 镜像是**只读**
- 镜像**不包含**动态数据
- 镜像构建后**内容不会改变**
## 分层存储镜像的核心设计
### 为什么需要分层
笔者认为分层存储是 Docker 最巧妙的设计之一
假设你有三个应用都基于 Ubuntu 运行
```
传统方式(不分层):
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ App A │ │ App B │ │ App C │
│ Ubuntu │ │ Ubuntu │ │ Ubuntu │
│ 500MB │ │ 500MB │ │ 500MB │
└─────────────┘ └─────────────┘ └─────────────┘
总计1.5GB ❌
Docker 分层方式:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ App A │ │ App B │ │ App C │
│ 50MB │ │ 30MB │ │ 40MB │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└────────────────┼────────────────┘
┌─────────────────┐
│ Ubuntu │
共享500MB │
└─────────────────┘
总计620MB ✅
```
### 分层是如何工作的
笔者用一个实际的 Dockerfile 来解释分层
```docker
FROM ubuntu:24.04 # 第 1 层:基础系统(约 78MB
RUN apt-get update # 第 2 层:更新包索引
RUN apt-get install nginx # 第 3 层:安装 nginx
COPY app.conf /etc/nginx/ # 第 4 层:复制配置文件
```
构建后的镜像结构
```
┌─────────────────────────────────────┐
│ 第 4 层: COPY app.conf (只读) │ ← 最新添加的层
├─────────────────────────────────────┤
│ 第 3 层: nginx 安装文件 (只读) │
├─────────────────────────────────────┤
│ 第 2 层: apt 缓存更新 (只读) │
├─────────────────────────────────────┤
│ 第 1 层: Ubuntu 基础系统 (只读) │ ← 基础镜像层
└─────────────────────────────────────┘
```
每一层的特点
- **只读**构建完成后不可修改
- **可共享**多个镜像可以共享相同的层
- **有缓存**未变化的层不会重新构建
### 分层存储的"陷阱"
> **笔者特别提醒**理解这一点可以帮你避免构建出臃肿的镜像
**关键原理**每一层的文件变化会被记录**删除操作只是标记不会真正减小镜像体积**
```docker
# 错误示范 ❌
FROM ubuntu:24.04
RUN apt-get update
RUN apt-get install -y build-essential # 安装编译工具(约 200MB
RUN make && make install # 编译应用
RUN apt-get remove build-essential # 试图删除编译工具
# 结果:镜像仍然包含 200MB 的编译工具!
```
```docker
# 正确做法 ✅
FROM ubuntu:24.04
RUN apt-get update && \
apt-get install -y build-essential && \
make && make install && \
apt-get remove -y build-essential && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*
# 在同一层完成安装、使用、清理
```
### 查看镜像的分层
```bash
# 查看镜像的历史(每层的构建记录)
$ docker history nginx:latest
IMAGE CREATED CREATED BY SIZE
a6bd71f48f68 2 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B
<missing> 2 weeks ago STOPSIGNAL SIGQUIT 0B
<missing> 2 weeks ago EXPOSE map[80/tcp:{}] 0B
<missing> 2 weeks ago ENTRYPOINT ["/docker-entrypoint.sh"] 0B
<missing> 2 weeks ago COPY 30-tune-worker-processes.sh /docker-ent… 4.62kB
...
```
## 镜像的标识
Docker 镜像有多种标识方式
### 1. 镜像名称和标签
格式`[仓库地址/]仓库名[:标签]`
```bash
# 完整格式
registry.example.com/myproject/myapp:v1.2.3
# 简写(使用 Docker Hub
nginx:1.25
ubuntu:24.04
# 省略标签(默认使用 latest
nginx # 等同于 nginx:latest
```
### 2. 镜像 IDContent-Addressable
每个镜像有一个基于内容计算的唯一 ID
```bash
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest a6bd71f48f68 2 weeks ago 187MB
ubuntu 24.04 ca2b0f26964c 3 weeks ago 78.1MB
```
### 3. 镜像摘要Digest
更精确的标识基于镜像内容的 SHA256 哈希
```bash
$ docker images --digests
REPOSITORY TAG DIGEST IMAGE ID
nginx latest sha256:6db391d1c0cfb30588ba0bf72ea999404f2764184d8b8d10d89e8a9c6... a6bd71f48f68
```
> 💡 笔者建议在生产环境使用镜像摘要而非标签因为标签可以被覆盖但摘要是不可变的
## 镜像的来源
Docker 镜像可以通过以下方式获取
| 方式 | 说明 | 示例 |
|------|------|------|
| ** Registry 拉取** | 最常用的方式 | `docker pull nginx` |
| ** Dockerfile 构建** | 自定义镜像 | `docker build -t myapp .` |
| **从容器提交** | 保存容器状态不推荐 | `docker commit` |
| **从文件导入** | 离线传输 | `docker load < image.tar` |
## 本章小结
| 概念 | 要点 |
|------|------|
| **镜像是什么** | 只读的应用模板包含运行所需的一切 |
| **分层存储** | 多层叠加共享基础层节省空间 |
| **只读特性** | 构建后不可修改保证一致性 |
| **层的陷阱** | 删除操作只是标记不减小体积 |
理解了镜像接下来让我们学习[容器](container.md)镜像的运行实例
## 延伸阅读
- [获取镜像](../image/pull.md) Registry 下载镜像
- [使用 Dockerfile 定制镜像](../image/build.md)创建自己的镜像
- [Dockerfile 最佳实践](../appendix/best_practices.md)构建高质量镜像的技巧
- [底层实现 - 联合文件系统](../underly/ufs.md)深入理解分层存储的技术原理