Chapter 01

容器化概念与 Docker 架构

理解"在我电脑能跑"问题的根源,掌握容器与虚拟机的本质区别

"在我电脑能跑"问题的根源

这是每个开发者都遇到过的经典困境:你的代码在本机运行完美,但部署到测试或生产服务器后就出错了。根源在于两个方面:

📦
依赖版本不同 本机装的是 Node.js 18,服务器上是 Node.js 14。本机是 Python 3.11,服务器是 Python 3.8。第三方库版本、系统库版本都可能不同。
⚙️
环境配置不同 环境变量、配置文件路径、文件权限、端口占用情况、操作系统版本(甚至 macOS vs Linux 的行为差异)。

传统的解决方案是编写长长的"部署文档",列出所有需要安装的软件和配置步骤。但这种方式脆弱且不可靠——Docker 的出现彻底改变了这一切。

虚拟机 vs 容器

容器不是第一个解决环境隔离问题的技术,虚拟机(VM)更早。了解二者的区别有助于理解容器的价值。

虚拟机架构

物理硬件 (CPU/内存/磁盘)
宿主操作系统 + Hypervisor
Guest OS 1(完整 Linux/Windows)
应用依赖库
应用程序 A

容器架构

物理硬件 (CPU/内存/磁盘)
宿主操作系统内核(共享)
容器运行时(Docker Engine)
应用依赖库(隔离)
应用程序 A
对比维度虚拟机容器
隔离级别完整操作系统隔离进程级隔离(共享宿主内核)
启动时间分钟级(需启动完整 OS)秒级甚至毫秒级
占用空间几 GB(含完整 OS)几十 MB(仅含应用依赖)
性能开销较大(硬件虚拟化)极小(近乎原生)
密度一台宿主机跑数十个 VM一台宿主机跑数百个容器
安全隔离更强(独立内核)较强(共享内核,有逃逸风险)
适用场景不同 OS、强安全隔离微服务、快速部署、CI/CD
💡

它们并不互斥 — 在实际生产中,通常在虚拟机(云服务器)上运行容器。容器提供应用级隔离,虚拟机提供底层安全隔离。

容器技术的底层:Linux 特性

Docker 本身并没有发明什么新技术,它是对 Linux 内核若干已有特性的优雅封装和工具化。

Linux Namespace(命名空间)

Namespace 让每个容器"认为"自己拥有整个系统。Linux 提供多种命名空间:

Control Groups / cgroups(控制组)

Namespace 解决了"看到什么"的问题,cgroups 解决"能用多少"的问题。它限制并统计容器的资源使用:

Union File System(联合文件系统)

容器镜像的分层存储依赖联合文件系统。Docker 默认使用 overlay2 驱动:

镜像基础层(只读)— FROM ubuntu:22.04
镜像层 2(只读)— RUN apt-get install curl
镜像层 3(只读)— COPY app /app
容器可写层(运行时创建,删除容器则消失)

多个容器可以共享同一个只读镜像层,节省磁盘空间,同时写时复制(Copy-on-Write)机制确保各容器的修改互不影响。

Docker 核心概念

Docker 架构:Client-Server 模式

Docker 采用 Client-Server 架构,各组件职责清晰:

# Docker 架构调用链

docker CLI(客户端)
    │  通过 Unix Socket / TCP 发送 REST API 请求
    ▼
dockerd(Docker Daemon)
    │  高层管理:镜像、容器、网络、Volume
    ▼
containerd(OCI 容器运行时管理器)
    │  管理容器的完整生命周期
    ▼
containerd-shim
    │  进程监管,与 containerd 解耦
    ▼
runc(OCI 容器运行时)
    │  实际调用 Linux Namespace + cgroups
    ▼
Linux 内核(Namespace / cgroups / overlayfs)

这种分层架构的好处是:即使 dockerd 重启,正在运行的容器(containerd 管理)不会受影响。

Docker Desktop vs Docker Engine

🖥️
Docker Desktop(Mac / Windows) 包含一个轻量 Linux 虚拟机(因为 Docker 依赖 Linux 内核),GUI 管理界面,自带 Docker Compose 和 Kubernetes。适合开发环境使用,免费用于个人使用。
🐧
Docker Engine(Linux 服务器) 纯命令行,直接运行在 Linux 内核上(无需虚拟机),性能最佳。生产服务器、CI/CD 环境的标准选择,完全开源免费。

镜像分层原理

理解分层是理解 Docker 性能和缓存机制的关键:

# 每条 Dockerfile 指令创建一层
FROM ubuntu:22.04          # 层 1(从 Registry 拉取)
RUN apt-get update           # 层 2(新增文件)
RUN apt-get install -y curl  # 层 3(新增文件)
COPY ./app /app              # 层 4(新增文件)
CMD ["./app"]               # 层 5(仅元数据,不创建文件层)

Docker 27.x 与 containerd 集成

Docker 27(2024年发布)进一步深化了与 containerd 的集成。理解这个关系有助于排查高级问题。

# 查看 Docker 版本(包含 containerd 和 runc 版本)
docker version
# Client: Docker Engine - Community
#  Version: 27.x.x
# Server: Docker Engine - Community
#  Engine:
#   Version: 27.x.x
#  containerd:
#   Version: 1.7.x
#  runc:
#   Version: 1.1.x

# Docker 27 新特性:containerd 镜像存储(实验)
# 在 /etc/docker/daemon.json 中启用:
# { "features": { "containerd-snapshotter": true } }
💡

为什么 dockerd 重启不影响运行中容器 — 容器进程由 containerd-shim 监管,与 dockerd 解耦。即使重启 Docker 守护进程(systemctl restart docker),已运行的容器不会中断,只是短暂失去管理能力(约数秒),重启完成后自动恢复管理。

边界情况与常见误区

⚠️

误区 1:容器 = 虚拟机 — 容器共享宿主机内核,无法运行不同操作系统的内核。在 Linux 宿主机上无法直接运行 Windows 容器(反之亦然)。Docker Desktop for Mac/Windows 内置了一个轻量 Linux VM 专门运行 Linux 容器。

⚠️

误区 2:容器内的 root 就是宿主机的 root — 在没有 User Namespace 的情况下,容器内的 root(UID 0)确实就是宿主机的 root,这是安全风险。生产环境应使用非 root 用户运行容器,或者开启 rootless 模式(docker context use rootless)。

⚠️

误区 3:容器数据会自动持久化 — 容器可写层在容器删除时消失。数据库容器如果不挂载 Volume,删除容器后所有数据都会丢失。详见第6章。

⚠️

误区 4:镜像拉取总是最新版docker pull nginx 实际上是拉取 nginx:latest 标签。如果本地已有该标签,Docker 不会自动重新拉取。要确保最新版,需显式用 docker pull nginx:latest 或用版本号标签。

overlay2 存储驱动深入

Docker 默认的 overlay2 驱动基于 Linux 的 OverlayFS,理解它的工作原理有助于排查磁盘空间问题。

# 查看存储驱动信息
docker info | grep Storage
# Storage Driver: overlay2

# 查看镜像和容器占用的磁盘空间
docker system df

# 清理所有未使用资源(镜像、容器、网络、卷)
docker system prune -a --volumes

# OverlayFS 的目录结构(Linux 上)
# /var/lib/docker/overlay2/
# ├── <layer-id-1>/   ← 镜像层(只读)
# │   ├── diff/        ← 本层实际存储的文件
# │   ├── link         ← 短 ID 符号链接
# │   └── lower        ← 指向父层
# └── <layer-id-2>/
#     ├── diff/
#     ├── merged/      ← 联合视图(容器实际看到的)
#     ├── work/        ← OverlayFS 内部工作目录

本章小结 — 容器通过 Linux Namespace 隔离进程视图,通过 cgroups 限制资源,通过 overlay2 共享文件系统层。Docker 是这些技术的工具化封装:dockerd 提供高层 API,containerd 管理容器生命周期,runc 执行底层系统调用。理解这三层架构是排查容器问题的基础。