Release v1.5.0: Restructure chapters and update for Docker v30.x

This commit is contained in:
Baohua Yang
2026-02-04 22:12:38 -08:00
parent b4b0d4160a
commit fdb879dcf2
304 changed files with 1314 additions and 364 deletions

View File

@@ -0,0 +1,246 @@
# Docker 容器
## 一句话理解容器
> **容器是镜像的运行实例如果把镜像比作程序那么容器就是进程**
用面向对象编程的术语来说**镜像是类Class容器是对象Instance**
- 一个镜像可以创建多个容器
- 每个容器相互独立互不影响
- 容器可以被创建启动停止删除暂停
## 容器的本质
> 💡 **笔者认为理解这一点是理解 Docker 的关键**
**容器的本质是一个特殊的进程**
```
┌─────────────────────────────────────────────────────────────┐
│ 普通进程 │
│ • 与其他进程共享系统资源 │
│ • 可以看到其他进程 │
│ • 共享网络和文件系统 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 容器进程 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ • 有自己的进程空间(看不到宿主机上的其他进程) │ │
│ │ • 有自己的网络(独立 IP、端口 │ │
│ │ • 有自己的文件系统(独立的 root 目录) │ │
│ │ • 有自己的用户(容器内的 root ≠ 宿主机的 root │ │
│ └───────────────────────────────────────────────────────┘ │
│ 但仍然运行在宿主机的内核上 │
└─────────────────────────────────────────────────────────────┘
```
这种隔离是通过 Linux 内核的 **Namespace** 技术实现的
## 容器 vs 虚拟机核心区别
很多初学者会混淆容器和虚拟机笔者用一张图来说明
```
虚拟机 容器
┌───────────────────────┐ ┌───────────────────────┐
│ App A │ App B │ │ App A │ App B │
├────────────┼──────────┤ ├────────────┼──────────┤
│ Guest OS │ Guest OS │ │ Container │ Container│
│ (完整系统) │ (完整系统)│ │ (仅应用) │ (仅应用) │
├────────────┴──────────┤ └────────────┴──────────┤
│ Hypervisor │ │ Docker Engine │
├───────────────────────┤ ├───────────────────────┤
│ Host OS │ │ Host OS │
├───────────────────────┤ ├───────────────────────┤
│ Hardware │ │ Hardware │
└───────────────────────┘ └───────────────────────┘
每个 VM 运行完整 OS 所有容器共享宿主机内核
```
| 特性 | 容器 | 虚拟机 |
|------|------|--------|
| **隔离级别** | 进程级Namespace | 硬件级Hypervisor |
| **启动时间** | 秒级甚至毫秒 | 分钟级 |
| **资源占用** | MB 级别 | GB 级别 |
| **性能损耗** | 几乎为零 | 5-20% |
| **内核** | 共享宿主机内核 | 各自独立内核 |
## 容器的存储层
### 镜像层 + 容器层
当容器运行时Docker 会在镜像的只读层之上创建一个**可写层**容器存储层
```
┌─────────────────────────────────────────────┐
│ 容器存储层(可读写) │ ← 容器运行时创建
│ 运行时产生的文件变化记录在这里 │
├─────────────────────────────────────────────┤
│ 镜像第 N 层(只读) │
├─────────────────────────────────────────────┤
│ 镜像第 N-1 层(只读) │
├─────────────────────────────────────────────┤
│ ... │
├─────────────────────────────────────────────┤
│ 镜像第 1 层(只读) │ ← 基础镜像层
└─────────────────────────────────────────────┘
```
### Copy-on-Write写时复制
当容器需要修改镜像层中的文件时
1. Docker 将该文件**复制**到容器存储层
2. 在容器层中进行修改
3. 原始镜像层保持不变
```
读取文件:直接从镜像层读取(共享,高效)
修改文件:复制到容器层,然后修改(只有这个容器能看到修改)
```
### 容器存储层的生命周期
> **笔者特别强调**这是新手最容易踩的坑
**容器存储层与容器生命周期绑定容器删除数据就没了**
```bash
# 创建容器,写入数据
$ docker run -it ubuntu bash
root@abc123:/# echo "important data" > /data.txt
root@abc123:/# exit
# 删除容器
$ docker rm abc123
# 数据丢了!没有任何办法恢复!
```
### 正确的数据持久化方式
按照 Docker 最佳实践容器存储层应该保持**无状态**需要持久化的数据应该使用
| 方式 | 说明 | 适用场景 |
|------|------|---------|
| **[数据卷Volume](../07_data_network/data/volume.md)** | Docker 管理的存储 | 数据库应用数据 |
| **[绑定挂载Bind Mount](../07_data_network/data/bind-mounts.md)** | 挂载宿主机目录 | 开发时共享代码 |
```bash
# 使用数据卷(推荐)
$ docker run -v mydata:/var/lib/mysql mysql
# 使用绑定挂载
$ docker run -v /host/path:/container/path nginx
```
这些位置的读写**会跳过容器存储层**直接写入宿主机性能更好也不会随容器删除而丢失
## 容器的生命周期
```
┌──────────────────────────────────────────────────┐
│ 容器生命周期 │
└──────────────────────────────────────────────────┘
docker create docker start docker stop
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Created │───────────▶│ Running │───────────▶│ Stopped │
└─────────┘ └─────────┘ └─────────┘
│ │ │
│ │ docker pause │
│ ▼ │
│ ┌─────────┐ │
│ │ Paused │ │
│ └─────────┘ │
│ │ │
│ docker rm │ docker rm │
└───────────────────────┴──────────────────────┘
┌──────────┐
│ Deleted │
└──────────┘
```
### 常用生命周期命令
```bash
# 创建并启动容器(最常用)
$ docker run nginx
# 分步操作
$ docker create nginx # 创建容器(不启动)
$ docker start abc123 # 启动容器
# 停止容器
$ docker stop abc123 # 优雅停止(发送 SIGTERM等待后发送 SIGKILL
$ docker kill abc123 # 强制停止(直接发送 SIGKILL
# 暂停/恢复(不常用,但有时有用)
$ docker pause abc123 # 暂停容器内所有进程
$ docker unpause abc123 # 恢复
# 删除容器
$ docker rm abc123 # 删除已停止的容器
$ docker rm -f abc123 # 强制删除运行中的容器
```
## 容器与进程的关系
> **核心概念**容器的生命周期 = 主进程PID 1的生命周期
```bash
# 主进程运行,容器运行
# 主进程退出,容器停止
```
这就是为什么
```bash
# 这个容器会立即退出bash 没有输入就退出了)
$ docker run ubuntu
# 这个容器会持续运行nginx 作为守护进程持续运行)
$ docker run nginx
```
详细解释请参考[后台运行](../05_container/daemon.md)章节
## 容器的隔离性
Docker 容器通过以下 Namespace 实现隔离
| Namespace | 隔离内容 | 效果 |
|-----------|---------|------|
| **PID** | 进程 ID | 容器内 PID 1 是应用进程看不到宿主机其他进程 |
| **NET** | 网络 | 独立的网络栈IP 地址端口 |
| **MNT** | 文件系统 | 独立的根目录和挂载点 |
| **UTS** | 主机名 | 独立的主机名和域名 |
| **IPC** | 进程间通信 | 独立的信号量消息队列 |
| **USER** | 用户 | 独立的用户和组 ID |
> 想深入了解请阅读[底层实现 - 命名空间](../13_implementation/namespace.md)
## 本章小结
| 概念 | 要点 |
|------|------|
| **容器是什么** | 镜像的运行实例本质是隔离的进程 |
| **容器 vs 虚拟机** | 共享内核更轻量但隔离性较弱 |
| **存储层** | 可写层随容器删除而消失 |
| **数据持久化** | 使用 Volume Bind Mount |
| **生命周期** | 与主进程PID 1绑定 |
理解了镜像和容器接下来让我们学习[仓库](repository.md)存储和分发镜像的服务
## 延伸阅读
- [启动容器](../05_container/run.md)详细的容器启动选项
- [后台运行](../05_container/daemon.md)理解容器为什么会"立即退出"
- [进入容器](../05_container/attach_exec.md)如何操作运行中的容器
- [数据管理](../07_data_network/README.md)Volume 和数据持久化详解