16.docker:存储

文章目录

Docker:存储

Docker 的两类存储资源

想象一下,你的容器就像一个临时小房间,里面可以放东西,但一旦房间拆除(容器删除),里面的东西就都没了。为此 Docker 为容器提供了两种存放数据的资源:

  1. 由 storage driver 管理的镜像层和容器层。
  2. Data Volume。

1. storage driver

在镜像那一章,我们学习了docker镜像的分层结构

容器的最上层是可写的容器层,以及若干只读的镜像层组成,容器的数据存放在这些层中,这样的分层结构最大的特性是 Copy-on-Write(COW):

  1. 新数据会直接存放在最上面的容器层
  2. 修改现有数据会从镜像层将数据复制到容器层,修改后的数据直接保存在容器层,镜像层保持不变
  3. 如果多个层中有命名相同的文件,用户只能看到最上层的文件

可见docker storage driver的优点:

  • 分层结构使镜像和容器的创建更加迅速
  • 镜像复用节省空间
  • 写时不完全复制节省资源

常见的 Storage Driver:overlay2aufsdevicemapper

对于使用哪一种,这是一个难题,因为没有哪一种driver能适应多有场景,而driver本身也在快速发展和迭代,当然官方给了一个简单的方法,使用linux发行版中默认的storage driver即可

就比如

CentOS Stream 8 用的overlay2,底层文件系统是xfs,各层数据存放在 /var/lib/docker

2. data volume

Data Volume 本质上是 Docker Host 文件系统中的目录或文件,能够直接被 mount 到容器的文件系统中

docker有以下特点:

  • 持久化:容器删除,数据还在
  • 高性能:直接访问宿主机磁盘
  • 可共享:多个容器可挂载同一卷
  • 可迁移:备份恢复方便

我们具体选择上述的哪一种方式存放数据呢?以下有几个场景

  1. Database 软件 vs Database 数据
  2. Web 应用 vs 应用产生的日志
  3. 数据分析软件 vs input/output 数据
  4. Apache Server vs 静态 HTML 文件

相信大家会做出这样的选择:

  1. 前者放在数据层中。因为这部分内容是无状态的,应该作为镜像的一部分。
  2. 后者放在 Data Volume 中。这是需要持久化的数据,并且应该与镜像分开存放。

那么我们怎么设置volume的容量呢?

因为 volume 实际上是 docker host 文件系统的一部分,所以 volume 的容量取决于文件系统当前未使用的空间,目前还没有方法设置 volume 的容量。

在具体使用上,docker 提供两种类型的volume:bind mount 和 docker managed volume

Bind Mount

bind mount 是将 host 上已存在的目录或文件 mount 到容器。

例如 docker host 上有目录 home/myapp:挂载到 容器的 /app

bash 复制代码
# 基本语法
docker run -v /宿主机/目录:/容器内目录 镜像名

# 实际例子:把本地的 /home/myapp 挂载到容器的 /app
docker run -d -v /home/myapp:/app nginx

# 常用写法(使用当前目录)
docker run -d -v $(pwd)/html:/usr/share/nginx/html nginx

使用场景

  1. 开发时共享代码:代码在本地改,容器里立即生效
  2. 配置文件管理:把配置文件放在宿主机,多个容器共用
  3. 日志收集:容器日志直接写到宿主机目录
  4. 方便迁移:比如将 mysql 容器的数据放在 bind mount 里,这样 host 可以方便地备份和迁移数据

当然也有不足的地方:

bind mount 需要指定 host 文件系统的特定路径,这就限制了容器的可移植性,当需要将容器迁移到其他 host,而该 host 没有要 mount 的数据或者数据不在相同的路径时,操作会失败。

注意事项

如果宿主机目录不存在,Docker 会自动创建吗?

答案:不会!必须先创建好目录


Docker Managed Volume

docker managed volume 与 bind mount 在使用上的最大区别是不需要指定 mount 源,指明 mount point 就行了

bash 复制代码
# 方式1:运行容器时创建
docker run -d -v /容器内路径 镜像名
# 例子:Docker 会自动在宿主机创建目录
docker run -d -v /data mysql

# 方式2:先创建数据卷,再使用
docker volume create my_volume  # 创建
docker run -d -v my_volume:/data mysql  # 使用

# 查看所有数据卷
docker volume ls

# 查看数据卷详细信息
docker volume inspect my_volume

拿httpd容器举个例子

bash 复制代码
[root@docker ~]# docker run -d -v /usr/local/apache2/htdocs httpd
e191a2a9bafef8eff1730453ef53ed2fb81757f6dcd6d1099ca3f1f9125ede4a

[root@docker ~]# docker inspect e191a2a9ba
"Mounts": [
            {
                "Type": "volume",
                "Name": "db84f285dcc41c890221967cc2ec70a5f7e63a1da117b1613ec230adbb0beabd",
                "Source": "/var/lib/docker/volumes/db84f285dcc41c890221967cc2ec70a5f7e63a1da117b1613ec230adbb0beabd/_data",
                "Destination": "/usr/local/apache2/htdocs",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],

docker inspect 的输出很多,我们感兴趣的是 Mounts 这部分,这里会显示容器当前使用的所有 data volume,包括 bind mount 和 docker managed volume。

可以看见在linux上docker 把数据卷存放到 /var/lib/docker/volumes/ 的目录下面,其中有一个很长名字的目录下的 /_data 就是mount 源

但要明确一点:此时的 /usr/local/apache2/htdocs 已经不再是由 storage driver 管理的层数据了,它已经是一个 data volume。我们可以像 bind mount 一样对数据进行操作,例如更新数据

现在,

简单回顾一下 docker managed volume 的创建过程:

  1. 容器启动时,简单的告诉 docker "我需要一个 volume 存放数据,帮我 mount 到目录 /abc"。
  2. docker 在 /var/lib/docker/volumes 中生成一个随机目录作为 mount 源。
  3. 如果 /abc 已经存在,则将数据复制到 mount 源,
  4. 将 volume mount 到 /abc

使用场景

  1. 不像要操心宿主机的路径
  2. 让docker 完全管理存储
  3. 需要备份和迁移数据卷

如何共享数据?

数据共享是 volume 的关键特性,本节我们详细讨论通过 volume 如何在容器与 host 之间,容器与容器之间共享数据

容器与 host 共享数据

对于 bind mount 是非常明确的:直接将要共享的目录 mount 到容器

而docker managed volume 就要复杂一些,由于 volume 位于 host 中的目录,是在容器启动时才生成的,所以需要将共享数据拷贝到 volume 中

bash 复制代码
[root@docker ~]# docker run -d -p 80:80 -v /usr/local/apache2/htdocs httpd
8b0774de494bfae8c70ad4e655903b8e2b2fb11db3356dc1da1e66600f9b1b81

[root@docker ~]# curl 127.0.0.1:80
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>It works! Apache httpd</title>
</head>
<body>
<p>It works!</p>
</body>
</html>

# 将docker host 的文件拷贝到容器中
[root@docker ~]# docker cp ~/index.html 8b0774de49:/usr/local/apache2/htdocs
Successfully copied 2.05kB to 8b0774de49:/usr/local/apache2/htdocs

[root@docker ~]# curl 127.0.0.1:80
hahahahaha

docker cp 可以在容器和 host 之间拷贝数据,当然我们也可以直接通过 Linux 的 cp 命令复制到 /var/lib/docker/volumes/xxx。

容器之间共享数据

方法一:

将共享数据放在 bind mount 中,然后将其 mount 到多个容器

还是以httpd为例,这次我们创建由三个httpd容器组成的集群,让他们使用相同的html文件

将容器的$HOME/htdocs mount 到三个容器

bash 复制代码
[root@docker ~]# docker run --name web1 -d -p 80 -v ~/htdocs:/usr/local/apache2/htdocs httpd
65612fdd1fd5357b618b2be27536ecf3dd43c29c638dd6c7c9734f36507e4137
[root@docker ~]# docker run --name web2 -d -p 80 -v ~/htdocs:/usr/local/apache2/htdocs httpd
e8af72a7cf1ca173a4ccc3c03162de23727d3a15d73f0501e2b01364ff84f69c
[root@docker ~]# docker run --name web3 -d -p 80 -v ~/htdocs:/usr/local/apache2/htdocs httpd
60fcc8745bb8f491c60d2c75f7f6ed85c096f7b0e4c4d5a67bcdbc1019803e04

查看当前主页面

bash 复制代码
[root@docker ~]# docker ps
CONTAINER ID   IMAGE     COMMAND              CREATED              STATUS              PORTS                                     NAMES
60fcc8745bb8   httpd     "httpd-foreground"   About a minute ago   Up About a minute   0.0.0.0:32771->80/tcp, :::32771->80/tcp   web3
e8af72a7cf1c   httpd     "httpd-foreground"   About a minute ago   Up About a minute   0.0.0.0:32770->80/tcp, :::32770->80/tcp   web2
65612fdd1fd5   httpd     "httpd-foreground"   About a minute ago   Up About a minute   0.0.0.0:32769->80/tcp, :::32769->80/tcp   web1

[root@docker ~]# curl 127.0.0.1:32769
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
 <head>
  <title>Index of /</title>
 </head>
 <body>
<h1>Index of /</h1>
<ul></ul>
</body></html>
[root@docker ~]# curl 127.0.0.1:32770
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
 <head>
  <title>Index of /</title>
 </head>
 <body>
<h1>Index of /</h1>
<ul></ul>
</body></html>
[root@docker ~]# curl 127.0.0.1:32771
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
 <head>
  <title>Index of /</title>
 </head>
 <body>
<h1>Index of /</h1>
<ul></ul>
</body></html>

修改volume中主页文件,并查看确定所有容器使用了新的主页

bash 复制代码
[root@docker ~]# vim htdocs/index.html
updated index page!

[root@docker ~]# docker ps -a
CONTAINER ID   IMAGE     COMMAND              CREATED         STATUS         PORTS                                     NAMES
60fcc8745bb8   httpd     "httpd-foreground"   2 minutes ago   Up 2 minutes   0.0.0.0:32771->80/tcp, :::32771->80/tcp   web3
e8af72a7cf1c   httpd     "httpd-foreground"   2 minutes ago   Up 2 minutes   0.0.0.0:32770->80/tcp, :::32770->80/tcp   web2
65612fdd1fd5   httpd     "httpd-foreground"   2 minutes ago   Up 2 minutes   0.0.0.0:32769->80/tcp, :::32769->80/tcp   web1
[root@docker ~]# curl 127.0.0.1:32769
updated index page!
[root@docker ~]# curl 127.0.0.1:32770
updated index page!
[root@docker ~]# curl 127.0.0.1:32771
updated index page!

方法二:

volume container 共享数据

volume container 是专门为其他容器提供 volume 的容器。它提供的卷可以是 bind mount,也可以是 docker managed volume。

下面我们创建一个volume container:

bash 复制代码
[root@docker ~]# docker create --name vc_data \
> -v ~/htdocs/:/usr/local/apache2/htdocs \
> -v /other/userful/tools \
> busybox
014faa96a1cf5eb930e85e98ca765c0c8579703fa3d46b9fb9c88734b2491dce

解释:

  • 这里执行docker create 是因为volume container的作用只是提供数据,它本身不需要处于运行状态
  • 容器mount 了两个volume:
    • 第一个-v 是bind mount ,存放web server静态文件
    • 第二个-v 是docker managed volume ,存放一些实用工具(当然现在是空的,这里只是做个示例)

通过docker inspect 可以查看这两个volume

bash 复制代码
[root@docker ~]# docker inspect vc_data
......
"Mounts": [
            {
                "Type": "bind",
                "Source": "/root/htdocs",
                "Destination": "/usr/local/apache2/htdocs",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            },
            {
                "Type": "volume",
                "Name": "d4a26dd5e2fa266a999ecc612e94b23f5e224e40315d5394e3ebcdbd0a4155e2",
                "Source": "/var/lib/docker/volumes/d4a26dd5e2fa266a999ecc612e94b23f5e224e40315d5394e3ebcdbd0a4155e2/_data",
                "Destination": "/other/userful/tools",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],

其他容器可以通过 --volumes-from 使用 vc_data 这个 volume container:

bash 复制代码
[root@docker ~]# docker run --name web1 -d -p 80 --volumes-from vc_data httpd
71cfab54eb669e4ed1d1ef65c4c45a99adcb134ff9344394c20a65994e09e338

[root@docker ~]# docker run --name web2 -d -p 80 --volumes-from vc_data httpd
7a49e071194135dad80adb66775d9c11041e52e85f5176815a8c5c193b2809f2

[root@docker ~]# docker run --name web3 -d -p 80 --volumes-from vc_data httpd
2b970fc89a4134e561c1b1ca88db718fc2d1cda63b78aced3e44495cf95cfb2f

三个 httpd 容器都使用了 vc_data,看看它们现在都有哪些 volume,以 web1 为例:

bash 复制代码
[root@docker ~]# docker inspect web1
"Mounts": [
            {
                "Type": "bind",
                "Source": "/root/htdocs",
                "Destination": "/usr/local/apache2/htdocs",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            },
            {
                "Type": "volume",
                "Name": "d4a26dd5e2fa266a999ecc612e94b23f5e224e40315d5394e3ebcdbd0a4155e2",
                "Source": "/var/lib/docker/volumes/d4a26dd5e2fa266a999ecc612e94b23f5e224e40315d5394e3ebcdbd0a4155e2/_data",
                "Destination": "/other/userful/tools",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],

可以看见,web1容器使用的就是vc_data 的volume,而且连挂载点都是一样的

现在验证一下数据共享的效果:

bash 复制代码
[root@docker ~]# docker ps
CONTAINER ID   IMAGE     COMMAND              CREATED         STATUS         PORTS                                     NAMES
2b970fc89a41   httpd     "httpd-foreground"   2 minutes ago   Up 2 minutes   0.0.0.0:32770->80/tcp, :::32770->80/tcp   web3
7a49e0711941   httpd     "httpd-foreground"   2 minutes ago   Up 2 minutes   0.0.0.0:32769->80/tcp, :::32769->80/tcp   web2
71cfab54eb66   httpd     "httpd-foreground"   2 minutes ago   Up 2 minutes   0.0.0.0:32768->80/tcp, :::32768->80/tcp   web1

[root@docker ~]# echo "This content is from a volume container!" > ~/htdocs/index.html

[root@docker ~]# curl 127.0.0.1:32768
This content is from a volume container!
[root@docker ~]# curl 127.0.0.1:32769
This content is from a volume container!
[root@docker ~]# curl 127.0.0.1:32770
This content is from a volume container!

可见,三个容器已经成功共享了 volume container 中的 volume

volume container 的特点:

  • 与 bind mount 相比,不必为每一个容器指定 host path,容器只需与 volume container 关联,实现了容器与 host 的解耦
  • 使用 volume container 的容器其 mount point 是一致的,有利于配置的规范和标准化,但也带来一定的局限,使用时需要综合考虑

Data-packed Volume Container

创建时就自带初始数据的数据卷容器

volume container 的数据归根到底还是在 host 里,有没有办法将数据完全放到 volume container 中,同时又能与其他容器共享呢?

通常我们称这种容器为 data-packed volume container。其原理是将数据打包到镜像中,然后通过 docker managed volume 共享

怎么创建?

bash 复制代码
[root@docker ~]# cd dockerfile/
[root@docker dockerfile]# mkdir htdocs
[root@docker dockerfile]# vim Dockerfile 
[root@docker dockerfile]# cat Dockerfile
FROM busybox:latest
ADD htdocs /usr/local/apache2/htdocs
VOLUME /usr/local/apache2/htdocs

ADD 将静态文件添加到容器目录 /usr/local/apache2/htdocs。
VOLUME 的作用与 -v 等效,用来创建 docker managed volume,mount point 为 /usr/local/apache2/htdocs,因为这个目录就是 ADD 添加的目录,所以会将已有数据拷贝到 volume 中。

修改文本内容

bash 复制代码
[root@docker dockerfile]# echo "This content is from a data packed volume container!" > htdocs/index.html

build 新镜像 datapacked:

bash 复制代码
[root@docker dockerfile]# docker build -t datapacked .

用新镜像创建 data-packed volume container:

bash 复制代码
[root@docker ~]# docker create --name vc_data datapacked
7406f82b781dc19b4625d81934e5a29e81810349b8fad9891f92c00b5209b801

因为在 Dockerfile 中已经使用了 VOLUME 指令,这里就不需要指定 volume 的 mount point 了。启动 httpd 容器并使用 data-packed volume container:

bash 复制代码
[root@docker ~]# docker run -d -p 80:80 --volumes-from vc_data httpd
4897f83f470490e76493bae7f9343c09ebdf16abd9dc8bd45119a422b181801d

[root@docker ~]# curl 127.0.0.1:80
This content is from a data packed volume container!

容器能够正确读取 volume 中的数据。data-packed volume container 是自包含的,不依赖 host 提供数据,具有很强的移植性,非常适合 只使用 静态数据的场景,比如应用的配置信息、web server 的静态文件等。


Volume 生命周期管理

创建数据卷

bash 复制代码
docker volume create [卷名]
docker run -v 卷名:/路径  # 会自动创建

查看数据卷

bash 复制代码
docker volume ls                    # 列表
docker volume inspect 卷名          # 详细信息
docker inspect 容器名               # 查看容器的数据卷信息

清理无用数据卷

bash 复制代码
# 删除所有未被使用的数据卷(谨慎!)
docker volume prune

# 删除指定数据卷
docker volume rm 卷名

备份和恢复

因为 volume 实际上是 host 文件系统中的目录和文件,所以 volume 的备份实际上是对文件系统的备份。将volume 文件打包备份即可

volume 的恢复也很简单,如果数据损坏了,直接用之前备份的数据拷贝到 指定目录下就可以了

数据卷迁移

如果我们想使用更新版本的容器,这就涉及到数据迁移,方法是:

  1. docker stop 当前容器。

  2. 启动新版本容器并 mount 原有 volume。

    例如docker run -d -p 5000:5000 -v /myregistry:/var/lib/registry registry:latest

当然,在启用新容器前要确保新版本的默认数据路径是否发生变化。

总结对比表

特性 Bind Mount Docker Managed Volume Volume Container
宿主机路径 需要指定 Docker 自动分配 Docker 自动分配
数据持久化
数据共享
备份难度 容易(直接复制) 中等 容易
性能 直接IO 略低 略低
适合场景 开发、配置 生产数据 多容器共享

实用命令小抄

bash 复制代码
# 查看数据卷占用空间
docker system df -v

# 清理所有无用资源
docker system prune -a --volumes

# 复制数据卷数据到宿主机
docker run --rm -v 源卷:/源 -v $(pwd):/备份 busybox cp -r /源 /备份
相关推荐
week_泽2 小时前
OpenCV图像拼接实践笔记(第一部分)
人工智能·笔记·opencv
乾元2 小时前
AI 在云网络(VPC / VNet)部署的编排与安全对齐——从“手工堆资源”到“意图驱动的网络生成”(含 Terraform 工程化)
运维·网络·人工智能·网络协议·安全·云计算·terraform
盛满暮色 风止何安2 小时前
负载均衡的部署模式
运维·服务器·网络·网络安全·负载均衡
无双@2 小时前
Github BettaFish 微舆docker部署教程 —— 打造你的八卦天团!
docker·容器·开源·github·微舆·bettafish
BahTiYar_2 小时前
ctfshow Web应用安全与防护系列
笔记·web安全
七月七332 小时前
半小时搞定GitHub学生认证
笔记·github
小毅&Nora2 小时前
【人工智能】【阿里云百炼平台】 ① 大模型全景图:从文本到全模态,一张图看懂AI能力边界(2025版)
人工智能·阿里云·云计算
蒙奇D索大2 小时前
【数据结构】排序算法精讲 | 交换排序全解:交换思想、效率对比与实战代码剖析
数据结构·笔记·考研·算法·排序算法·改行学it
_F_y2 小时前
数据链路层
运维·服务器·网络