mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1629 字
5 分钟
容器存储
2026-05-31

容器的文件系统是临时的——容器删除后,可写层中的所有数据都会丢失。如果你在容器中写入数据库文件、日志、用户上传内容,容器重启后这些数据就消失了。

Volume 是容器数据持久化的标准方案。它将宿主机的目录或文件挂载到容器的指定路径,绕过 OverlayFS 的可写层,直接读写宿主文件系统。Kubernetes 进一步通过 PV/PVC/StorageClass 体系实现了存储的动态供给和管理。

本章将深入容器存储的完整方案,从 Docker Volume 到 Kubernetes CSI。

容器存储的演进是一条从”本地挂载”到”动态供给”的路径。Docker 早期使用 device mapper 作为存储驱动,性能差且不稳定;后来转向 AUFS,但 AUFS 不在内核主线中;最终 overlay2 成为默认选择。Volume 最初只是简单的”绑定挂载”(-v /host/path:/container/path),Kubernetes 引入 PV/PVC/StorageClass 体系后,存储管理实现了质的飞跃——用户声明”我需要 10GB 存储”,StorageClass 自动从云厂商供给。2018 年,CSI(Container Storage Interface)标准化了存储插件接口,AWS EBS、Ceph、NFS 等存储后端可以通过统一的 CSI 插件接入 Kubernetes。容器存储从”手动挂载”到”声明式供给”,每一步演进都让运维更简单、应用更可移植。

前置知识#

Note

本章的 Kubernetes 存储部分(PV/PVC/CSI)与 Ch16 容器在 Kubernetes 中 有交叉,建议结合阅读。

一、容器存储的问题#

1.1 可写层的局限#

# 容器可写层的问题
docker run -d --name db mysql
# 在容器中写入数据...
docker exec db mysql -e "CREATE DATABASE myapp;"
# 删除容器
docker rm -f db
# 重新创建容器
docker run -d --name db mysql
docker exec db mysql -e "SHOW DATABASES;"
# myapp 数据库不见了!因为可写层随容器删除

1.2 容器存储的层次#

graph TB subgraph 应用层["应用层"] APP["容器进程"] end subgraph 挂载层["挂载层"] VOLUME["Volume<br/>绑定挂载/命名卷"] OVERLAY["OverlayFS<br/>可写层"] end subgraph 存储后端["存储后端"] LOCAL["本地磁盘<br/>/var/lib/docker/volumes/"] NFS["NFS<br/>网络文件系统"] CLOUD["云存储<br/>EBS/Azure Disk/GCE PD"] CEPH["Ceph<br/>分布式存储"] end APP --> VOLUME APP --> OVERLAY VOLUME --> LOCAL VOLUME --> NFS VOLUME --> CLOUD VOLUME --> CEPH style 应用层 fill:#e8eaf6,stroke:#283593 style 挂载层 fill:#c8e6c9,stroke:#2e7d32 style 存储后端 fill:#fff3e0,stroke:#e65100

二、Docker Volume#

2.1 三种挂载类型#

挂载类型数据持久性写入性能典型用途
Volume (bind mount)宿主机目录原生开发环境、配置文件
Volume (docker volume)Docker 管理原生数据库持久化、共享存储
tmpfs内存(不持久)最快敏感数据、临时文件
类型语法说明
绑定挂载-v /host/path:/container/path挂载宿主机指定目录
命名卷-v myvol:/container/pathDocker 管理的持久卷
匿名卷-v /container/pathDocker 自动创建的临时卷
tmpfs--tmpfs /container/path内存文件系统
# 绑定挂载
docker run -v /home/user/data:/app/data nginx
# 命名卷
docker volume create mydata
docker run -v mydata:/app/data nginx
# 匿名卷
docker run -v /app/data nginx
# tmpfs 挂载
docker run --tmpfs /app/cache nginx
# 只读挂载
docker run -v /home/user/config:/app/config:ro nginx

2.2 Volume 的底层实现#

Volume 的底层是 Linux 的 bind mount——将一个目录挂载到另一个路径:

# Docker Volume 的底层操作
# 1. 创建卷目录
mkdir -p /var/lib/docker/volumes/mydata/_data
# 2. 绑定挂载到容器的 Mount Namespace
mount --bind /var/lib/docker/volumes/mydata/_data /var/lib/docker/overlay2/.../merged/app/data
# 3. 容器内看到的就是宿主机的目录
docker exec mycontainer ls /app/data
# 等同于 ls /var/lib/docker/volumes/mydata/_data

2.3 Volume 的生命周期#

flowchart TB CREATE["docker volume create mydata"] --> RUN["docker run -v mydata:/app/data"] RUN --> WRITE["容器写入 /app/data"] WRITE --> STOP["docker stop/rm"] STOP --> DATA_SAFE["数据保留在 /var/lib/docker/volumes/mydata/_data"] DATA_SAFE --> REUSE["docker run -v mydata:/app/data<br/>(数据仍在)"] REUSE --> DELETE["docker volume rm mydata<br/>(数据删除)"] style DATA_SAFE fill:#c8e6c9,stroke:#2e7d32 style DELETE fill:#ffcdd2,stroke:#c62828

2.4 Volume 驱动#

Docker 支持 Volume 驱动插件,将数据存储到不同的后端:

# 查看已安装的 Volume 驱动
docker volume ls
# 使用特定驱动创建卷
docker volume create --driver local mydata
docker volume create --driver local --opt type=nfs --opt o=addr=192.168.1.1,rw --opt device=:/export/data nfs-vol
# 常见 Volume 驱动
# local: 本地文件系统(默认)
# nfs: NFS 网络存储
# rexray: 多云存储
# convoy: 分布式存储

三、存储驱动#

3.1 存储驱动的职责#

存储驱动管理容器的文件系统层——如何叠加、如何写时复制、如何管理 whiteout:

存储驱动技术适用场景
overlay2OverlayFS默认推荐
fuse-overlayfsFUSE OverlayFSRootless 容器
devicemapperDevice Mapper旧系统
btrfsBtrfs 子卷Btrfs 文件系统
zfsZFS 数据集ZFS 文件系统
vfs目录复制调试

3.2 存储驱动与 Volume 的关系#

# 存储驱动管理的是容器的"层叠文件系统"
# Volume 管理的是"持久化数据"
# 两者互不干扰:
# - 存储驱动负责镜像层 + 可写层
# - Volume 负责绕过可写层直接读写宿主文件系统
# 查看存储驱动
docker info | grep "Storage Driver"
# Storage Driver: overlay2

3.3 存储驱动性能对比#

操作overlay2devicemapperbtrfszfs
创建容器
删除容器
读取文件
写入新文件
修改已有文件慢(copy-up)
磁盘空间效率
Warning

修改大文件时,overlay2 的 copy-up 机制会复制整个文件。如果你的应用频繁修改大文件,应该使用 Volume 而非容器的可写层。

四、Kubernetes 存储#

4.1 PV/PVC/StorageClass#

Kubernetes 的存储体系由三个核心概念组成:

概念角色生命周期
PV (PersistentVolume)存储资源集群级别
PVC (PersistentVolumeClaim)存储请求命名空间级别
StorageClass存储类型集群级别
graph LR subgraph 用户侧["用户侧"] POD["Pod"] PVC["PVC<br/>storage: 10Gi<br/>accessMode: ReadWriteOnce"] end subgraph 集群侧["集群侧"] SC["StorageClass<br/>provisioner: kubernetes.io/aws-ebs"] PV["PV<br/>capacity: 10Gi<br/>accessMode: ReadWriteOnce"] end subgraph 基础设施["基础设施"] EBS["AWS EBS<br/>10GB Volume"] end POD --> PVC PVC -->|"绑定"| PV SC -->|"动态供给"| PV PV --> EBS style 用户侧 fill:#e8eaf6,stroke:#283593 style 集群侧 fill:#c8e6c9,stroke:#2e7d32 style 基础设施 fill:#fff3e0,stroke:#e65100

4.2 PVC 示例#

# StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
fsType: ext4
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myapp-data
spec:
accessModes: [ReadWriteOnce]
storageClassName: fast-ssd
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Pod
spec:
containers:
- name: myapp
volumeMounts:
- name: data
mountPath: /app/data
volumes:
- name: data
persistentVolumeClaim:
claimName: myapp-data

4.3 访问模式#

访问模式缩写说明
ReadWriteOnceRWO单节点读写
ReadOnlyManyROX多节点只读
ReadWriteManyRWX多节点读写
ReadWriteOncePodRWOP单 Pod 读写(Kubernetes 1.27+)

五、CSI:容器存储接口#

5.1 CSI 原理#

CSI(Container Storage Interface)是 Kubernetes 的存储插件标准,类似于 CNI 之于网络:

sequenceDiagram participant K8S as Kubernetes participant CSI as CSI Driver participant STORAGE as 存储后端 Note over K8S,STORAGE: 动态供给 K8S->>CSI: CreateVolume (capacity=10Gi) CSI->>STORAGE: 创建 10GB 卷 STORAGE-->>CSI: 卷 ID: vol-abc123 CSI-->>K8S: VolumeContext{id: "vol-abc123"} Note over K8S,STORAGE: 挂载卷 K8S->>CSI: NodeStageVolume (volume_id, target_path) CSI->>STORAGE: 挂载卷到节点 CSI-->>K8S: 挂载成功 K8S->>CSI: NodePublishVolume (target_path, staging_path) CSI->>CSI: bind mount 到 Pod 路径 CSI-->>K8S: 发布成功 Note over K8S,STORAGE: 卸载卷 K8S->>CSI: NodeUnpublishVolume K8S->>CSI: NodeUnstageVolume K8S->>CSI: DeleteVolume CSI->>STORAGE: 删除卷

5.2 CSI Driver 接口#

接口功能调用者
CreateVolume创建存储卷Controller
DeleteVolume删除存储卷Controller
ControllerPublishVolume将卷附加到节点Controller
ControllerUnpublishVolume将卷从节点分离Controller
NodeStageVolume将卷挂载到暂存路径Node
NodeUnstageVolume卸载暂存路径Node
NodePublishVolume将卷绑定到 Pod 路径Node
NodeUnpublishVolume解绑 Pod 路径Node

5.3 CSI Driver 生命周期#

CSI Driver 的每个接口在卷的完整生命周期中有明确的调用顺序:

stateDiagram-v2 [*] --> Created: CreateVolume Created --> Published: ControllerPublishVolume<br/>+ NodeStageVolume<br/>+ NodePublishVolume Published --> Unpublished: NodeUnpublishVolume<br/>+ NodeUnstageVolume<br/>+ ControllerUnpublishVolume Unpublished --> Deleted: DeleteVolume Deleted --> [*] note right of Published: Pod 运行中<br/>卷已挂载到容器 note right of Unpublished: Pod 已删除<br/>卷已从节点分离

5.4 常见 CSI Driver#

CSI Driver存储后端特点
aws-ebs-csi-driverAWS EBSAWS 块存储
azure-disk-csi-driverAzure DiskAzure 块存储
gce-pd-csi-driverGCE PDGCP 块存储
ceph-csiCeph RBD/CephFS分布式存储
local-path-provisioner本地磁盘简单本地存储
nfs-csiNFS网络文件系统
hostpath-csi宿主路径开发测试

六、动手实践#

6.1 Docker Volume 实验#

#!/bin/bash
# Docker Volume 实验
# 1. 创建命名卷
docker volume create mydata
# 2. 查看卷详情
docker volume inspect mydata
# Mountpoint: /var/lib/docker/volumes/mydata/_data
# 3. 使用卷运行容器
docker run -d --name writer -v mydata:/app/data alpine \
sh -c "echo 'Hello from writer' > /app/data/test.txt && sleep 3600"
# 4. 另一个容器读取同一卷
docker run --rm -v mydata:/app/data alpine cat /app/data/test.txt
# Hello from writer
# 5. 绑定挂载实验
mkdir -p /tmp/host-data
echo "from host" > /tmp/host-data/file.txt
docker run --rm -v /tmp/host-data:/app/data alpine cat /app/data/file.txt
# from host
# 6. 清理
docker rm -f writer
docker volume rm mydata

6.2 存储性能测试#

#!/bin/bash
# 对比 Volume vs 可写层的 IO 性能
echo "=== 可写层 IO 性能 ==="
docker run --rm alpine sh -c "
dd if=/dev/zero of=/tmp/test bs=1M count=100 2>&1 | tail -1
sync
dd if=/tmp/test of=/dev/null bs=1M 2>&1 | tail -1
"
echo ""
echo "=== Volume IO 性能 ==="
docker run --rm -v /tmp/vol-test:/data alpine sh -c "
dd if=/dev/zero of=/data/test bs=1M count=100 2>&1 | tail -1
sync
dd if=/data/test of=/dev/null bs=1M 2>&1 | tail -1
"
# Volume 通常比可写层快 2-5x,因为绕过了 OverlayFS 的 copy-up

七、容器存储性能优化#

7.1 存储性能瓶颈定位#

# 1. 测试容器可写层的 IO 性能
docker run --rm alpine sh -c "
dd if=/dev/zero of=/tmp/test bs=4k count=10000 oflag=dsync 2>&1 | tail -1
"
# 典型结果: ~50 MB/s (OverlayFS copy-up 开销)
# 2. 测试 Volume 的 IO 性能
docker run --rm -v /tmp/vol-test:/data alpine sh -c "
dd if=/dev/zero of=/data/test bs=4k count=10000 oflag=dsync 2>&1 | tail -1
"
# 典型结果: ~200 MB/s (直接写宿主文件系统)
# 3. 测试宿主机原生 IO 性能
dd if=/dev/zero of=/tmp/host-test bs=4k count=10000 oflag=dsync 2>&1 | tail -1
# 典型结果: ~250 MB/s

7.2 存储性能优化策略#

策略说明性能提升
使用 Volume绕过 OverlayFS copy-up2-5x
使用 tmpfs内存文件系统10-50x
减少层数合并 RUN 指令减少查找开销
使用大页hugetlbfs减少页表开销
使用 IO 调度器none/mq-deadline减少延迟

7.3 Kubernetes 存储性能调优#

# 使用本地存储获得最佳性能
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-fast-storage
spec:
capacity:
storage: 100Gi
accessModes: [ReadWriteOnce]
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/fast-ssd
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: [node-1]

7.4 CSI Driver 性能对比#

CSI Driver类型IOPS延迟适用场景
aws-ebs gp3块存储16,000<1ms通用
aws-ebs io2块存储64,000<0.5ms数据库
local-path本地磁盘100,000+<0.1ms高性能
ceph-rbd分布式块10,0001-5ms共享存储
nfs网络文件5,0005-20ms文件共享

八、本章小结#

上一章剖析了容器网络原理。

概念职责适用场景
Docker Volume容器数据持久化单机容器
存储驱动文件系统层叠管理容器运行时
PV/PVC存储资源管理Kubernetes
StorageClass存储动态供给Kubernetes
CSI存储插件接口Kubernetes
Note

容器存储的核心原则是:数据不应存储在容器的可写层。无论是 Docker Volume 还是 Kubernetes PVC,都是将数据存储在容器生命周期之外。理解 Volume 的 bind mount 原理和 CSI 的插件机制,是构建可靠容器存储方案的基础。

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

容器存储
https://blog.souloss.com/posts/container-runtime/container-storage/
作者
Souloss
发布于
2026-05-31
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

相关文章 智能推荐
1
Cgroup v2 深入
容器运行时 Cgroup 是容器资源限制的核心机制。逐层拆解 Cgroup v2 的统一层级设计、CPU/内存/IO 三大控制器的实现原理、eBPF 扩展机制,以及容器运行时如何通过 Cgroup 实现资源隔离——从「docker run --memory=512m」到内核的 cgroup 文件系统,理解每一步的资源控制逻辑。
2
容器网络
容器运行时 容器网络的核心问题是——隔离的 Network Namespace 如何与外部通信?详细解读 veth pair(虚拟网卡对)、bridge(虚拟网桥)、iptables/NAT(地址转换)、CNI(容器网络接口)的完整链路,以及 Docker 的四种网络模式和 Kubernetes 的 Pod 网络模型——从「容器能 ping 通外网」到「理解每一条网络规则」。
3
容器在 Kubernetes 中
容器运行时 深入容器在 Kubernetes 中的运行机制——CRI 接口、Pod Sandbox 创建、多容器模式、Init Container、Sidecar 注入,以及 kubelet 如何通过 CRI 与容器运行时交互。
4
Wasm 容器:WasmEdge/WASI
容器运行时 WebAssembly(Wasm)正在从浏览器走向服务器——WASI(WebAssembly System Interface)定义了 Wasm 访问操作系统的标准接口,WasmEdge/runwasi 等运行时让 Wasm 模块可以作为容器运行。详细解读 Wasm 容器的架构、WASI 接口、与 OCI 的集成、与 Linux 容器的对比——从「Wasm 只能在浏览器运行」到「Wasm 是下一代容器运行时」。
5
OverlayFS:容器文件系统
容器运行时 OverlayFS 是容器分层文件系统的核心。全面剖析 OverlayFS 的 lowerdir/upperdir/workdir 三层结构、whiteout 标记文件机制、copy-up 写时复制语义、多层叠加规则,以及容器运行时如何用 OverlayFS 实现镜像层复用和容器可写层——理解「为什么容器启动这么快」和「镜像层是怎么共享的」。