通过前几篇入门介绍,我们知道默认情况下docker容器中所有创建的文件都在容器可写层(writable container layer),这种方式存在几个问题:
-
当容器不再存在,数据不会持久化,如果另一个进程需要数据,则很难从容器中取出数据
-
容器的可写层是和容器运行的宿主机紧密耦合的,很难迁移
-
写入容器可写层需要存储驱动程序来管理文件系统,也就是我们常说的联合文件系统(AUFS),这中机制和直接写入主机文件系统相比有不小的性能损耗
针对这些问题,docker提供了几种方式用于在主机中存储文件,这样即便容器停止运行或者被删除,文件也会保持不变,这就是所谓的程序(运行)和数据(存储)的解耦。这种解耦有利于程序的重用和数据的共享,今天就来具体探讨一下docker管理应用数据的几种方式.
三种存储机制
-
Docker管理卷(docker managed volume)
-
绑定-挂载卷(bind-mount volume)
-
tmpfs mount
从容器内部看,这三种方式并没有什么区别,都是以容器内部文件系统中的文件目录或者一个具体的文件作为数据读写的载体。但从宿主机的角度看,他们是有明显的区别的,可以用下图来简单表述他们的区别。bind-mount volume可以把文件存储在宿主机文件系统的任何位置,docker宿主机和非docker进程可以随时修改他们;docker managed volume 把文件存储在宿主机docker管理的区域,非常方便通过docker api或者CLI 命令来管理;tmpfs mount是把文件存储在内存中。
1. Docker Managed Volume
Docker管理卷(以下简称Volume)是容器生成和使用数据的首选方式,这种方式完全由docker管理,不依赖宿主机的操作系统和特定目录结构,大概有如下优点:
-
Volume在容器之间更安全共享和重用,更能容易备份和迁移
-
Volume可以方便的使用docker CLI命令或者API操控
-
Volume可以在Linux或Windows容器上工作(不受OS和文件目录结构影响)
-
Volume驱动可以把文件加密存储在远程服务器或者云上(AS3,OSS等)
-
Volume作为存储不会增加容器的大小,读写时具有较高的性能
1.1 创建和管理Volume的几个命令
- create一个名字为vol_1的volume
$ docker volume create vol_1
vol_1
- list volume
$ docker volume ls
DRIVER VOLUME NAME
local vol_1
- inspect a volume
$ docker volume inspect vol_1
[{ "CreatedAt": "2021-12-04T02:54:53Z", "Driver": "local", "Labels": {}, "Mountpoint": "/var/lib/docker/volumes/vol_1/_data", "Name": "vol_1", "Options": {}, "Scope": "local" }]
- remove a volume
$ docker volume rm vol_1
vol_1
- 删除所有不再使用的volume
$ docker volume prune
1.2 运行容器并挂载volume
使用-v(—volume)或者—mount标识在启动容器的时候挂载volume,如果volume不存在的话会自动创建。
1) -v 方式
参数有三部分,用冒号分割,格式为 field1:field2:field3
-
field1是volume的名字,如果省略表明这是一个匿名volume
-
field2是volume在容器内的路径,这部分不能省略
-
field3是可选项,通常用逗号分割各个附加选项,比如 RO(readOnly)表只读volume
下面是一个简单的sample,运行容器同时创建/挂载一个名字为vol_1的volume到容器的/app目录:
$ docker run -d —name test -v vol_1:/app nginx:latest
2) --mount 方式
mount 命令由一组逗号分割的key=value的键值对组成, 这种方式使得命令比较冗长,但让我们更加明确命令的含义,也更加容易理解,key通常有以下几个
-
type: mount的类型,有bind,volume,tmpfs等,对于docker managed volume来说,type是volume
-
Source(src):指定volume的名字,对于匿名volume来说可以省略
-
destination(dst 或 target) 指定容器内的目录或者文件的路径
-
readOnly或者ro: 表示这个volume是只读的
-
Volume-opt选项: 指定一些可选的键值对
下面是一个简单的sample,运行容器同时创建/挂载一个名字为vol_1的volume到容器的/app目录:
$ docker run -d \
--name test \
--mount source=vol_1,target=/app \
nginx:latest
1.3 在docker-compose中使用volume
docker-compose 的会在后续文章具体介绍,本文只需要知道他是一个容器编排工具就足够了。我们可以使用docker volume create直接在compose外部创建卷,然后在docker-compose.yml内部引用,如下所示
version: "3.9"
services:
frontend:
image: node:lts
volumes:
- myapp:/home/node/app
volumes:
myapp:
external: true
1.4 使用volume****启动service
当使用volume启动服务时,每个服务容器都使用自己的local volume。如果使用本地卷驱动程序,则所有容器都无法共享此数据,但某些卷驱动程序确实支持共享存储。下面的示例使用四个副本启动nginx服务,每个副本使用一个名为myvol2的本地卷。
$ docker service create -d \
--replicas=4 \
--name devtest-service \
--mount source=myvol2,target=/app \
nginx:latest
1.5 应用场景介绍
1) 使用容器数据填充新建的volume
如果使用运行容器创建volume,volume挂载的容器的文件存或者目录里有文件,则这些容器中的文件会自动拷贝到新建的volume中,其他共享这个volume的容器也可以使用这些数据。我们来演示一下:
--mount 方式
$ docker run -d \
--name=nginxtest \
--mount source=nginx-vol,destination=/usr/share/nginx/html \
nginx:latest
-v 方式
$ docker run -d \
--name=nginxtest \
-v nginx-vol:/usr/share/nginx/html \
nginx:latest
我们知道nginx容器在/usr/share/nginx/html目录下存在一些html文件,当我们使用以上任何一个命令运行容器并创建volume挂载在这个目录之后,新建的volume就自动拥有了这些文件.
2) 在容器之间共享volume
如今微服务越来越流行,在构建容错应用程序时,可能需要配置同一服务的多个副本以访问相同的文件。此时共享存储volume就有了用武之地,比如基于AS3或者OSS等,下面我们以阿里云上一台ECS作为共享存储来演示一下。由于ssh方式访问ECS需要使用vieux/sshfs驱动,所以需要先安装一下。
a. 安装sshfs驱动插件:
b. 创建volume
作为sample,我就直接使用root账号+密码方式粗暴演示了(密码和IP做了隐藏):
c. 启动容器中使用
首先启动一个容器来挂在sshvolume
$ docker run -d \
> --name sshfs-container \
> --volume-driver vieux/sshfs \
> --mount src=sshvolume,target=/app \
> nginx:latest1b108f924f8a127206494cf82239d92ea5728ad130d988ac0e671531e62cb8fb
然后我们在容器中向挂在目录/app随便创建一个文件并写入一些数据
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1b108f924f8a nginx:latest "/docker-entrypoint.…" 17 seconds ago Up 16 seconds 80/tcp sshfs-container
$ docker exec -it 1b108f924f8a bash
#cd /app
#touch test.log
# echo abc test.log
附一个完整的命令截图:
登录ECS查看已经写入volume的文件:
3) 备份volume
假设我们先启动了一个容器:
$ docker run -v /dbdata --name dbstore ubuntu /bin/bash
接着:
-
启动新容器并从dbstore容器装载volume
-
将本地主机目录装载为/backup
-
将命令传递给/backup目录中的backup.tar文件,对dbdata卷的内容进行tar。
(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata
当命令完成且容器停止时,剩下的是dbdata卷的备份。
4) 从备份中****恢复volume
使用刚刚创建的备份,我们可以将其还原到同一个容器中,或者还原到其他地方创建的另一个容器中。例如,创建一个名为dbstore2的新容器:
$ docker run -v /dbdata --name dbstore2 ubuntu /bin/bash
然后再新容器中解压
$ docker run --rm --volumes-from dbstore2 -v $(pwd):/backup ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1"
2. bind-mount volume
绑定挂载和docker managed volume在使用命令上大同小异,如果使用-v方式创建,则第一个field是主机上的一个绝对路径,如果使用--mount方式创建,type指定为bind即可。绑定挂载不受docker管理,我们没有办法使用docker api或者cli command对其进行操作。正如我们介绍第一部分docker managed volume说的,那是我们首选方案。所以本文对bind-mount volume不做更多的展开了。
3. tmpfs mount
本文最后要简单介绍的是tmpfs mount,这种类型的mount只能在宿主机运行linux的时候使用,最大的一个特点是只会在容器内存中读写数据,比较适合存储一些敏感数据。在我们详细阐述了第一种类型的存储之后,相信tmpfs mount的创建和使用都比较简单,命令上也是大同小异.