Docker-存储
一、Docker存储概述
1.数据卷特性
- Docker镜像由多个只读层构成,启动容器时,Docker会加载只读镜像层并在镜像栈顶部添加一个读写层
- 如果运行中的容器修改了现有的一个只读的文件,该文件会从只读层复制到读写层,只读版本依旧存在,但已经被读写层的副本所覆盖了此即写时复制(copy-on-write)机制

- 这个设计使得Docker可以提高镜像构建、存储和分发的效率,节省了时间和存储空间,然而也存在如下问题:
存在于联合文件系统中,不能在宿主机上很方便地对容器中的文件进行访问。
多个容器之间的数据无法共享。
当删除容器时,容器产生的数据将丢失
为了解决这些问题,Docker引入了数据卷(volume)机制,此卷可以绕过联合文件系统,与宿主机上的目录绑定
2.数据卷结构

- - volumes: /var/lib/docker/volumes/ 卷的名字
- - bind mounts:宿主机任意位置目录或文件
- - tmpfs :内存中空间,不会写入主机文件系统

3.数据卷优点
- volume 在容器创建时就会初始化,在容器运行时就可以使用其中的文件
- volume 能在不同的容器之间共享和重用
- 对volume中数据的操作会马上生效
- 对volume中数据的操作不会影响到镜像本身
- volume 的生存周期独立于容器的生存周期,即使删除容器,volume 仍然会存在,没有任何容器使用的volume也不会被Docker删除
二、使用 Volume
1.创建 volume
# 删除所有容器和卷
[root@localhost ~]# docker rm $(docker ps -aq) -f
[root@localhost ~]# docker volume rm $(docker volume ls -q)# 创建volume
[root@localhost ~]# docker volume create my-vol
my-vol
[root@localhost ~]# docker volume ls
DRIVER VOLUME NAME
local my-vol
[root@localhost ~]# ll /var/lib/docker/volumes/
总用量 52
brw------- 1 root root 259, 3 11月 12 08:32 backingFsBlockDev
-rw-------. 1 root root 65536 11月 12 08:40 metadata.db
drwx-----x 3 root root 19 11月 12 08:40 my-vol
注:Docker当前并未对volume的大小提供配额管理;用户在创建volume时无法指定volume的大小

注:my_vol下还有一层目录,挂载的时候实际上是把这个目录挂载到容器中
其他创建方式:
# 如果没有创建卷,在启动容器时挂载,也会自动创建
[root@localhost ~]# docker run -d -v nginx-vol:/data nginx:1.22.1
d406b3a496d429452566de1d612edaed5bc493322fde1b8e8a683df028bb0c80
[root@localhost ~]# docker volume ls
DRIVER VOLUME NAME
local my-vol
local nginx-vol# 匿名卷的创建方式,不选定挂载卷
$ docker run -d -v /data nginx:1.22.1# 启动mysql容器时,即便不手动挂载,也会自动挂载一个匿名卷
[root@localhost ~]# docker run -d -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0
DRIVER VOLUME NAME
local a54dc0b6a13954740884a4b15396e1341c73a8f123453e6e35610c012d53291c
local my-vol
local nginx-vol# 在启动mysql容器时,VOLUME [/var/lib/mysql]指令会在启动mysql容器时创建匿名卷并挂载到/var/lib/mysql目录下
[root@localhost ~]# docker history mysql:8.0
IMAGE CREATED CREATED BY SIZE <missing> 3 weeks ago VOLUME [/var/lib/mysql] 0B buildkit.dockerfile.v0
注:一般情况下尽量不使用匿名卷,匿名卷多了之后我们是看不到是哪个容器在用它;启动mysql容器时也可以直接挂载新卷组覆盖匿名卷
[root@localhost ~]# docker run -d -e MYSQL_ROOT_PASSWORD=123456 -v mysql-vol:/var/lib/mysql mysql:8.0
30e6d41f8e92920c77c345076ecd423bf15b106ed4aa156b77b4471ee8daba3e
[root@localhost ~]# docker volume ls
DRIVER VOLUME NAME
local a54dc0b6a13954740884a4b15396e1341c73a8f123453e6e35610c012d53291c
local my-vol
local mysql-vol
local nginx-vol
2.添加 volume
[root@localhost ~]# docker run -d -v my-vol:/data nginx:1.22.1
06cbbfe23d1c5f6333414c60d4e153d88e55e7c41e4aa2269f2eed74b8f9f164
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
06cbbfe23d1c nginx:1.22.1 "/docker-entrypoint.…" 15 seconds ago Up 14 seconds 80/tcp priceless_napier
[root@localhost ~]# docker exec -it 06cbbfe23d1c bash
root@06cbbfe23d1c:/# cd /data/
root@06cbbfe23d1c:/data## 宿主机中创建文件
[root@localhost _data]# touch host_file
# 容器中同样可以查看
root@06cbbfe23d1c:/data# ls
host_fileroot@06cbbfe23d1c:/data# touch docker-file
[root@localhost _data]# ll
总用量 0
-rw-r--r-- 1 root root 0 11月 12 08:49 docker-file
-rw-r--r-- 1 root root 0 11月 12 08:48 host_file
注:容器中的目录和宿主机中的/var/lib/docker/volumes/my-vol/_data是一样的
3.查看 volume
[root@localhost ~]# docker volume inspect mysql-vol
[{"CreatedAt": "2025-11-12T09:01:23+08:00","Driver": "local","Labels": null,"Mountpoint": "/var/lib/docker/volumes/mysql-vol/_data","Name": "mysql-vol","Options": null,"Scope": "local"}
]
4.挂载宿主机目录
[root@localhost ~]# docker run -d -v /data/docker-volume:/data nginx:1.22.1
[root@localhost ~]# ll /data/
总用量 0
drwxr-xr-x 2 root root 6 11月 12 09:09 docker-volume
drwxr-xr-x 8 root root 100 11月 6 14:55 registry# 宿主机创建文件
[root@localhost ~]# cd /data/docker-volume/
[root@localhost docker-volume]# touch host-file[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1d8fe1c0e18d nginx:1.22.1 "/docker-entrypoint.…" About a minute ago Up About a minute 80/tcp exciting_maxwell
# 容器中查看
[root@localhost ~]# docker exec -it 1d8fe1c0e18d bash
root@1d8fe1c0e18d:/# cd /data/
root@1d8fe1c0e18d:/data# ls
host-file
注:可以是宿主机的任意目录
5.挂载宿主机文件
[root@localhost ~]# docker run -d -v /data/docker-volume/host-file:/data/docker-volume.txt nginx:1.22.1# 在宿主机的目录下写入数据
[root@localhost docker-volume]# echo "hello world" >> host-file[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5e33da3c14da nginx:1.22.1 "/docker-entrypoint.…" 2 seconds ago Up 2 seconds 80/tcp naughty_herschel
# 启动容器查看
[root@localhost ~]# docker exec -it 5e33da3c14da bash
root@5e33da3c14da:/# cd /data/
root@5e33da3c14da:/data# cat docker-volume.txt
hello world
注:文件必须使用绝对路径
6.指定挂载权限
# 设为只读模式
[root@localhost ~]# docker run -d -v /data/docker-volume:/data:ro nginx:1.22.1
d4bc2ac50d63be9cc9eb1dcb802950ba710ddfff90361bcbb3afd5c9cbdc792e
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d4bc2ac50d63 nginx:1.22.1 "/docker-entrypoint.…" 3 seconds ago Up 2 seconds 80/tcp zealous_chebyshev# 尝试创建目录,发现没有权限
[root@localhost ~]# docker exec -it d4bc2ac50d63 bash
root@d4bc2ac50d63:/# cd /data/
root@d4bc2ac50d63:/data# mkdir file
mkdir: cannot create directory 'file': Read-only file system
7.共享 Volume
注:在使用 docker run或docker create创建新容器时,可以使用 --volumes-from 标签使得容器与已有的容器共享volum
[root@localhost ~]# ll /data/docker-volume/nginx-conf/
总用量 4
-rw-r--r-- 1 root root 398 2月 21 2025 nginx.repo[root@localhost ~]# docker run --name nginx-01 -d -v /data/docker-volume/nginx-conf/:/data nginx:1.22.1
d1c3edb2b260071d17f29c47188169bf4bc06133d78501f284b9c8b4dbfdd587
[root@localhost ~]# docker run --name nginx-02 -d --volumes-from nginx-01 nginx:1.22.1
aecc3eddc812c7202bb46ed778977137a7706fa299483c472bd106335f9d923a# 查看
[root@localhost ~]# docker exec nginx-01 ls /data
nginx.repo
[root@localhost ~]# docker exec nginx-02 ls /data
nginx.repo# 新创建的容器 nginx-02 与之前创建的容器 nginx-01 共享 volume8.删除 Volume
在删除容器时一并删除volume有以下两种方法。
使用
docker rm -v <container_name>删除容器。在运行容器时使用
docker run --rm, --rm 标签会在容器停止运行时删除容器以及容器所挂载的volume。
注意:
在使用docker volume rm 删除 volume 时,只有在没有任何容器使用时,该 volume 才能成功删除。
两种方法只会删除未命名的 volume,而对用户指定名字的 volume 进行保留。
如果 volume 是从宿主机中挂载的,无论对容器进行任何操作都不会导致其在宿主机中被删除。
9.Docker&宿主机复制文件
# 宿主文件 nginx.repo 复制到容器中[root@localhost nginx-conf]# docker cp /data/docker-volume/nginx-conf/nginx.repo nginx-01:/tmp/
Successfully copied 2.05kB to nginx-01:/tmp/
[root@localhost nginx-conf]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESd1c3edb2b260 nginx:1.22.1 "/docker-entrypoint.…" 6 minutes ago Up 6 minutes 80/tcp nginx-01[root@localhost nginx-conf]# docker exec -it d1c3edb2b260 bash
root@d1c3edb2b260:/# ls /tmp/
nginx.repo
10.-v vs --mount
挂载 Volume:
[root@localhost ~]# docker run -d \--name devtest \--mount type=volume,source=myvol2,target=/app \nginx:latest
挂载本地目录:
[root@localhost ~]# docker run -d \-it \--name devtest \--mount type=bind,source="$(pwd)"/target,target=/app \nginx:latest[root@localhost ~]# docker run -d \-it \--name devtest \--mount type=bind,source="$(pwd)"/target,target=/app,readonly \nginx:latest
挂载 tmpfs:
[root@localhost ~]# docker run -d \-it \--name tmptest \--mount type=tmpfs,destination=/app \nginx:latest
-v 和 --mount 的区别
-v 或 --volume:由三个字段组成,用冒号字符(:)分隔。这些字段必须按照正确的顺序,并且每个字段的含义不太清晰
--mount:由多个键值对组成,用逗号分隔,每个键值对由<key>=<value>组成。--mount 语法比 -v 或 --volume 更冗长,但键的顺序并不重要,并且该标志的值更易于理解
三、Docker 资源限制
1.cgroup 介绍
- cgroup 是 Linux 内核提供的一种机制
- cgroup 可以限制、记录进程组所使用的物理资源(包括 CPU、Memory、IO等)
- 实现 cgroup 的主要目的是为不同用户层面的资源管理,提供一个统一化的接口
2.cgroup 功能
资源限制:cgroup 可以对任务使用的资源总额进行限制。如设定应用运行时使用内存的上限,一旦超过这个配额就发出 OOM(Out of Memory)提示。
优先级分配:通过分配的CPU时间片数量及磁盘IO带宽大小,实际上就相当于控制了任务运行的优先级。
资源统计:cgroup 可以统计系统的资源使用量,如CPU使用时长、内存用量等
[root@localhost ~]# cd /sys/fs/cgroup [root@localhost cgroup]# mkdir example [root@localhost cgroup]# cd example/ [root@localhost example]# ls cgroup.controllers cpu.max.burst hugetlb.1GB.events io.bfq.weight memory.oom.group misc.events cgroup.events cpuset.cpus hugetlb.1GB.events.local io.latency memory.peak misc.events.local cgroup.freeze cpuset.cpus.effective hugetlb.1GB.max io.max memory.reclaim misc.max cgroup.kill cpuset.cpus.exclusive hugetlb.1GB.numa_stat io.stat memory.stat misc.peak cgroup.max.depth cpuset.cpus.exclusive.effective hugetlb.1GB.rsvd.current io.weight memory.swap.current pids.current cgroup.max.descendants cpuset.cpus.partition hugetlb.1GB.rsvd.max memory.current memory.swap.events pids.events cgroup.procs cpuset.mems hugetlb.2MB.current memory.events memory.swap.high pids.events.local cgroup.stat cpuset.mems.effective hugetlb.2MB.events memory.events.local memory.swap.max pids.max cgroup.subtree_control cpu.stat hugetlb.2MB.events.local memory.high memory.swap.peak pids.peak cgroup.threads cpu.stat.local hugetlb.2MB.max memory.low memory.zswap.current rdma.current cgroup.type cpu.weight hugetlb.2MB.numa_stat memory.max memory.zswap.max rdma.max cpu.idle cpu.weight.nice hugetlb.2MB.rsvd.current memory.min memory.zswap.writeback cpu.max hugetlb.1GB.current hugetlb.2MB.rsvd.max memory.numa_stat misc.current任务控制:cgroup 可以对任务执行挂起、恢复等操作
3.查看当前cgroup版本
[root@localhost ~]# mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
[root@localhost ~]# cd /sys/fs/cgroup
# 创建一个目录 example ,这个目录表示一个进程组
[root@localhost cgroup]# mkdir example
[root@localhost cgroup]# cd example/# 在 example 进程组中会有各种配置文件
[root@localhost example]# ls
cgroup.controllers cpu.max.burst hugetlb.1GB.events io.bfq.weight memory.oom.group misc.events
cgroup.events cpuset.cpus hugetlb.1GB.events.local io.latency memory.peak misc.events.local
cgroup.freeze cpuset.cpus.effective hugetlb.1GB.max io.max memory.reclaim misc.max
cgroup.kill cpuset.cpus.exclusive hugetlb.1GB.numa_stat io.stat memory.stat misc.peak
cgroup.max.depth cpuset.cpus.exclusive.effective hugetlb.1GB.rsvd.current io.weight memory.swap.current pids.current
cgroup.max.descendants cpuset.cpus.partition hugetlb.1GB.rsvd.max memory.current memory.swap.events pids.events
cgroup.procs cpuset.mems hugetlb.2MB.current memory.events memory.swap.high pids.events.local
cgroup.stat cpuset.mems.effective hugetlb.2MB.events memory.events.local memory.swap.max pids.max
cgroup.subtree_control cpu.stat hugetlb.2MB.events.local memory.high memory.swap.peak pids.peak
cgroup.threads cpu.stat.local hugetlb.2MB.max memory.low memory.zswap.current rdma.current
cgroup.type cpu.weight hugetlb.2MB.numa_stat memory.max memory.zswap.max rdma.max
cpu.idle cpu.weight.nice hugetlb.2MB.rsvd.current memory.min memory.zswap.writeback
cpu.max hugetlb.1GB.current hugetlb.2MB.rsvd.max memory.numa_stat misc.current
4.限制进程CPU资源
先启动一个占用 CPU 的程序,找到程序的进程号
# ps -ef | grep busyloop
root 155677 115803 99 16:14 pts/2 00:01:06 ./busyloop
# 把进程号添加到 cgroup.procs 文件 中
# echo 155677 >> cgroup.procs
# v2 版本限制cpu的文件使用 cpu.max, 这样会限制 155677 进程最多使用 1 核 CPU.
# echo "100000 100000" > cpu.max5.限制容器CPU资源
# 运行一个容器,限制可以使用1个cpu,使用 stress 占用2个cpu
[root@localhost example]# docker run -it --rm --cpus=1 registry.cn-beijing.aliyuncs.com/xxhf/stress /bin/bash# top 查看PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 4668 root 20 0 3704 384 384 R 51.5 0.0 1:40.96 stress 4667 root 20 0 3704 256 256 R 49.8 0.0 1:41.11 stress 6.限制容器内存资源
[root@localhost example]# docker run -it --rm -m 300M registry.cn-beijing.aliyuncs.com/xxhf/stress /bin/bash
root@243450f7f7e1:/# stress --vm 1 --vm-bytes 300M
stress: info: [11] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: FAIL: [11] (416) <-- worker 12 got signal 9
stress: WARN: [11] (418) now reaping child worker processes
stress: FAIL: [11] (452) failed run completed in 48sroot@243450f7f7e1:/# stress --vm 1 --vm-bytes 290M
stress: info: [13] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd# 使用 docker stats 命令 可以查看容器资源使用情况
[root@localhost /]# docker stats 243450f7f7e1
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
243450f7f7e1 naughty_haslett 126.49% 292.2MiB / 300MiB 97.40% 866B / 0B 5.75GB / 7.73GB 3
- 如果操作系统有swap 分区,容器会使用swap分区来保存内存文件,需要使用 'stress --vm 1 --vm-bytes 600M' 才会触发限制。
- 或者加一个参数
--memory-swap=300M,表示 可以使用的内存和交换空间的总和
