Docker 内容寻址存储(Content-Addressable Storage)
核心概念
Docker 使用内容寻址存储来管理镜像层。简单说:每一层的标识符(digest/hash)是该层内容的哈希值,而不是根据文件名、时间戳或序列号来标识。
# 内容寻址的本质:内容是身份的来源
sha256:abc123def456... = 这串特定的二进制内容
# 内容变了 → 哈希变了 → 身份变了 → 新的层
# 内容不变 → 哈希不变 → 身份不变 → 复用已有层
对比:位置寻址 vs 内容寻址
graph TB
subgraph 位置寻址(传统方式)
P1[/var/data/layer5.tgz] --> P2[文件名: layer5]
P3[改名 → 找到不同的文件]
P4[同文件复制到不同目录 → 重复存储]
end
subgraph 内容寻址(Docker 方式)
C1[sha256:abc...] --> C2[通过哈希定位内容]
C3[哈希不变 → 内容不变 → 定位不变]
C4[相同哈希 → 只存一份]
end
Docker 中内容寻址的具体实现
1. 层(Layer)的寻址
# 查看镜像层的 digest
docker inspect nginx:latest --format '{{.RootFS.Layers}}'
# [sha256:7e718b9f9f6f8b1f924898f2e6470e7a4bf8a9c42f0f3650a8a0b7e1c3a5d1e0
# sha256:9f3b8c1d2e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9]
# 查看这个 layer 在磁盘上的位置
# 不是通过文件名,而是通过哈希目录定位
ls /var/lib/docker/overlay2/
# 7e718b9f9f6f8b1f924898f2e6470e7a4bf8a9c42f0f3650a8a0b7e1c3a5d1e0
# 9f3b8c1d2e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9
# (注意:实际目录名是截断后的哈希值)
2. Registry 中的寻址
# 在 Registry 中,层通过 digest 标识
# Push 层时
PUT /v2//blobs/uploads/
Content: 层的 tar.gz 数据
# Registry 计算 digest,如果已存在则直接引用
# 这就是为什么相同基础镜像不需要重复 Push
内容寻址的三大好处
好处一:天然去重
graph LR
subgraph 10个Python应用
A[应用1: FROM python:3.11]
B[应用2: FROM python:3.11]
C[应用3: FROM python:3.11]
end
subgraph 物理存储
D[python:3.11 基础层<br/>sha256: xxx<br/>只存1份]
E[应用层各存各的]
end
A & B & C --> D
A --> A1[应用1层]
B --> B1[应用2层]
C --> C1[应用3层]
如果不使用内容寻址:
# 每个镜像单独存一个完整副本
10 个 Python 应用 × 120MB = 1.2GB
使用内容寻址:
# 基础层去重
120MB(共享基础层)+ 1KB × 10(应用层)≈ 120MB
# 节省 90% 以上空间!
好处二:完整性校验
每个层的 digest 是内容的安全哈希(SHA256),天然具备完整性验证能力:
# Pull 镜像时,Docker 会自动校验
docker pull nginx:latest
# 底层发生了什么:
# 1. 下载 Manifest → 得到各层 digest
# 2. 下载层数据
# 3. 计算下载内容的 SHA256
# 4. 对比 Manifest 中的 digest
# 5. 不一致则报错
# 手动校验
docker image inspect nginx:latest --format '{{.Id}}'
# sha256:xxxx...
好处三:不可变性
一旦层的内容被创建,它的 digest 就永远固定了。这带来了可重现构建(reproducible builds)的基础:
# 基于确切 digest 构建,保证可重现
FROM ubuntu@sha256:abc123def456...
# 而不是 FROM ubuntu:latest (latest 会变)
Docker 中内容寻址的完整链路
graph TB
subgraph 构建阶段
DF[Dockerfile 指令] --> EXEC[执行指令]
EXEC --> CONTENT[产生文件变更]
CONTENT --> HASH[计算 SHA256]
HASH --> LAYER[存储新层<br/>目录名 = 哈希]
end
subgraph 推拉阶段
PUSH[Push 镜像] --> MANIFEST[生成 Manifest<br/>包含层 digest]
PUSH --> UPLOAD[上传层数据<br/>通过 digest 标识]
PULL[Pull 镜像] --> GET_MANIFEST[获取 Manifest]
GET_MANIFEST --> GET_LAYERS[按 digest 下载层]
GET_LAYERS --> VERIFY[校验 digest]
end
subgraph 运行阶段
START[启动容器] --> LOCATE[按 digest 找到层]
LOCATE --> MOUNT[OverlayFS 挂载]
MOUNT --> RUN[容器运行]
end
内容寻址在 Docker 生态中的具体应用
镜像层存储
# 本地存储层的目录结构
/var/lib/docker/overlay2/
├── l/ # 符号链接层
│ ├── ABCXYZ -> ../7e718b9f.../diff
│ └── DEFUVW -> ../9f3b8c1d.../diff
├── 7e718b9f9f6f8b1f9248.../ # 以哈希截断命名的目录
│ ├── diff/ # 层的实际内容
│ ├── link # 链接到 l/ 中的短名
│ ├── lower # 下层信息
│ └── merged # 合并视图
└── 9f3b8c1d2e4f5a6b7c8d.../
Registry 存储
# Registry 中的 blob 存储也是内容寻址
/var/lib/registry/docker/registry/v2/
└── blobs/
└── sha256/
├── ab/
│ └── abc123... # 以 digest 为路径
└── 7e/
└── 7e718b9f... # 另一层的 digest
镜像引用
# 通过 digest 精确引用镜像
docker pull myapp@sha256:abc123def456...
# digest 可以像 tag 一样使用
docker run myapp@sha256:abc123def456...
# 构建时引用基础镜像
FROM node:18@sha256:xyz789...
面试追问
问:内容寻址会不会有哈希碰撞?
答:理论上 SHA256 有碰撞可能,但概率极低(约 1/2^256)。在实际工程中可认为不会发生。Docker 和整个容器生态都依赖这个假设。
问:内容寻址与 Git 的寻址方式类似吗?
答:非常类似。Git 也是内容寻址——每次提交的 SHA1 哈希就是内容的标识。Docker 层的 digest 就像是 Git 的 commit hash。
总结
| 特性 | 位置寻址 | 内容寻址(Docker) |
|---|---|---|
| 标识方式 | 文件名 + 路径 | 内容哈希 |
| 去重能力 | 需要手动管理 | 天然去重 |
| 完整性 | 无自动校验 | SHA256 自动校验 |
| 不可变性 | 可随意修改 | 内容不可变(Digest 永久不变) |
| 分布式友好 | 需额外同步机制 | 哈希天然可分布式 |
一句话总结:内容寻址让 Docker 做到了「同名文件可能不同、同内容注定相同」——用内容的哈希值作为唯一标识,实现了自动去重、完整性校验和不可变性三大特性。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END


暂无评论内容