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

# 基本语法
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 就行了

# 方式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容器举个例子

[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 中

[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 到三个容器

[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

查看当前主页面

[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中主页文件,并查看确定所有容器使用了新的主页

[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:

[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

[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:

[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 为例:

[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,而且连挂载点都是一样的

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

[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 共享

怎么创建?

[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 中。

修改文本内容

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

build 新镜像 datapacked:

[root@docker dockerfile]# docker build -t datapacked .

在这里插入图片描述

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

[root@docker ~]# docker create --name vc_data datapacked
7406f82b781dc19b4625d81934e5a29e81810349b8fad9891f92c00b5209b801

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

[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 生命周期管理

创建数据卷

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

查看数据卷

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

清理无用数据卷

# 删除所有未被使用的数据卷(谨慎!)
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 MountDocker Managed VolumeVolume Container
宿主机路径需要指定Docker 自动分配Docker 自动分配
数据持久化
数据共享
备份难度容易(直接复制)中等容易
性能直接IO略低略低
适合场景开发、配置生产数据多容器共享

实用命令小抄

# 查看数据卷占用空间
docker system df -v

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

# 复制数据卷数据到宿主机
docker run --rm -v 源卷:/源 -v $(pwd):/备份 busybox cp -r /源 /备份
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值