容器基础(02):Docker 镜像存储——分层、overlayfs、镜像优化、存储驱动
更新时间:2026-09-01。本文是
cloud/docker/Docker 基础第 02 篇,接 Docker 基础。Docker 镜像由只读层叠加而成,每层对应一个指令。overlayfs 把多个层合并成一个文件系统视图,容器写时复制。
本文要回答的问题
- 镜像分层是什么?每层对应什么?为什么分层节省空间?
- overlayfs 怎么把多个层合并成一个文件系统?
- 写时复制(Copy-on-Write)是什么?修改文件时发生了什么?
- 怎么优化镜像大小?多阶段构建怎么做?
一、镜像分层
text
Docker 镜像是一个分层文件系统
每层对应 Dockerfile 的一条指令
FROM alpine:3.19 → 层 1:基础层
RUN apk add --no-cache python3 → 层 2:安装 python3
COPY . /app → 层 3:复制代码
CMD ["python3", "app.py"] → 元数据层(不占用空间)
Docker 镜像分层:
┌─────────────────────────────┐
│ 层 3:COPY . /app │ ← 可写层(容器层)
├─────────────────────────────┤
│ 层 2:RUN apk add python3 │ ← 只读层
├─────────────────────────────┤
│ 层 1:FROM alpine:3.19 │ ← 只读层
└─────────────────────────────┘分层的好处:
- 共享基础层:多个镜像共用同一个基础层,节省空间
- 缓存:构建时已缓存的层不需要重新构建
- 增量传输:拉取时只拉取本地没有的层
二、overlayfs 原理
text
overlayfs 把多个目录合并成一个目录
lowerdir:底层只读层(基础镜像层)
upperdir:上层可写层(容器层)
merged:合并后的视图(容器的文件系统)
┌─────────────────────────────────────┐
│ merged(容器看到的是合并后的视图) │
├─────────────────────────────────────┤
│ upperdir(容器层:修改的文件) │ ← 可写层
├─────────────────────────────────────┤
│ lowerdir:1(COPY . /app) │ ← 只读层
├─────────────────────────────────────┤
│ lowerdir:2(RUN apk add python3) │ ← 只读层
├─────────────────────────────────────┤
│ lowerdir:3(FROM alpine:3.19) │ ← 只读层
└─────────────────────────────────────┘文件查找:
- 先从 upperdir 找
- 找不到从 lowerdir 从高到低找
- 找到哪个就用哪个
三、写时复制(Copy-on-Write)
text
修改文件时发生了什么?
1. 读取文件:从 lowerdir 找到文件,读取
2. 修改文件:把文件复制到 upperdir,修改
3. 删除文件:在 upperdir 创建一个 whiteout 文件标记删除
4. 新建文件:直接在 upperdir 创建
这就是"写时复制":修改时才复制,不修改不复制好处: 多个容器共享同一个镜像,每个容器只修改自己的 upperdir,镜像层不被修改,节省空间。
四、镜像优化
多阶段构建
dockerfile
# ❌ 单阶段构建:go 镜像 1.2GB
FROM golang:1.22
WORKDIR /app
COPY . .
RUN go build -o app .
CMD ["./app"]
# ✅ 多阶段构建:最终镜像 20MB
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN go build -o app .
FROM alpine:3.19
COPY --from=builder /app/app /usr/local/bin/app
CMD ["app"]
# 最终镜像只有 alpine + 二进制其他优化技巧
dockerfile
# 1. 用 alpine 基础镜像(小)
FROM alpine:3.19
# 替代 FROM ubuntu:22.04(ubuntu 200MB,alpine 5MB)
# 2. 合并 RUN 指令,减少层数
RUN apt-get update && \
apt-get install -y --no-install-recommends python3 && \
rm -rf /var/lib/apt/lists/*
# 3. 清理缓存
RUN pip install --no-cache-dir -r requirements.txt
# 4. 不要安装不必要的包
# 用 --no-install-recommends 只装最小依赖
# 5. 多阶段构建只复制需要的文件
COPY --from=builder /app/output /app/五、存储驱动对比
| 驱动 | 适用场景 | 特点 |
|---|---|---|
| overlay2 | 推荐(默认) | 性能好,稳定,支持写时复制 |
| fuse-overlayfs | rootless 模式 | 无 root 用户也支持 |
| devicemapper | 旧版 RHEL/CentOS | 已被 overlay2 替代 |
| aufs | 旧版 Docker | 已被 overlay2 替代 |
推荐: 只要系统支持,永远用 overlay2。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 镜像体积太大 | 推送拉取慢,磁盘占用大 | 多阶段构建,用 alpine,清理缓存 |
| 层缓存不命中 | 每次构建重新安装依赖 | 把不变的层放前面(package.json 先 COPY) |
| overlay2 空间不足 | 容器写数据时写满 upperdir | 监测 overlay 空间,挂载 volume 到外部存储 |
| 存储驱动配置错误 | 容器启动失败 | 用 docker info 检查存储驱动,换成 overlay2 |
相关与延伸
下一篇:Docker 容器网络——bridge/host/none、端口映射、网络模型;Docker 基础,见 Docker 基础。
一句话总结
Docker 镜像存储:镜像是分层文件系统,每层对应 Dockerfile 一条指令,overlayfs 把多层合并成视图;写时复制:修改文件时复制到 upperdir,不修改共享镜像层,节省空间;多阶段构建把编译结果从大镜像复制到小镜像,最终镜像只有几十 MB;优化镜像:用 alpine 基础镜像,合并 RUN 指令,清理缓存,多阶段构建;存储驱动推荐 overlay2。