searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Docker 原理与架构设计

2026-07-13 17:03:56
8
0

阅读完本文后,您将能够:

  • 说清 Linux 容器赖以实现的三大内核机制——命名空间(namespaces)、控制组(cgroups)与联合文件系统(UnionFS);
  • 描述 Docker 的分层运行时架构(dockerd → containerd → runc)及各组件职责;
  • 解释镜像分层、写时复制与构建缓存的工作原理;
  • 识别 Docker 的网络与存储模型,以及其安全边界与加固手段;
  • 将上述原理转化为生产环境下的容器架构设计实践。

重要 本文聚焦 Docker 引擎(Docker Engine,社区版/企业版)的原理与架构。容器编排(Kubernetes、Docker Swarm)与镜像分发(Harbor 等)属于上层主题,仅在必要时提及。


目录

  1. 概述:为什么需要容器
  2. 容器化的底层原理
  3. Docker 的组件架构
  4. 镜像分层与构建原理
  5. 容器运行原理与生命周期
  6. 网络与存储模型
  7. 架构演进历史
  8. 安全模型与加固
  9. 原理对架构设计的启示
  10. 常见误区与故障排除
  11. 后续步骤

1. 概述:为什么需要容器

传统应用部署长期面临「在我机器上能跑」的环境一致性问题:依赖版本、系统库、配置文件、运行时环境的细微差异,都会导致应用在开发、测试、生产之间行为不一致。虚拟机虽能解决一致性问题,但每个虚拟机都要运行一整套客户操作系统,体积大、启动慢、资源开销高。

容器是一种轻量级的操作系统级虚拟化技术。它将应用及其全部依赖打包为一个标准化单元,共享宿主机内核,无需启动完整的客户操作系统,因此具备以下特点:

特性 容器 虚拟机
虚拟化层级 操作系统级(共享内核) 硬件级(每实例一整套 OS)
启动时间 秒级 分钟级
资源开销 低(MB 级) 高(GB 级)
隔离强度 较弱(共享内核) 强(硬件隔离)
单机密度 高(数百实例) 低(数十实例)

Docker 并不是容器的发明者——Linux 内核的容器原语(namespaces、cgroups)在 Docker 诞生(2013 年)前就已存在——但 Docker 首次把这些底层能力封装成「镜像 + 容器 + 一致的工作流」,让容器技术得以大规模普及,并推动形成了今天的 OCI(Open Container Initiative)标准。

提示 **一句话概括:**容器 = 受隔离与限流的进程。它本质上仍是宿主机上的普通进程,只是被内核「骗」成了以为自己独占整台机器。


2. 容器化的底层原理

容器的「隔离」「限流」「分层」三大能力,分别由 Linux 内核的三类机制实现。理解这三点,就理解了容器的本质。

2.1 命名空间(Namespaces):实现隔离

命名空间让一组进程看到一份****独立、被裁剪过的系统资源视图,从而彼此隔离。Docker 主要使用以下命名空间:

命名空间 隔离对象 效果
PID 进程号 容器内进程从 PID 1 重新编号,看不到宿主机其他进程
NET 网络栈 独立的网卡、路由表、iptables、端口空间
IPC 进程间通信 独立的 System V IPC、POSIX 消息队列
MNT 挂载点 独立的文件系统视图
UTS 主机名与域名 独立的 hostname
USER 用户与用户组 ID 容器内的 root 可映射为宿主机上的非特权用户
Cgroup cgroup 视图 独立的 cgroup 层次结构视图(Linux 4.6+)

可以亲手验证。在宿主机启动一个容器,然后观察其命名空间:

# 启动一个长期运行的容器
docker run -d --name demo alpine sleep 3600
​
# 找到容器内 sleep 进程在宿主机上的真实 PID
PID=$(docker inspect -f '{{.State.Pid}}' demo)
echo $PID
​
# 查看该进程所属的所有命名空间
ls -l /proc/$PID/ns

提示 **也可以用 **lsns 命令列出宿主机上当前所有的命名空间及其属主进程,直观看到 Docker 为每个容器创建的独立命名空间集合。

2.2 控制组(Cgroups):实现资源限制

命名空间负责「隔离」,但隔离后的进程仍可能耗尽宿主机的 CPU、内存、磁盘 I/O。**cgroups(Control Groups) 负责对一组进程进行资源计量与限制**,是防止「吵闹的邻居」的关键。

主要资源控制器包括:

控制器 限制对象 Docker 对应参数
cpu / cpuacct CPU 配额与使用统计 --cpus--cpu-shares
memory 内存与交换空间 --memory--memory-swap
pids 进程数(防 fork 炸弹) --pids-limit
blkio 块设备 I/O --device-read-bps
devices 设备访问白名单 --device

cgroups 有 v1(按控制器分层次)与 v2(统一层级)两代。现代发行版默认逐步转向 cgroup v2。同样可以实地观察:

# 查看上面 demo 容器进程的 cgroup 归属
cat /proc/$PID/cgroup

重要 生产环境务必为每个容器显式设置 CPU 与内存上限。否则单个容器的异常(如内存泄漏)可能拖垮整台宿主机。

2.3 联合文件系统(UnionFS):实现分层

联合文件系统将****多个目录「叠加」成一个逻辑文件系统,是 Docker 镜像分层与「写时复制」的基础。

  • 镜像(image) 由若干**只读层(read-only layer)**堆叠而成,每一层对应 Dockerfile 中的一条指令(如 RUNCOPY)。
  • 容器(container) 在镜像之上再加一层可写层(writable layer)。所有对容器的修改都写入这一层,底层镜像保持不变。
  • 写时复制(Copy-on-Write, CoW):当容器需要修改位于只读层中的某个文件时,系统并不直接改写原文件,而是先把它复制到可写层再修改,从而保证镜像层的不变性与可共享性。

Docker 支持多种存储驱动,overlay2 是当前默认且性能最佳的实现:

存储驱动 说明
overlay2 默认推荐,基于 OverlayFS,性能与稳定性最佳
fuse-overlayfs 用于 rootless 模式
btrfs/zfs 依赖对应文件系统的高级特性
devicemapper/aufs 早期方案,现已不推荐

查看当前使用的存储驱动:

docker info | grep -i "storage driver"

2.4 一条命令的全过程

**把三大机制串联起来,一次 **docker run 在底层大致发生:

  1. 命名空间为容器构建独立的进程/网络/文件系统视图;
  2. cgroups套上 CPU、内存等资源限制;
  3. 联合文件系统把镜像各层叠加,并附加可写层;
  4. 最终 exec 出容器入口进程——它是一个****被隔离、被限流、拥有叠加文件系统视图的普通宿主机进程

3. Docker 的组件架构

Docker 采用****客户端/服务端(C/S)架构,并自 1.11 版起拆分为「高层运行时 + 低层运行时」的分层设计。整体结构如下图所示。

3.1 架构总览

┌─────────────────────────────────────────────────────────────────┐
│                          宿主机 (Host)                           │
│                                                                 │
│   ┌──────────────┐   REST API    ┌──────────────────────────┐   │
│   │  Docker CLI  │ ─────────────▶│     dockerd (守护进程)    │   │
│   │   (client)   │  Unix socket  │  镜像/网络/卷/API 管理     │   │
│   └──────────────┘  /var/run/    └─────────────┬────────────┘   │
│                                     docker.sock │ gRPC          │
│                                                  ▼               │
│                                       ┌──────────────────────┐  │
│                                       │     containerd       │  │
│       ┌──────────────────┐            │ 容器生命周期/镜像分发 │  │
│       │     Registry     │◀──────────▶│   /快照管理           │  │
│       │  (Hub/Harbor)    │   拉取/推送 └──────────┬───────────┘  │
│       └──────────────────┘                       │ 每容器一个     │
│                                       ┌──────────┴───────────┐  │
│                                       ▼                      ▼  │
│                                ┌─────────────┐        ┌────────┐ │
│                                │containerd-  │        │ ...    │ │
│                                │  shim       │        │        │ │
│                                │   │ runc    │        │        │ │
│                                │   ▼         │        │        │ │
│                                │ 容器进程     │        │        │ │
│                                └─────────────┘        └────────┘ │
│                                                                 │
│   Linux 内核:  namespaces  |  cgroups  |  UnionFS  | netfilter   │
└─────────────────────────────────────────────────────────────────┘

3.2 各组件职责

组件 角色 职责
Docker ClientdockerCLI) 客户端 解析用户命令,通过 REST API 与守护进程通信
dockerd 守护进程/高层管理 监听 API、管理镜像/容器/网络/卷、构建镜像
containerd 高层容器运行时 管理容器完整生命周期、镜像拉取与分发、快照、supervision
containerd-shim 每容器一个的垫片 作为容器进程的父进程,接管 STDIO 与退出码,使守护进程重启不影响容器
runc 低层 OCI 运行时 依据 OCI 运行时规范,真正「组装」namespaces + cgroups 并启动容器进程
Registry 镜像仓库 存储与分发镜像(Docker Hub、私有仓库、Harbor 等)

3.3 调用链:从命令到进程

**用户执行 **docker run 后,请求沿分层架构逐级下发:

docker CLI  ──REST──▶  dockerd  ──gRPC──▶  containerd
   ──创建 shim──▶  containerd-shim  ──OCI runtime spec──▶  runc
   ──setns+cgroup──▶  容器入口进程

提示 **注意 **runc 在启动容器进程后会随即退出,由常驻的 containerd-shim 充当容器进程的父进程。正是这一设计,使得 dockerd 甚至 containerd 重启升级时,容器仍能继续运行——这是分层架构带来的关键收益。

3.4 OCI 标准

Docker 在 2015 年联合业界发起 ****OCI(Open Container Initiative),定义了两项事实标准:

  • 镜像规范(image-spec):镜像的格式、清单(manifest)、层与配置的标准结构;
  • 运行时规范(runtime-spec):描述「如何把一个容器跑起来」的环境与行为(即 runc 读取的那份配置)。

标准化使得 Docker 镜像可以在 containerd、CRI-O、Kubernetes 等不同运行时之间无缝流通,也使得低层运行时(runc、kata、gVisor)可以互换。


4. 镜像分层与构建原理

4.1 镜像、层与容器

  • 镜像(Image):只读的分层模板,包含应用代码、运行时、库与配置。镜像通过内容寻址——每层与镜像整体都以 SHA256 摘要标识。
  • 层(Layer):文件系统的增量变更集,可被多个镜像共享复用,节省存储与传输。
  • 容器(Container):镜像的运行实例,等于「全部只读镜像层 + 一个可写层 + 隔离环境」。
┌───────────────────────┐  ◀── 可写层(容器独有,随容器删除而消失)
            │   Writable layer       │
            ├───────────────────────┤
            │  Layer N: CMD/ENTRYPOINT│ ◀── 只读层(来自镜像,多容器共享)
            ├───────────────────────┤
            │  Layer 2: COPY 源码    │
            ├───────────────────────┤
            │  Layer 1: 安装依赖     │
            ├───────────────────────┤
            │  Layer 0: 基础镜像     │
            └───────────────────────┘

4.2 Dockerfile 与构建过程

Dockerfile 中****每条会修改文件系统的指令RUNCOPYADD)都会产生一个新层。构建时:

  1. 客户端将****构建上下文(build context)发送给 dockerd;
  2. dockerd 逐条执行指令,每条指令基于上一层产生新层;
  3. 构建缓存:若某条指令及其上层均未变化,则直接复用缓存层,跳过实际执行——这是加速重复构建的核心机制。

一个简化的示例:

# 基础镜像(Layer 0)
FROM alpine:3.20
​
# 安装依赖(Layer 1)——变动频率低,放前面以命中缓存
RUN apk add --no-cache curl
​
# 拷贝源码(Layer 2)——变动频率高,放后面
COPY app.sh /app/app.sh
​
# 入口(不产生文件层,仅修改镜像配置)
CMD ["/app/app.sh"]

重要 缓存是「指令 + 上文」共同决定的。把****变动频繁的内容(如源代码)放在 Dockerfile 末尾,把变动少的内容(如依赖安装)放在前面,可最大化缓存命中率,显著缩短构建时间。

4.3 多阶段构建与镜像瘦身

**最终镜像往往不需要编译工具链。**多阶段构建(multi-stage build) 允许在一个 Dockerfile 中用多个 FROM,只在最终阶段拷入必要产物:

# 阶段 1:构建(含编译器,体积大)
FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN go build -o app .
​
# 阶段 2:运行(仅含二进制,体积小)
FROM alpine:3.20
COPY --from=builder /src/app /usr/local/bin/app
CMD ["app"]

**最终镜像只包含第二阶段的内容,体积可从数百 MB 降至数 MB。常见瘦身手段还包括:合并 **RUN 指令、清理包缓存、选用 alpine/distroless 基础镜像。


5. 容器运行原理与生命周期

5.1 容器的状态

一个容器在其生命周期中处于以下状态之一:

created ──start──▶ running ──stop──▶ exited
      │                  │
      └───── pause ──────▶ paused ──unpause──▶ running
状态 含义
created 已创建(已分配命名空间与文件系统)但未启动入口进程
running 入口进程正在运行
paused 冻结容器内所有进程(cgroup freezer)
exited 进程已退出(容器与可写层仍保留,可重启)
removed 容器及其可写层被删除

5.2 PID 1 与信号处理

**容器入口进程是容器命名空间内的 **PID 1,需要承担两项传统上由 init 系统完成的职责:

  • 信号转发:接收 SIGTERM 等并优雅退出;
  • 回收僵尸进程(reaping)。

重要 **若直接以 shell 脚本或后台 fork 的进程作为入口,PID 1 常无法正确转发信号,导致 **docker stop 超时后被 SIGKILL 强杀,造成数据不一致。建议入口直接指向应用主进程,或使用 tinidumb-init 等轻量 init。


6. 网络与存储模型

6.1 存储模型

**容器文件系统位于可写层,**容器删除即丢失。持久化数据应使用以下机制:

类型 说明 适用场景
volume(卷) 由 Docker 管理,位于/var/lib/docker/volumes 生产首选,跨平台、易备份迁移
bind mount(绑定挂载) 直接挂载宿主机任意路径 开发期挂源码、配置文件
tmpfs(内存盘) 数据存于内存 临时、敏感数据,重启即失
# 使用命名卷持久化数据
docker run -d --name db -v pgdata:/var/lib/postgresql/data postgres:16

6.2 网络模型

Docker 提供多种网络驱动:

网络模式 说明 典型场景
bridge 默认。通过docker0网桥 + NAT(iptables/netfilter)访问外部 单机多容器互通
host 直接使用宿主机网络栈,无隔离 对网络性能要求极高、无需端口隔离
none 仅 loopback,无网络 离线计算、安全沙箱
overlay 跨主机 VXLAN 网络 Docker Swarm 多主机组网
macvlan 容器拥有独立 MAC/IP,直连物理网络 传统网络迁移、低延迟

提示 自定义 bridge 网络会自动为其中的容器提供基于容器名的 DNS 解析,因此多容器协作时应使用自定义网络而非默认 bridge

docker network create appnet
docker run -d --name app --network appnet myapp
docker run -d --name db  --network appnet postgres:16
# 在 app 容器内可直接用主机名 db 连接数据库

7. 架构演进历史

Docker 的架构并非一蹴而就,理解其演进有助于看清设计取舍。

阶段 时间 架构要点
LXC 时代 2013 初版 直接调用 LXC 工具创建容器,强依赖宿主机 LXC
libcontainer 2014 自研 Go 实现的运行时库,摆脱对 LXC 的依赖
运行时分层 2016(v1.11) 拆分为 dockerd → containerd → runc 三层,引入 OCI 标准
containerd 独立 2017 containerd 作为 CNCF 项目独立演进,并被 Kubernetes 用作 CRI 运行时
Rootless / v2 近年 支持 rootless 模式、cgroup v2、BuildKit 默认构建器

注意 演进的核心趋势是****关注点分离:把「镜像/网络/卷管理」「容器生命周期」「低层系统调用」分别交给 dockerd、containerd、runc,并统一于 OCI 标准。这使得各组件可独立升级、可被其他平台(如 Kubernetes)复用。


8. 安全模型与加固

容器的隔离****弱于虚拟机——所有容器共享宿主机内核。因此容器安全是一套纵深防御组合拳,而非单一机制。

机制 作用 默认是否启用
Namespaces 资源视图隔离
Cgroups 资源用量限制 是(需显式配置上限)
Linux Capabilities 细粒度权限裁剪,丢弃大部分特权能力 是(默认丢弃多数)
seccomp 过滤危险系统调用(默认 profile 屏蔽数十个)
AppArmor / SELinux 强制访问控制(MAC) 视发行版而定
NoNewPrivs 禁止通过 setuid 等提权
Rootless / USER namespace 容器内 root 映射为宿主机非特权用户 需显式开启

常见加固实践:

  • 以****非 root 用户运行容器进程(Dockerfile 中 USER 指令);
  • **启用 **--read-only 只读根文件系统,数据写入卷;
  • **显式 **--cap-drop ALL 后仅按需 --cap-add
  • 生产开启 ****rootless 模式或 USER 命名空间映射;
  • 使用可信基础镜像并****定期扫描漏洞

警告 **切勿以 **--privileged 运行容器——它相当于把容器几乎变成宿主机 root,会绕过上述大部分安全机制。仅在受控的调试场景临时使用。


9. 原理对架构设计的启示

理解原理之后,可将其转化为容器化应用的设计准则。

  1. 无状态优先:将状态(数据、会话)外置到卷或外部存储,容器本身保持无状态、可随时销毁重建。
  2. 单容器单职责:一个容器只运行一个主进程;多服务组合交给编排系统而非塞进一个容器。
  3. 显式声明资源:始终设置 --memory--cpus,让 cgroups 成为可预测的资源护栏。
  4. 镜像即契约:把依赖、配置固化进镜像,实现「构建一次,处处运行」;配置与密钥通过环境变量或 secret 注入,而非写死在镜像里。
  5. 分层与缓存意识:合理安排 Dockerfile 指令顺序与多阶段构建,兼顾镜像体积与构建效率。
  6. 网络与持久化分离设计:用自定义网络 + DNS 做服务发现,用卷做数据持久化,避免依赖容器可写层。
  7. 安全纵深:非 root、最小权限、只读根、漏洞扫描,作为镜像出厂标准。
  8. 面向编排设计:即使当前用单机 Docker,也应按「可被 Kubernetes/Swarm 编排」的方式设计容器(健康检查、优雅退出、可水平扩展)。

10. 常见误区与故障排除

现象或误区 根因与处理
误以为「容器是迷你虚拟机」 容器是共享内核的被隔离进程,无独立内核;不要在容器内跑 init 系统或多服务
docker stop长时间后才停止 入口进程不是 PID 1,或未处理SIGTERM。让应用直接作为入口或用tini
容器重启后数据丢失 数据写在了可写层。改用 volume 或绑定挂载持久化
容器间无法用名字互连 使用的是默认 bridge,它不提供 DNS。改用自定义 bridge 网络
磁盘空间莫名被占满 废弃镜像/层与停止的容器堆积。用docker system prune清理
构建很慢、缓存总失效 变动频繁的指令放在了 Dockerfile 前部。调整指令顺序,把易变内容后置
容器内 root 能影响宿主机 默认未启用 USER 命名空间。生产建议 rootless 或用户映射,并避免--privileged

实时排查常用命令:

docker ps -a                 # 查看所有容器及状态
docker logs -f <容器>         # 跟踪日志
docker inspect <容器>         # 查看挂载、网络、资源限制等详细配置
docker stats                 # 实时资源占用(CPU/内存/IO)
docker system df             # 磁盘占用分布

11. 后续步骤

  • BuildKit:学习新一代构建器,支持并行构建、更好的缓存与密钥挂载。
  • Compose:用 docker compose 描述并管理多容器应用。
  • 编排:进阶到 Docker Swarm 或 Kubernetes,实现服务的调度、扩缩容与自愈。
  • 镜像分发:搭建 Harbor 等私有镜像仓库,纳入漏洞扫描与签名。
  • 可观测性:将容器的 metrics、日志、追踪接入 Prometheus、Loki、Jaeger 等体系。

参考

0条评论
0 / 1000
Maxwell
11文章数
0粉丝数
Maxwell
11 文章 | 0 粉丝
原创

Docker 原理与架构设计

2026-07-13 17:03:56
8
0

阅读完本文后,您将能够:

  • 说清 Linux 容器赖以实现的三大内核机制——命名空间(namespaces)、控制组(cgroups)与联合文件系统(UnionFS);
  • 描述 Docker 的分层运行时架构(dockerd → containerd → runc)及各组件职责;
  • 解释镜像分层、写时复制与构建缓存的工作原理;
  • 识别 Docker 的网络与存储模型,以及其安全边界与加固手段;
  • 将上述原理转化为生产环境下的容器架构设计实践。

重要 本文聚焦 Docker 引擎(Docker Engine,社区版/企业版)的原理与架构。容器编排(Kubernetes、Docker Swarm)与镜像分发(Harbor 等)属于上层主题,仅在必要时提及。


目录

  1. 概述:为什么需要容器
  2. 容器化的底层原理
  3. Docker 的组件架构
  4. 镜像分层与构建原理
  5. 容器运行原理与生命周期
  6. 网络与存储模型
  7. 架构演进历史
  8. 安全模型与加固
  9. 原理对架构设计的启示
  10. 常见误区与故障排除
  11. 后续步骤

1. 概述:为什么需要容器

传统应用部署长期面临「在我机器上能跑」的环境一致性问题:依赖版本、系统库、配置文件、运行时环境的细微差异,都会导致应用在开发、测试、生产之间行为不一致。虚拟机虽能解决一致性问题,但每个虚拟机都要运行一整套客户操作系统,体积大、启动慢、资源开销高。

容器是一种轻量级的操作系统级虚拟化技术。它将应用及其全部依赖打包为一个标准化单元,共享宿主机内核,无需启动完整的客户操作系统,因此具备以下特点:

特性 容器 虚拟机
虚拟化层级 操作系统级(共享内核) 硬件级(每实例一整套 OS)
启动时间 秒级 分钟级
资源开销 低(MB 级) 高(GB 级)
隔离强度 较弱(共享内核) 强(硬件隔离)
单机密度 高(数百实例) 低(数十实例)

Docker 并不是容器的发明者——Linux 内核的容器原语(namespaces、cgroups)在 Docker 诞生(2013 年)前就已存在——但 Docker 首次把这些底层能力封装成「镜像 + 容器 + 一致的工作流」,让容器技术得以大规模普及,并推动形成了今天的 OCI(Open Container Initiative)标准。

提示 **一句话概括:**容器 = 受隔离与限流的进程。它本质上仍是宿主机上的普通进程,只是被内核「骗」成了以为自己独占整台机器。


2. 容器化的底层原理

容器的「隔离」「限流」「分层」三大能力,分别由 Linux 内核的三类机制实现。理解这三点,就理解了容器的本质。

2.1 命名空间(Namespaces):实现隔离

命名空间让一组进程看到一份****独立、被裁剪过的系统资源视图,从而彼此隔离。Docker 主要使用以下命名空间:

命名空间 隔离对象 效果
PID 进程号 容器内进程从 PID 1 重新编号,看不到宿主机其他进程
NET 网络栈 独立的网卡、路由表、iptables、端口空间
IPC 进程间通信 独立的 System V IPC、POSIX 消息队列
MNT 挂载点 独立的文件系统视图
UTS 主机名与域名 独立的 hostname
USER 用户与用户组 ID 容器内的 root 可映射为宿主机上的非特权用户
Cgroup cgroup 视图 独立的 cgroup 层次结构视图(Linux 4.6+)

可以亲手验证。在宿主机启动一个容器,然后观察其命名空间:

# 启动一个长期运行的容器
docker run -d --name demo alpine sleep 3600
​
# 找到容器内 sleep 进程在宿主机上的真实 PID
PID=$(docker inspect -f '{{.State.Pid}}' demo)
echo $PID
​
# 查看该进程所属的所有命名空间
ls -l /proc/$PID/ns

提示 **也可以用 **lsns 命令列出宿主机上当前所有的命名空间及其属主进程,直观看到 Docker 为每个容器创建的独立命名空间集合。

2.2 控制组(Cgroups):实现资源限制

命名空间负责「隔离」,但隔离后的进程仍可能耗尽宿主机的 CPU、内存、磁盘 I/O。**cgroups(Control Groups) 负责对一组进程进行资源计量与限制**,是防止「吵闹的邻居」的关键。

主要资源控制器包括:

控制器 限制对象 Docker 对应参数
cpu / cpuacct CPU 配额与使用统计 --cpus--cpu-shares
memory 内存与交换空间 --memory--memory-swap
pids 进程数(防 fork 炸弹) --pids-limit
blkio 块设备 I/O --device-read-bps
devices 设备访问白名单 --device

cgroups 有 v1(按控制器分层次)与 v2(统一层级)两代。现代发行版默认逐步转向 cgroup v2。同样可以实地观察:

# 查看上面 demo 容器进程的 cgroup 归属
cat /proc/$PID/cgroup

重要 生产环境务必为每个容器显式设置 CPU 与内存上限。否则单个容器的异常(如内存泄漏)可能拖垮整台宿主机。

2.3 联合文件系统(UnionFS):实现分层

联合文件系统将****多个目录「叠加」成一个逻辑文件系统,是 Docker 镜像分层与「写时复制」的基础。

  • 镜像(image) 由若干**只读层(read-only layer)**堆叠而成,每一层对应 Dockerfile 中的一条指令(如 RUNCOPY)。
  • 容器(container) 在镜像之上再加一层可写层(writable layer)。所有对容器的修改都写入这一层,底层镜像保持不变。
  • 写时复制(Copy-on-Write, CoW):当容器需要修改位于只读层中的某个文件时,系统并不直接改写原文件,而是先把它复制到可写层再修改,从而保证镜像层的不变性与可共享性。

Docker 支持多种存储驱动,overlay2 是当前默认且性能最佳的实现:

存储驱动 说明
overlay2 默认推荐,基于 OverlayFS,性能与稳定性最佳
fuse-overlayfs 用于 rootless 模式
btrfs/zfs 依赖对应文件系统的高级特性
devicemapper/aufs 早期方案,现已不推荐

查看当前使用的存储驱动:

docker info | grep -i "storage driver"

2.4 一条命令的全过程

**把三大机制串联起来,一次 **docker run 在底层大致发生:

  1. 命名空间为容器构建独立的进程/网络/文件系统视图;
  2. cgroups套上 CPU、内存等资源限制;
  3. 联合文件系统把镜像各层叠加,并附加可写层;
  4. 最终 exec 出容器入口进程——它是一个****被隔离、被限流、拥有叠加文件系统视图的普通宿主机进程

3. Docker 的组件架构

Docker 采用****客户端/服务端(C/S)架构,并自 1.11 版起拆分为「高层运行时 + 低层运行时」的分层设计。整体结构如下图所示。

3.1 架构总览

┌─────────────────────────────────────────────────────────────────┐
│                          宿主机 (Host)                           │
│                                                                 │
│   ┌──────────────┐   REST API    ┌──────────────────────────┐   │
│   │  Docker CLI  │ ─────────────▶│     dockerd (守护进程)    │   │
│   │   (client)   │  Unix socket  │  镜像/网络/卷/API 管理     │   │
│   └──────────────┘  /var/run/    └─────────────┬────────────┘   │
│                                     docker.sock │ gRPC          │
│                                                  ▼               │
│                                       ┌──────────────────────┐  │
│                                       │     containerd       │  │
│       ┌──────────────────┐            │ 容器生命周期/镜像分发 │  │
│       │     Registry     │◀──────────▶│   /快照管理           │  │
│       │  (Hub/Harbor)    │   拉取/推送 └──────────┬───────────┘  │
│       └──────────────────┘                       │ 每容器一个     │
│                                       ┌──────────┴───────────┐  │
│                                       ▼                      ▼  │
│                                ┌─────────────┐        ┌────────┐ │
│                                │containerd-  │        │ ...    │ │
│                                │  shim       │        │        │ │
│                                │   │ runc    │        │        │ │
│                                │   ▼         │        │        │ │
│                                │ 容器进程     │        │        │ │
│                                └─────────────┘        └────────┘ │
│                                                                 │
│   Linux 内核:  namespaces  |  cgroups  |  UnionFS  | netfilter   │
└─────────────────────────────────────────────────────────────────┘

3.2 各组件职责

组件 角色 职责
Docker ClientdockerCLI) 客户端 解析用户命令,通过 REST API 与守护进程通信
dockerd 守护进程/高层管理 监听 API、管理镜像/容器/网络/卷、构建镜像
containerd 高层容器运行时 管理容器完整生命周期、镜像拉取与分发、快照、supervision
containerd-shim 每容器一个的垫片 作为容器进程的父进程,接管 STDIO 与退出码,使守护进程重启不影响容器
runc 低层 OCI 运行时 依据 OCI 运行时规范,真正「组装」namespaces + cgroups 并启动容器进程
Registry 镜像仓库 存储与分发镜像(Docker Hub、私有仓库、Harbor 等)

3.3 调用链:从命令到进程

**用户执行 **docker run 后,请求沿分层架构逐级下发:

docker CLI  ──REST──▶  dockerd  ──gRPC──▶  containerd
   ──创建 shim──▶  containerd-shim  ──OCI runtime spec──▶  runc
   ──setns+cgroup──▶  容器入口进程

提示 **注意 **runc 在启动容器进程后会随即退出,由常驻的 containerd-shim 充当容器进程的父进程。正是这一设计,使得 dockerd 甚至 containerd 重启升级时,容器仍能继续运行——这是分层架构带来的关键收益。

3.4 OCI 标准

Docker 在 2015 年联合业界发起 ****OCI(Open Container Initiative),定义了两项事实标准:

  • 镜像规范(image-spec):镜像的格式、清单(manifest)、层与配置的标准结构;
  • 运行时规范(runtime-spec):描述「如何把一个容器跑起来」的环境与行为(即 runc 读取的那份配置)。

标准化使得 Docker 镜像可以在 containerd、CRI-O、Kubernetes 等不同运行时之间无缝流通,也使得低层运行时(runc、kata、gVisor)可以互换。


4. 镜像分层与构建原理

4.1 镜像、层与容器

  • 镜像(Image):只读的分层模板,包含应用代码、运行时、库与配置。镜像通过内容寻址——每层与镜像整体都以 SHA256 摘要标识。
  • 层(Layer):文件系统的增量变更集,可被多个镜像共享复用,节省存储与传输。
  • 容器(Container):镜像的运行实例,等于「全部只读镜像层 + 一个可写层 + 隔离环境」。
┌───────────────────────┐  ◀── 可写层(容器独有,随容器删除而消失)
            │   Writable layer       │
            ├───────────────────────┤
            │  Layer N: CMD/ENTRYPOINT│ ◀── 只读层(来自镜像,多容器共享)
            ├───────────────────────┤
            │  Layer 2: COPY 源码    │
            ├───────────────────────┤
            │  Layer 1: 安装依赖     │
            ├───────────────────────┤
            │  Layer 0: 基础镜像     │
            └───────────────────────┘

4.2 Dockerfile 与构建过程

Dockerfile 中****每条会修改文件系统的指令RUNCOPYADD)都会产生一个新层。构建时:

  1. 客户端将****构建上下文(build context)发送给 dockerd;
  2. dockerd 逐条执行指令,每条指令基于上一层产生新层;
  3. 构建缓存:若某条指令及其上层均未变化,则直接复用缓存层,跳过实际执行——这是加速重复构建的核心机制。

一个简化的示例:

# 基础镜像(Layer 0)
FROM alpine:3.20
​
# 安装依赖(Layer 1)——变动频率低,放前面以命中缓存
RUN apk add --no-cache curl
​
# 拷贝源码(Layer 2)——变动频率高,放后面
COPY app.sh /app/app.sh
​
# 入口(不产生文件层,仅修改镜像配置)
CMD ["/app/app.sh"]

重要 缓存是「指令 + 上文」共同决定的。把****变动频繁的内容(如源代码)放在 Dockerfile 末尾,把变动少的内容(如依赖安装)放在前面,可最大化缓存命中率,显著缩短构建时间。

4.3 多阶段构建与镜像瘦身

**最终镜像往往不需要编译工具链。**多阶段构建(multi-stage build) 允许在一个 Dockerfile 中用多个 FROM,只在最终阶段拷入必要产物:

# 阶段 1:构建(含编译器,体积大)
FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN go build -o app .
​
# 阶段 2:运行(仅含二进制,体积小)
FROM alpine:3.20
COPY --from=builder /src/app /usr/local/bin/app
CMD ["app"]

**最终镜像只包含第二阶段的内容,体积可从数百 MB 降至数 MB。常见瘦身手段还包括:合并 **RUN 指令、清理包缓存、选用 alpine/distroless 基础镜像。


5. 容器运行原理与生命周期

5.1 容器的状态

一个容器在其生命周期中处于以下状态之一:

created ──start──▶ running ──stop──▶ exited
      │                  │
      └───── pause ──────▶ paused ──unpause──▶ running
状态 含义
created 已创建(已分配命名空间与文件系统)但未启动入口进程
running 入口进程正在运行
paused 冻结容器内所有进程(cgroup freezer)
exited 进程已退出(容器与可写层仍保留,可重启)
removed 容器及其可写层被删除

5.2 PID 1 与信号处理

**容器入口进程是容器命名空间内的 **PID 1,需要承担两项传统上由 init 系统完成的职责:

  • 信号转发:接收 SIGTERM 等并优雅退出;
  • 回收僵尸进程(reaping)。

重要 **若直接以 shell 脚本或后台 fork 的进程作为入口,PID 1 常无法正确转发信号,导致 **docker stop 超时后被 SIGKILL 强杀,造成数据不一致。建议入口直接指向应用主进程,或使用 tinidumb-init 等轻量 init。


6. 网络与存储模型

6.1 存储模型

**容器文件系统位于可写层,**容器删除即丢失。持久化数据应使用以下机制:

类型 说明 适用场景
volume(卷) 由 Docker 管理,位于/var/lib/docker/volumes 生产首选,跨平台、易备份迁移
bind mount(绑定挂载) 直接挂载宿主机任意路径 开发期挂源码、配置文件
tmpfs(内存盘) 数据存于内存 临时、敏感数据,重启即失
# 使用命名卷持久化数据
docker run -d --name db -v pgdata:/var/lib/postgresql/data postgres:16

6.2 网络模型

Docker 提供多种网络驱动:

网络模式 说明 典型场景
bridge 默认。通过docker0网桥 + NAT(iptables/netfilter)访问外部 单机多容器互通
host 直接使用宿主机网络栈,无隔离 对网络性能要求极高、无需端口隔离
none 仅 loopback,无网络 离线计算、安全沙箱
overlay 跨主机 VXLAN 网络 Docker Swarm 多主机组网
macvlan 容器拥有独立 MAC/IP,直连物理网络 传统网络迁移、低延迟

提示 自定义 bridge 网络会自动为其中的容器提供基于容器名的 DNS 解析,因此多容器协作时应使用自定义网络而非默认 bridge

docker network create appnet
docker run -d --name app --network appnet myapp
docker run -d --name db  --network appnet postgres:16
# 在 app 容器内可直接用主机名 db 连接数据库

7. 架构演进历史

Docker 的架构并非一蹴而就,理解其演进有助于看清设计取舍。

阶段 时间 架构要点
LXC 时代 2013 初版 直接调用 LXC 工具创建容器,强依赖宿主机 LXC
libcontainer 2014 自研 Go 实现的运行时库,摆脱对 LXC 的依赖
运行时分层 2016(v1.11) 拆分为 dockerd → containerd → runc 三层,引入 OCI 标准
containerd 独立 2017 containerd 作为 CNCF 项目独立演进,并被 Kubernetes 用作 CRI 运行时
Rootless / v2 近年 支持 rootless 模式、cgroup v2、BuildKit 默认构建器

注意 演进的核心趋势是****关注点分离:把「镜像/网络/卷管理」「容器生命周期」「低层系统调用」分别交给 dockerd、containerd、runc,并统一于 OCI 标准。这使得各组件可独立升级、可被其他平台(如 Kubernetes)复用。


8. 安全模型与加固

容器的隔离****弱于虚拟机——所有容器共享宿主机内核。因此容器安全是一套纵深防御组合拳,而非单一机制。

机制 作用 默认是否启用
Namespaces 资源视图隔离
Cgroups 资源用量限制 是(需显式配置上限)
Linux Capabilities 细粒度权限裁剪,丢弃大部分特权能力 是(默认丢弃多数)
seccomp 过滤危险系统调用(默认 profile 屏蔽数十个)
AppArmor / SELinux 强制访问控制(MAC) 视发行版而定
NoNewPrivs 禁止通过 setuid 等提权
Rootless / USER namespace 容器内 root 映射为宿主机非特权用户 需显式开启

常见加固实践:

  • 以****非 root 用户运行容器进程(Dockerfile 中 USER 指令);
  • **启用 **--read-only 只读根文件系统,数据写入卷;
  • **显式 **--cap-drop ALL 后仅按需 --cap-add
  • 生产开启 ****rootless 模式或 USER 命名空间映射;
  • 使用可信基础镜像并****定期扫描漏洞

警告 **切勿以 **--privileged 运行容器——它相当于把容器几乎变成宿主机 root,会绕过上述大部分安全机制。仅在受控的调试场景临时使用。


9. 原理对架构设计的启示

理解原理之后,可将其转化为容器化应用的设计准则。

  1. 无状态优先:将状态(数据、会话)外置到卷或外部存储,容器本身保持无状态、可随时销毁重建。
  2. 单容器单职责:一个容器只运行一个主进程;多服务组合交给编排系统而非塞进一个容器。
  3. 显式声明资源:始终设置 --memory--cpus,让 cgroups 成为可预测的资源护栏。
  4. 镜像即契约:把依赖、配置固化进镜像,实现「构建一次,处处运行」;配置与密钥通过环境变量或 secret 注入,而非写死在镜像里。
  5. 分层与缓存意识:合理安排 Dockerfile 指令顺序与多阶段构建,兼顾镜像体积与构建效率。
  6. 网络与持久化分离设计:用自定义网络 + DNS 做服务发现,用卷做数据持久化,避免依赖容器可写层。
  7. 安全纵深:非 root、最小权限、只读根、漏洞扫描,作为镜像出厂标准。
  8. 面向编排设计:即使当前用单机 Docker,也应按「可被 Kubernetes/Swarm 编排」的方式设计容器(健康检查、优雅退出、可水平扩展)。

10. 常见误区与故障排除

现象或误区 根因与处理
误以为「容器是迷你虚拟机」 容器是共享内核的被隔离进程,无独立内核;不要在容器内跑 init 系统或多服务
docker stop长时间后才停止 入口进程不是 PID 1,或未处理SIGTERM。让应用直接作为入口或用tini
容器重启后数据丢失 数据写在了可写层。改用 volume 或绑定挂载持久化
容器间无法用名字互连 使用的是默认 bridge,它不提供 DNS。改用自定义 bridge 网络
磁盘空间莫名被占满 废弃镜像/层与停止的容器堆积。用docker system prune清理
构建很慢、缓存总失效 变动频繁的指令放在了 Dockerfile 前部。调整指令顺序,把易变内容后置
容器内 root 能影响宿主机 默认未启用 USER 命名空间。生产建议 rootless 或用户映射,并避免--privileged

实时排查常用命令:

docker ps -a                 # 查看所有容器及状态
docker logs -f <容器>         # 跟踪日志
docker inspect <容器>         # 查看挂载、网络、资源限制等详细配置
docker stats                 # 实时资源占用(CPU/内存/IO)
docker system df             # 磁盘占用分布

11. 后续步骤

  • BuildKit:学习新一代构建器,支持并行构建、更好的缓存与密钥挂载。
  • Compose:用 docker compose 描述并管理多容器应用。
  • 编排:进阶到 Docker Swarm 或 Kubernetes,实现服务的调度、扩缩容与自愈。
  • 镜像分发:搭建 Harbor 等私有镜像仓库,纳入漏洞扫描与签名。
  • 可观测性:将容器的 metrics、日志、追踪接入 Prometheus、Loki、Jaeger 等体系。

参考

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0