第02章:Docker 架构原理

第02章:Docker 架构原理

本章目标:深入理解 Docker 的 C/S 架构、核心组件及其交互方式,掌握镜像分层的底层实现原理。


2.1 Docker 整体架构

2.1.1 C/S(客户端-服务端)架构

Docker 采用经典的 C/S 架构,分为三个核心部分:

复制代码
┌──────────────────────────────────────────────────────────┐
│                    Docker 架构总览                         │
│                                                          │
│  ┌─────────────┐                                         │
│  │   Docker     │  docker build / docker pull             │
│  │   Client     │  docker run / docker ps                 │
│  │  (CLI 工具)  │                                         │
│  └──────┬──────┘                                         │
│         │ REST API (Unix Socket / TCP)                    │
│         ▼                                                 │
│  ┌─────────────────────────────────────────────┐         │
│  │              Docker Daemon (dockerd)          │         │
│  │                                               │         │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐     │         │
│  │  │ 镜像管理  │ │ 网络管理  │ │ 卷管理    │     │         │
│  │  └──────────┘ └──────────┘ └──────────┘     │         │
│  │  ┌──────────────────────────────────────┐   │         │
│  │  │         containerd                    │   │         │
│  │  │  ┌────────────┐  ┌─────────────┐    │   │         │
│  │  │  │  containerd │  │  containerd  │    │   │         │
│  │  │  │  -shim      │  │  -shim      │    │   │         │
│  │  │  └──────┬─────┘  └──────┬──────┘    │   │         │
│  │  │         │                │            │   │         │
│  │  │    ┌────┴───┐     ┌─────┴────┐      │   │         │
│  │  │    │容器 A   │     │容器 B     │      │   │         │
│  │  │    └────────┘     └──────────┘      │   │         │
│  │  └──────────────────────────────────────┘   │         │
│  └─────────────────────────────────────────────┘         │
└──────────────────────────────────────────────────────────┘

2.1.2 三大核心组件详解

① Docker Client(客户端)

Docker 客户端是你与 Docker 交互的主要方式:

bash 复制代码
# 你输入的所有 docker 命令都由客户端接收和解析
docker run -d -p 80:80 nginx
docker ps -a
docker images

客户端的职责

  • 解析命令行参数
  • 构造 REST API 请求
  • 将请求发送给 Docker Daemon
  • 格式化并展示返回结果

客户端可以通过以下方式连接 Daemon:

  • Unix Socket (默认,仅本机):unix:///var/run/docker.sock
  • TCP (远程管理):tcp://remote-host:2375
  • SSH(安全远程):通过 SSH 隧道连接
② Docker Daemon(守护进程 / dockerd)

Docker Daemon 是 Docker 的核心服务,负责:

  • 监听 API 请求
  • 管理 Docker 对象(镜像、容器、网络、卷)
  • 与其他 Daemon 通信(集群模式)
bash 复制代码
# 查看 dockerd 进程
ps aux | grep dockerd

# 查看 Docker 系统信息
docker info

# 查看 Docker 版本
docker version
③ containerd + runc(容器运行时)

Docker 的容器运行时分为两层:

复制代码
dockerd
  └── containerd(容器运行时管理器)
        └── containerd-shim(容器进程的父进程)
              └── runc(OCI 标准容器运行时)
                    └── 容器进程
组件 职责
containerd 容器生命周期管理、镜像分发、存储管理
containerd-shim 作为容器进程的父进程,允许 dockerd 重启而不影响容器
runc 实际创建和运行容器(调用 Namespace、Cgroups 等内核接口)
bash 复制代码
# 查看 containerd 状态
systemctl status containerd

# 查看 runc 版本
runc --version

2.2 Docker 镜像的分层原理

2.2.1 镜像 = 只读层的叠加

Docker 镜像不是一整块文件,而是由多个只读层(Layer) 堆叠而成:

复制代码
Docker 镜像的层级结构:

┌─────────────────────────────────┐
│  Layer 4: COPY app.py /app/     │  ← 3KB (hash: sha256:abc123...)
├─────────────────────────────────┤
│  Layer 3: RUN pip install ...   │  ← 15MB (hash: sha256:def456...)
├─────────────────────────────────┤
│  Layer 2: RUN apt-get install   │  ← 80MB (hash: sha256:789ghi...)
├─────────────────────────────────┤
│  Layer 1: FROM ubuntu:22.04     │  ← 77MB (hash: sha256:jkl012...)
└─────────────────────────────────┘

每层只存储与上一层的差异(delta)

2.2.2 分层的好处

复制代码
传统方式(无分层):
项目A: [OS + Python 3.8 + App A] = 800MB
项目B: [OS + Python 3.8 + App B] = 800MB
项目C: [OS + Python 3.11 + App C] = 850MB
总占用: 2450MB

Docker 分层方式:
基础层:    [OS (Ubuntu)]           = 77MB    ← 三个项目共享
Python层:  [Python 3.8]            = 120MB   ← A和B共享
Python层:  [Python 3.11]           = 130MB   ← C使用
App A层:   [App A 代码]            = 5MB
App B层:   [App B 代码]            = 8MB
App C层:   [App C 代码]            = 6MB
总占用: ~346MB(去重后实际更少)

关键优势

  1. 存储效率:相同层只存储一份,多个镜像共享基础层
  2. 传输效率docker pull 时只下载本地不存在的层
  3. 构建效率docker build 时,未修改的层直接使用缓存
  4. 安全审计:每层都有唯一哈希标识,可追溯变更来源

2.2.3 写时复制(Copy-on-Write)

容器启动时,Docker 在镜像层之上添加一个可写层(Container Layer)

复制代码
运行中的容器:
┌─────────────────────────────────┐
│  Container Layer (可写)          │  ← 修改、新增的文件在这里
│  - 修改了 /etc/config           │
│  - 新增了 /tmp/data.log         │
├─────────────────────────────────┤
│  Layer 4: COPY app.py /app/     │  ← 只读(镜像层)
├─────────────────────────────────┤
│  Layer 3: RUN pip install ...   │  ← 只读(镜像层)
├─────────────────────────────────┤
│  Layer 2: RUN apt-get install   │  ← 只读(镜像层)
├─────────────────────────────────┤
│  Layer 1: FROM ubuntu:22.04     │  ← 只读(镜像层)
└─────────────────────────────────┘

读取文件时的查找顺序:
1. 先在 Container Layer 查找
2. 没找到 → 在 Layer 4 查找
3. 没找到 → 在 Layer 3 查找
4. ... 依次向下查找

修改文件时(写时复制):
1. 从镜像层将文件复制到 Container Layer
2. 在 Container Layer 中修改副本
3. 原始镜像层文件不受影响

2.2.4 镜像的唯一标识

每个层都通过 SHA256 哈希 作为唯一标识:

bash 复制代码
# 查看镜像的分层信息
docker image inspect nginx:latest

# 输出示例(简化):
{
    "RootFS": {
        "Type": "layers",
        "Layers": [
            "sha256:2edcec35b5139b8d5c4d7cf15d1f5b5e2...",
            "sha256:e2f5911f58b28f5d0f5b3a8f23d8b3e1b...",
            "sha256:12d84a1c9e0e8f23a4b5c6d7e8f9a0b1c..."
        ]
    }
}

# 查看镜像的完整历史(每一层的构建命令)
docker history nginx:latest

2.3 Docker 的存储驱动

2.3.1 什么是存储驱动

存储驱动负责管理镜像层和容器可写层的具体存储实现。不同的存储驱动在性能、稳定性和资源使用上有差异。

2.3.2 常见存储驱动

存储驱动 底层技术 适用场景 性能特点
overlay2 OverlayFS Linux 默认推荐 读写性能优秀,内存效率高
btrfs Btrfs 文件系统 Btrfs 分区 支持快照,适合特定场景
zfs ZFS 文件系统 ZFS 分区 数据完整性高,资源消耗大
vfs 直接复制 调试用 性能最差,每层都完整复制
fuse-overlayfs FUSE 实现的 OverlayFS Rootless 模式 非 root 用户可用
bash 复制代码
# 查看当前使用的存储驱动
docker info | grep "Storage Driver"
# 输出:Storage Driver: overlay2

2.3.3 overlay2 详解

overlay2 是目前最推荐的存储驱动,它将层叠加为一个统一视图:

复制代码
overlay2 工作原理:

lowerdir (只读层,可以有多个)
    ↓
    ┌─────┐
    │ L3  │  ← 最底层(基础镜像)
    ├─────┤
    │ L2  │
    ├─────┤
    │ L1  │
    └──┬──┘
       │
merged (联合挂载点 - 容器看到的视图)
    ↓
    ┌──────────────┐
    │  统一文件系统  │
    └──────┬───────┘
           │
upperdir (可写层 - 容器的修改)
    ↓
    ┌─────┐
    │ Lw  │  ← 容器运行时的修改
    └─────┘

workdir (OverlayFS 内部使用的工作目录)

2.4 Docker 的网络架构

2.4.1 libnetwork

Docker 使用 libnetwork 库来管理容器网络,它抽象了多种网络实现:

复制代码
┌──────────────────────────────────────────┐
│              Docker 网络架构               │
│                                          │
│  ┌──────────┐                            │
│  │  daemon   │                           │
│  └────┬─────┘                            │
│       │                                  │
│  ┌────┴─────────────────────┐           │
│  │       libnetwork          │           │
│  │  ┌─────────┐ ┌────────┐ │           │
│  │  │ CNM     │ │ drivers │ │           │
│  │  │ 模型    │ │        │ │           │
│  │  └─────────┘ │- bridge│ │           │
│  │              │- host  │ │           │
│  │              │- overlay│ │           │
│  │              │- macvlan│ │           │
│  │              │- none  │ │           │
│  └──────────────┴────────┘ │           │
└──────────────────────────────────────────┘

2.4.2 网络模型(CNM)

Docker 网络遵循 容器网络模型(Container Network Model,CNM)

复制代码
CNM 三大组件:

Sandbox(沙盒)         Endpoint(端点)         Network(网络)
┌─────────────┐      ┌─────────────┐      ┌─────────────┐
│ - 网络栈    │      │ - veth pair │      │ - 网络名称  │
│ - IP地址    │◄────►│ - 连接点    │◄────►│ - IP子网    │
│ - 路由表    │      │             │      │ - 网关      │
│ - DNS       │      │             │      │ - 驱动      │
└─────────────┘      └─────────────┘      └─────────────┘

每个容器 = 1个 Sandbox
每个容器连接到网络 = 1个 Endpoint
每个网络可以连接多个容器

2.5 Docker 的镜像分发机制

2.5.1 镜像分发协议

Docker 使用 OCI Distribution Spec 来分发镜像:

复制代码
推送/拉取镜像的流程:

docker push myapp:v1.0
        │
        ▼
┌──────────────────┐
│  Docker Client   │
└────────┬─────────┘
         │ HTTPS API
         ▼
┌──────────────────┐
│ Registry Server  │  (如 Docker Hub, Harbor)
│                  │
│  ┌────────────┐  │
│  │ Manifest   │  │  ← 清单文件,描述镜像由哪些层组成
│  ├────────────┤  │
│  │ Layer 1    │  │  ← 每层是独立的 blob 对象
│  │ Layer 2    │  │
│  │ Layer 3    │  │
│  └────────────┘  │
└──────────────────┘

2.5.2 镜像清单(Manifest)

Manifest 是一个 JSON 文件,描述了镜像的完整信息:

json 复制代码
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  "config": {
    "mediaType": "application/vnd.docker.container.image.v1+json",
    "size": 7023,
    "digest": "sha256:b5b2b2c54748232..."
  },
  "layers": [
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 32654,
      "digest": "sha256:e692418e4cb..."
    },
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "size": 16724,
      "digest": "sha256:3c3dda4e7a..."
    }
  ]
}

2.6 Docker 的完整工作流程

2.6.1 docker run 的完整链路

复制代码
docker run -d -p 8080:80 nginx:latest

执行步骤:

1. Docker Client 解析命令
   └── 构造 API 请求:POST /containers/create

2. Docker Daemon 接收请求
   ├── 检查本地是否有 nginx:latest 镜像
   │   ├── 有 → 直接使用
   │   └── 没有 → 调用 Registry 拉取
   │         ├── 拉取 manifest
   │         ├── 对比本地层,下载缺失的层
   │         └── 组装镜像
   │
   ├── 创建容器配置
   │   ├── 设置网络(端口映射 8080→80)
   │   ├── 设置存储(可写层)
   │   └── 设置资源限制
   │
   └── 调用 containerd 创建容器
         ├── containerd 调用 containerd-shim
         ├── shim 调用 runc 创建容器
         │   ├── 创建 namespace(隔离进程、网络等)
         │   ├── 设置 cgroups(限制 CPU、内存等)
         │   ├── 挂载文件系统(overlay2 层叠)
         │   └── 启动容器进程(nginx -g 'daemon off;')
         └── 容器进程运行在独立的隔离环境中

3. 返回容器 ID
   └── Docker Client 显示容器 ID

2.6.2 docker build 的完整链路

复制代码
docker build -t myapp:v1.0 .

执行步骤:

1. 发送构建上下文(Build Context)
   └── 将当前目录(.)打包发送给 Docker Daemon

2. 解析 Dockerfile
   └── 逐条执行指令

3. 每条指令生成一个新的镜像层
   FROM ubuntu:22.04        → 使用已有层或拉取
   RUN apt-get update       → 创建新层(安装包)
   COPY . /app              → 创建新层(复制文件)
   CMD ["python", "app.py"] → 仅修改元数据,不创建新层

4. 构建缓存机制
   ├── 检查每一层是否有缓存
   │   ├── 有缓存 → 使用缓存层(⚡ 快速)
   │   └── 无缓存 → 执行指令,创建新层
   └── 一旦某层无缓存,后续所有层都重新构建

2.7 Docker Daemon 的配置

2.7.1 配置文件位置

操作系统 配置文件路径
Linux /etc/docker/daemon.json
Windows C:\ProgramData\docker\config\daemon.json
macOS Docker Desktop → Settings → Docker Engine

2.7.2 常用配置项

json 复制代码
{
  "data-root": "/data/docker",
  "storage-driver": "overlay2",
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  },
  "registry-mirrors": [
    "https://mirror.ccs.tencentyun.com"
  ],
  "insecure-registries": [],
  "max-concurrent-downloads": 10,
  "max-concurrent-uploads": 5,
  "default-address-pools": [
    { "base": "172.17.0.0/16", "size": 24 }
  ],
  "live-restore": true,
  "userland-proxy": false
}
配置项 说明
data-root Docker 数据存储目录(镜像、容器、卷等)
storage-driver 存储驱动类型
log-driver 容器日志驱动
log-opts 日志驱动选项(大小限制)
registry-mirrors 镜像加速器
live-restore dockerd 重启时保持容器运行
userland-proxy 禁用用户态代理,提高性能
bash 复制代码
# 重新加载 Docker 配置
sudo systemctl daemon-reload
sudo systemctl restart docker

# 验证配置
docker info

2.8 Docker 的命名空间详解

2.8.1 六大命名空间

bash 复制代码
# 进入一个运行中的容器
docker run -it --rm ubuntu:22.04 /bin/bash

# 在容器内查看(隔离效果)
# 1. PID Namespace - 只能看到容器自己的进程
ps aux
# USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
# root         1  0.0  0.0   2488  1320 pts/0    Ss   10:00   0:00 /bin/bash

# 2. NET Namespace - 独立的网络栈
ip addr
# 1: lo: <LOOPBACK,UP,LOWER_UP> ...
# 15: eth0@if16: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
#     inet 172.17.0.2/16 ...

# 3. MNT Namespace - 独立的文件系统
ls /
# bin  boot  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

# 4. UTS Namespace - 独立的主机名
hostname
# a1b2c3d4e5f6  (容器ID的前12位)

2.8.2 Namespace 的系统调用

c 复制代码
// Linux 提供的 namespace 系统调用
// clone() - 创建新进程并放入新的 namespace
int clone(int (*fn)(void *), void *stack, int flags, void *arg);

// 常用 flags
CLONE_NEWPID   // 新的 PID namespace
CLONE_NEWNET   // 新的 NET namespace
CLONE_NEWNS    // 新的 MNT namespace
CLONE_NEWUTS   // 新的 UTS namespace
CLONE_NEWIPC   // 新的 IPC namespace
CLONE_NEWUSER  // 新的 USER namespace

// unshare() - 将当前进程放入新的 namespace
int unshare(int flags);

// setns() - 将当前进程加入已有的 namespace
int setns(int fd, int nstype);

2.9 Docker 的 Cgroups 详解

2.9.1 Cgroups v1 vs v2

特性 Cgroups v1 Cgroups v2
层级结构 每个控制器独立层级 统一层级
资源分配 更灵活但复杂 更直观、统一
内存限制 memory.limit_in_bytes memory.max
CPU 限制 cpu.cfs_quota_us cpu.max
默认版本 CentOS 7/8 Ubuntu 20.04+、Fedora

2.9.2 查看容器的 Cgroups 配置

bash 复制代码
# 启动一个限制资源的容器
docker run -d --name test-container \
  --cpus="1.5" \
  --memory="512m" \
  --memory-swap="512m" \
  nginx:latest

# 查看容器的 cgroup 限制
# Cgroups v2 方式
cat /sys/fs/cgroup/system.slice/docker-<container-id>.scope/memory.max
cat /sys/fs/cgroup/system.slice/docker-<container-id>.scope/cpu.max

2.10 动手实验

实验 2.1:观察镜像的分层结构

bash 复制代码
# 拉取一个镜像
docker pull python:3.11-slim

# 查看镜像的每一层
docker history python:3.11-slim

# 查看详细的层信息
docker inspect python:3.11-slim

实验 2.2:观察容器的隔离性

bash 复制代码
# 终端1:在宿主机上查看进程
ps aux | grep nginx

# 终端2:启动 Nginx 容器
docker run -d --name nginx-test -p 8080:80 nginx:latest

# 终端3:在宿主机查看进程(能看见容器进程)
ps aux | grep nginx
# root      1234  ...  nginx: master process nginx -g daemon off;
# system+  1235  ...  nginx: worker process

# 终端4:进入容器查看进程(只看到自己的进程)
docker exec -it nginx-test ps aux
# PID TTY      STAT   TIME COMMAND
# 1   ?        Ss     0:00 nginx: master process nginx -g daemon off;
# 21  ?        S+     0:00 ps aux

实验 2.3:验证写时复制

bash 复制代码
# 启动一个 Ubuntu 容器
docker run -it --name copy-test ubuntu:22.04 /bin/bash

# 在容器内创建文件
echo "Hello Container" > /tmp/test.txt
cat /tmp/test.txt
# Hello Container

# 退出容器
exit

# 查看容器的可写层大小
docker inspect copy-test --format='{{.GraphDriver.Data}}'

# 再次启动容器,文件仍然存在(在可写层中)
docker start -ai copy-test
cat /tmp/test.txt
# Hello Container
exit

2.11 本章小结

要点 内容
架构模式 C/S 架构:Client(CLI)→ Daemon(dockerd)→ containerd → runc
镜像分层 每条 Dockerfile 指令生成一层,层通过 SHA256 哈希唯一标识
写时复制 容器启动时添加可写层,修改操作通过 CoW 实现
存储驱动 推荐 overlay2,管理层的叠加和读写
命名空间 PID/NET/MNT/UTS/IPC/USER 六种隔离机制
Cgroups 限制 CPU、内存、磁盘 I/O、网络带宽等资源

2.12 课后练习

  1. 理解题:解释 Docker 镜像的分层存储如何提高存储效率和构建速度。
  2. 实操题 :使用 docker history 查看 3 个不同镜像的层结构,分析每层的内容。
  3. 进阶题:配置 Docker 使用远程 Daemon(TCP 模式),体验 C/S 架构的网络通信。

📖 下一章:Docker 安装部署 ------ 在不同操作系统上搭建 Docker 环境