如何在 Docker 容器之间共享数据
前言
Docker 是一个开源的应用容器引擎。Docker 可以让开发者打包他们创建的应用以及相应的依赖包到一个可移植、轻量级的容器中。Docker 可大大简化在容器中管理应用程序的过程。容器使用沙盒机制,运行在其中的应用与主系统相互分离,类似与虚拟机。但容器比虚拟机有更棒的可移植性、占用计算机资源更小。
通常,Docker 容器都是用后即删的。但有时,在删除容器后,我们也希望保存容器运行时,生成的数据。比如,数据库,服务器生成的日志,以及用户在我们网站上留下的数据。我们可以使用Docker Volumes(Docker 卷)来长期存放这些容器内生成的数据。
Docker 卷可以在创建容器的同一命令中一起创建,也可以在需要的时候,单独创建。在本教程中,我们一起学习如何使用卷在容器与容器之间共享数据。
准备工作
要根据本教程学习,首先我们要有一台安装好 Ubuntu 20.04 的服务器:
- 有 sudo 的 root 用户权限,大家可根据《Ubuntu 120.04初始服务器设置》(撰写中,稍后上线)指南来配置自己的服务器。
- Docker 已经按照《Docker入门指南:如何在 Ubuntu 20.04 上安装和使用Docker》 的第1步和第2步中的说明进行安装。
第1步:创建一个卷
我们使用docker volume create
命令创建卷。可让我们立即创建卷而不会与任何特定容器相关联。我们将使用此命令添加名为的卷DataVolume1
:
docker volume create --name DataVolume1
如果命令执行成功,会输出:
DataVolume1
为了让这个卷发挥作用。我们用ubuntu镜像来创建一个容器。并在创建命令中使—rm
参数让这个容器在退出时自动删除。
添加 -v
参数来挂载新卷。-v
后面紧接挂载卷的名。注意卷后面还有个冒号。冒号后面紧跟容器内的绝对路径。如果指向的目录在镜像里没有,那么运行命令时,这个目录会被创建。
docker run -ti --rm -v DataVolume1:/datavolume1 ubuntu
在容器中时,让我们向卷中写入一些数据:
echo "KalaSearch-test-01" > /datavolume1/KalaSearch-test-01.txt
刚刚我们的命令中带了--rm
参数。所以容器退出时,这个容器会被自动删除。即便容器被删除,容器的数据保存在我们刚刚创建的卷中,我们仍然可以访问。
root@1c74497136f9:/# exit
我们可以使用docker volume inspect
命令验证系统中是否存在卷:
docker volume inspect DataVolume1
命令执行后的输出:
kalasearch@chuan-server:~$ docker volume inspect DataVolume1
[
{
"CreatedAt": "2020-07-25T05:51:08Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/DataVolume1/_data",
"Name": "DataVolume1",
"Options": {},
"Scope": "local"
}
]
接下来,让我们创建一个新容器并挂载DataVolume1
卷:
docker run --rm -ti -v DataVolume1:/datavolume1 ubuntu
验证内容:
kalasearch@chuan-server:~$ cat /datavolume1/KalaSearch-test-01.txt
命令输出:
KalaSearch-test-01
接着,退出容器:
exit
我们在这一步中,创建了一个卷,然后把它挂载到容器上。
第2步:创建独立于容器的卷,容器删除后仍可使用
接下来我们将会学习到,创建一个容器到同时一并创建一个卷。删除这个容器后,将这个卷挂载到新容器上。
我们使用docker run
命令来创建新到容器。-t
用来在终端显示。-i
用于允许我们和它交互。为了容易识别,我们用—name
来命名新容器。
添加 -v
参数来挂载新卷。-v
后面紧接挂载卷的名,我们它成为为DataVolume2。注意卷后面还有个冒号。冒号后面紧跟容器内的绝对路径。
docker run -ti --name=Container2 -v DataVolume2:/datavolume2 ubuntu
注:-v
参数在使用上有一个很容易出错的点,请注意。如果-v
后面的路径是以/
或~/
开头,则说明我们正在创建一个挂载卷。如果没有这个/
,则说明我们正在给容器创建一个卷,它指向后面的路径。
让我们来看个例子就明白了。
例:
-v /path:/path/in/container
这是将宿主机的/path
目录与容器的/path/in/container
目录挂载。-v path:/path/in/container
这段语句的意思是在容器里挂载名为path
的卷,它的存放位置是/path/in/container
。
有关从主机绑定挂载目录的更多信息,请参见《如何在Docker容器和主机之间共享数据》
在容器中,我们写入一些作为参考的参考数据。
echo "Kalasearch-test-02" > /datavolume2/Kalasearch-test-02.txt
cat /datavolume2/Kalasearch-test-02.txt
输出结果:
Kalasearch-test-02
退出容器:
root@ef7f9e9a9fdc:/# exit
当我们重新启动容器时,这个卷会自动挂载。
docker start -ai Container2
我们来验证以下是不是刚才创建的卷和卷中的数据还在:
cat /datavolume2/Kalasearch-test-02.txt
输出结果:
Kalasearch-test-02
最后,让我们退出这个容器
exit
如果 Docker 引用了这个卷,则我们就不能删除这个卷。我们来试试如果用命令来删除会发生什么:
我们可以使用此ID删除容器:
docker volume rm DataVolume2
输出结果:
Error response from daemon: remove DataVolume2: volume is in use - [ef7f9e9a9fdc363ec3f189986663ba9cbd58750b0209254fe5d4fcf0cb82d22a]
我们可以使用此ID删除容器:
docker rm ef7f9e9a9fdc363ec3f189986663ba9cbd58750b0209254fe5d4fcf0cb82d22a
输出结果:
ef7f9e9a9fdc363ec3f189986663ba9cbd58750b0209254fe5d4fcf0cb82d22a
卸下容器不会影响体积。通过使用列出卷,我们可以看到它仍然存在于系统上docker volume ls
:
docker volume ls
输出结果:
DRIVER VOLUME NAME
local DataVolume1
local DataVolume2
我们可以使用docker volume rm
将其删除:
docker volume rm DataVolume2
在这一步的学习中。我们创建了一个没有数据的空卷。接下来,我们会学习到在创建一个含有数据的卷并挂载到容器上时的情况。
第3步:创建一个包含目录和数据的卷
一般,使用docker volume create
命令,创建一个容器的同时独立创建一个卷。但有一个特殊情况,如果我们创建一个容器同时创建一个卷,并提供镜像中的目录,则这些数据将被复制到卷中。
让我们来看一个例子。我们创建一个容器并将数据卷挂载到/var
这个目录包含基础镜像数据。
docker run -ti --rm -v DataVolume3:/var ubuntu
所有在/var
镜像中的数据都将会复制到这个卷中。
我们先退出这个容器:
exit
这次,我们在创建和挂载时,添加ls
命令。这个命令可以把卷的内容显示出来。
docker run --rm -v DataVolume3:/datavolume3 ubuntu ls datavolume3
输出结果:
kalasearch@chuan-server:~$ docker run --rm -v DataVolume3:/datavolume3 ubuntu ls datavolume3
backups
cache
lib
local
lock
log
mail
opt
run
spool
tmp
接下来,我们将学习到如何在多个容器之间共享卷。
第4步:在多个 Docker 容器之间共享数据
上面我们学到如何将一个卷挂载到一个容器上。实际中,我们希望多个容器同时接到同一卷中,互相共享数据。接下来,我们来学习如何多个容器同时共享一个卷的数据。
docker run -ti --name=Container4 -v DataVolume4:/datavolume4 ubuntu
接下来,我们创建一个文件并添加一些文本:
echo "This file is shared between containers 更多教程请访问 KalaSearch.com" > /datavolume4/Kalasearch-test-04.txt
我们来检查以下写入的数据:
cat /datavolume4/Kalasearch-test-04.txt
输出结果:
root@eecde70fd97e:/# cat /datavolume4/Kalasearch-test-04.txt
This file is shared between containers 更多教程请访问 KalaSearch.com
接下来,我们在添加一些内容Container5
:
echo "Both containers can write to DataVolume4 更多教程请访问 KalaSearch.com" >> /datavolume4/Kalasearch-test-04.txt
最后,退出容器:
exit
接下来,我们来检查一下,数据是否仍在于Container4
。
创建Container5
,然后挂载Container4
的卷,看看能否访问
docker run -ti --name=Container5 --volumes-from Container4 ubuntu
让我们来检查一下数据是否互通了。
cat /datavolume4/kalasearch-test-04.txt
输出结果:
This file is shared between containers 更多教程请访问 KalaSearch.com
现在我们来验证,两个容器是否可以互通:
我们通过Container5
来写入一些内容到卷中的数据里。
echo "Both containers can write to DataVolume4" >> /datavolume4/kalasearch-test-04.txt
然后退出这个容器:
exit
接下来我们打开Container4
来确认数据是否已经写入并可以从这个容器中读取。
docker start -ai Container4
cat /datavolume4/kalasearch-test-04.txt
输出结果:
This file is shared between containers 更多教程请访问 KalaSearch.com
Both containers can write to DataVolume4
我们可以看到,两边的容器都可以向这个卷中写入数据,并可以从另一个容器中读取出来。
特别注意:因为一个卷可以对应多个容器,且每个容器都可以写入这一个卷。所以一定要小心数据被覆盖,谨慎操作。如果我们想让卷只读,可以加上:ro
接下来我们看看如何操作。
附赠教程:启动容器6并以只读方式挂载卷
为了避免多容器读写同一个卷造成重要数据被覆盖,我们可以用只读方式挂载一个卷到容器中。只需要在容器后面添加参数:ro
即可。
让我们来看个实例:
docker run -ti --name=Container6 --volumes-from Container4:ro ubuntu
我们来尝试删除卷中的kalasearch-test-04.txt
文件。
rm /datavolume4/kalasearch-test-04.txt
输出结果:
root@54ed85f0fc0f:/# rm /datavolume4/kalasearch-test-04.txt
rm: cannot remove '/datavolume4/kalasearch-test-04.txt': Read-only file system
最终我们可以发现,在只读挂载的卷中,我们无法删除卷中的数据。让数据更加安全。
这个教程结束,现在让我们来做最后也是最常规的步骤,删除不实用的容器,释放系统空间。
docker rm Container4 Container5 Container6
docker volume rm DataVolume4
到这里,我们应该学会了如何使用只读方式挂载卷到一个容器中,并确认容器读取这个卷时是只读状态。
总结
在本教程中,我们学会了如何给一个容器创建一个卷,并将这个卷的数据共享给其他卷,以及如何让在卷中的数据可以在多个容器中读取。以及如何将卷以只读模式挂载到容器上,以保护数据安全。
其实除了多个容器中共享数据外,运维更需要在容器与宿主机之间共享数据,以便在容器关闭或删除后,我们仍然可以在宿主机上方便的读取容器运行时的日志和其他生成的数据。大家想学习容器与主机如何共享数据,可以看我们这篇《如何在 Docker 容器和主机之间共享数据》文章。