# 2.2 容器

容器是 Docker 技术的核心，是应用实际运行的载体。本节将从容器的本质、与虚拟机的区别、存储层机制以及生命周期管理等方面，全面解析 Docker 容器。

## 2.2.1 一句话理解容器

> **容器是镜像的运行实例。如果把镜像比作程序，那么容器就是进程。** 用面向对象编程的术语来说：**镜像是类 (Class)，容器是对象 (Instance)**。

* 一个镜像可以创建多个容器
* 每个容器相互独立，互不影响
* 容器可以被创建、启动、停止、删除、暂停

## 2.2.2 容器的本质

> 💡 笔者认为，理解这一点是理解 Docker 的关键：**容器的本质是一个特殊的进程**。

{% @mermaid/diagram content="flowchart TD
subgraph NormalProcess \["普通进程"]
direction TB
N1\["• 共享系统资源<br/>• 共享网络<br/>• 共享文件系统"]
end

```
subgraph ContainerProcess ["容器进程 (运行在宿主机内核上)"]
    direction TB
    C1["• 独立进程空间<br/>• 独立网络环境<br/>• 独立文件系统<br/>• 独立用户空间"]
end" %}
```

这种隔离是通过 Linux 内核的 **Namespace** 技术实现的。具体表现为：

* **进程空间**：容器看不到宿主机上的其他进程。
* **网络**：容器拥有独立的 IP、端口等网络资源。
* **文件系统**：容器拥有独立的 root 目录。
* **用户**：容器内的 root 用户不等于宿主机的 root 用户。

## 2.2.3 容器 vs 虚拟机：核心区别

很多初学者会混淆容器和虚拟机。笔者用一张图来说明：

{% @mermaid/diagram content="flowchart TD
subgraph VM \["虚拟机 (每个 VM 运行完整 OS)"]
direction TB
subgraph VMApp \["应用层"]
VA\[App A] & VB\[App B]
end
subgraph VMGuest \["Guest OS (完整系统)"]
G1\[Guest OS] & G2\[Guest OS]
end
V\[Hypervisor]
VMH\[Host OS]
VMHW\[Hardware]
VMApp --> VMGuest --> V --> VMH --> VMHW
end

```
subgraph Container ["容器 (所有容器共享宿主机内核)"]
    direction TB
    subgraph CApp ["应用层"]
        CA[App A] & CB[App B]
    end
    subgraph CContainer ["隔离层"]
        CC1[Container 仅应用] & CC2[Container 仅应用]
    end
    CE[Docker Engine]
    CH[Host OS]
    CHW[Hardware]
    CApp --> CContainer --> CE --> CH --> CHW
end" %}
```

| 特性       | 容器              | 虚拟机              |
| -------- | --------------- | ---------------- |
| **隔离级别** | 进程级 (Namespace) | 硬件级 (Hypervisor) |
| **启动时间** | 秒级 (甚至毫秒)       | 分钟级              |
| **资源占用** | MB 级别           | GB 级别            |
| **性能损耗** | 几乎为零            | 5-20%            |
| **内核**   | 共享宿主机内核         | 各自独立内核           |

## 2.2.4 容器的存储层

理解容器的存储层机制对于数据的持久化和镜像的优化至关重要。本节将介绍容器的可写层以及 Copy-on-Write 机制。

### 镜像层 + 容器层

当容器运行时，Docker 会在镜像的只读层之上创建一个 **可写层** (容器存储层)：

{% @mermaid/diagram content="flowchart TD
ContainerLayer\["容器存储层（可读写）<br/>容器运行时创建，记录文件变化"]
ImageLayerN\["镜像第 N 层（只读）"]
ImageLayerN1\["镜像第 N-1 层（只读）"]
Dots\["..."]
ImageLayer1\["镜像第 1 层（只读）<br/>基础镜像层"]

```
ContainerLayer --> ImageLayerN --> ImageLayerN1 --> Dots --> ImageLayer1" %}
```

### Copy-on-Write：写时复制

当容器需要修改镜像层中的文件时：

1. Docker 将该文件 **复制** 到容器存储层
2. 在容器层中进行修改
3. 原始镜像层保持不变

```bash
读取文件：直接从镜像层读取（共享，高效）
修改文件：复制到容器层，然后修改（只有这个容器能看到修改）
```

### ⚠️ 容器存储层的生命周期

> **笔者特别强调**：这是新手最容易踩的坑！**容器存储层与容器生命周期绑定。容器删除，数据就没了！**

```bash
## 创建容器，写入数据

$ docker run -it ubuntu bash
root@abc123:/# echo "important data" > /data.txt
root@abc123:/# exit

## 删除容器

$ docker rm abc123

## 数据丢了！没有任何办法恢复！

```

### 正确的数据持久化方式

按照 Docker 最佳实践，容器存储层应该保持 **无状态**。需要持久化的数据应该使用：

| 方式                                                                                                                  | 说明           | 适用场景     |
| ------------------------------------------------------------------------------------------------------------------- | ------------ | -------- |
| [**数据卷 (Volume)**](https://yeasy.gitbook.io/docker_practice/di-er-bu-fen-jin-jie-pian/08_data/8.1_volume)           | Docker 管理的存储 | 数据库、应用数据 |
| [**绑定挂载 (Bind Mount)**](https://yeasy.gitbook.io/docker_practice/di-er-bu-fen-jin-jie-pian/08_data/8.2_bind-mounts) | 挂载宿主机目录      | 开发时共享代码  |

```bash
## 使用数据卷（推荐）

$ docker run -v mydata:/var/lib/mysql mysql

## 使用绑定挂载

$ docker run -v /host/path:/container/path nginx
```

这些位置的读写 **会跳过容器存储层**，直接写入宿主机，性能更好，也不会随容器删除而丢失。

## 2.2.5 容器的生命周期

掌握容器的生命周期对于管理和调试 Docker 应用非常重要。如图 2-1 所示，容器会经历从创建到删除的完整状态流转。

{% @mermaid/diagram content="stateDiagram-v2
direction TB
\[\*] --> Created : docker create
Created --> Running : docker start
Running --> Stopped : docker stop
Running --> Paused : docker pause
Paused --> Running : docker unpause

```
Created --> Deleted : docker rm
Stopped --> Deleted : docker rm
Paused --> Deleted : docker rm

Deleted --> [*]" %}
```

图 2-1：容器生命周期状态流转图

### 常用生命周期命令

```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    # 强制删除运行中的容器
```

## 2.2.6 容器与进程的关系

> **核心概念**：容器的生命周期 = 主进程 (PID 1) 的生命周期

```bash
## 主进程运行，容器运行

## 主进程退出，容器停止

```

这就是为什么：

```bash
## 这个容器会立即退出（bash 没有输入就退出了）

$ docker run ubuntu

## 这个容器会持续运行（nginx 作为守护进程持续运行）

$ docker run nginx
```

详细解释请参考[后台运行](https://yeasy.gitbook.io/docker_practice/di-yi-bu-fen-ru-men-pian/05_container/5.2_daemon)章节。

## 2.2.7 容器的隔离性

Docker 容器通过以下 Namespace 实现隔离：

| Namespace | 隔离内容  | 效果                         |
| --------- | ----- | -------------------------- |
| **PID**   | 进程 ID | 容器内 PID 1 是应用进程，看不到宿主机其他进程 |
| **NET**   | 网络    | 独立的网络栈、IP 地址、端口            |
| **MNT**   | 文件系统  | 独立的根目录和挂载点                 |
| **UTS**   | 主机名   | 独立的主机名和域名                  |
| **IPC**   | 进程间通信 | 独立的信号量、消息队列                |
| **USER**  | 用户    | 独立的用户和组 ID                 |

> 想深入了解？请阅读[底层实现 - 命名空间](https://yeasy.gitbook.io/docker_practice/di-san-bu-fen-shen-ru-pian/12_implementation/12.2_namespace)。
