【Docker教程】Docker 数据持久化

介绍

Docker 容器一旦删除,那么我们在容器内的一切操作将不复存在,比如我们在容器内存储的数据等;为了解决这个问题,Docker 提出了数据卷概念,就像我们运行一个mysql容器,我们需要存储我们的数据,日志之类,想要他们不会随着容器的删除而销毁,或者我们需要在各个容器间共享数据,那么这些数据卷或者数据卷容器都能做到。

数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性:

  • 数据卷可以在容器之间共享和重用
  • 对数据卷的修改会立马生效
  • 对数据卷的更新,不会影响镜像
  • 数据卷默认会一直存在,即使容器被删除
  • 数据卷的生命周期一直持续到没有容器使用它为止

数据卷的使用,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的数据卷

Docker提供了两种方式管理数据

  • 数据卷
  • 数据卷容器

数据卷

添加数据卷

docker run 命令中 使用 -v 标识给容器内添加一个或者多个数据卷

在容器内创建一个新的数据卷 /data/shop

1
$ docker run -d -P --name my-shop-web -v /data/shop sqgulj/shop-web

这种方式我们也可以在Dockerfile中使用VOLUME指令来给创建的容器添加一个或者多个数据卷,比如官方的 mysql 的 Dockerfile 中用 VOLUME 指定了数据卷

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.........

RUN {
apt-get update && apt-get install -y mysql-server="${MYSQL_VERSION}" && rm -rf /var/lib/apt/lists/* \
&& rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql /var/run/mysqld \
&& chown -R mysql:mysql /var/lib/mysql /var/run/mysqld \
# ensure that /var/run/mysqld (used for socket and lock files) is writable regardless of the UID our mysqld instance ends up having at runtime
&& chmod 777 /var/run/mysqld \
# comment out a few problematic configuration values
&& find /etc/mysql/ -name '*.cnf' -print0 \
| xargs -0 grep -lZE '^(bind-address|log)' \
| xargs -rt -0 sed -Ei 's/^(bind-address|log)/#&/' \
# don't reverse lookup hostnames, they are usually another container
&& echo '[mysqld]\nskip-host-cache\nskip-name-resolve' > /etc/mysql/conf.d/docker.cnf

VOLUME /var/lib/mysql

COPY docker-entrypoint.sh /usr/local/bin/
RUN ln -s usr/local/bin/docker-entrypoint.sh /entrypoint.sh # backwards compat
ENTRYPOINT ["docker-entrypoint.sh"]
............

其中 VOLUME /var/lib/mysql 指定了mysql的数据卷

将宿主机的一个目录,挂在到容器里

使用 -v,除了可以创建一个数据卷,还可以挂载本地主机目录到容器中

1
$ docker run -d -P --name my-shop-web -v /local/data/shop:/data/shop sqgulj/shop-web

这将会把本地目录/local/data/shop挂载到容器的/data/shop目录,宿主机上的目录必须是绝对路径,如果目录不存在docker会自动创建它。

出于可移植和分享的考虑,在Dockerfile中这种方式不支持

docker默认情况下是对数据卷有读写权限,但是我们通过这样的方式让数据卷只读:

1
$ docker run -d -P --name my-shop-web -v /local/data/shop:/data/shop:ro sqgulj/shop-web

这里我们同样挂载了/local/data/shop目录,只是添加了 ro 选项来限制它只读。

将宿主机上单个文件挂载到容器中

除了能挂载目录外,-v 标识还可以将宿主机的一个特定文件挂载为数据卷

1
$ sudo docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash

这样就可以记录在容器输入过的命令了

如果直接挂载一个文件,很多文件编辑工具,包括 vi 或者 sed –in-place,可能会造成文件 inode 的改变,从 Docker 1.1.0起,这会导致报错误信息。所以最简单的办法就直接挂载文件的父目录。

查看数据卷
  • 使用命令 docker volume 可以查看我们生成的数据卷
1
2
3
$ docker volume ls 
DRIVER VOLUME NAME
local b202765855d3b77e574284fde0079d31fb0258997a7b72eb6f7c8299053f216b

我们可以看到我们添加的数据卷 /data/shop ,当我们查看的时候并不是我们提供的名称,而是生成一系列的随机串,当我们为多个容器添加数据卷的时候,它也会为其生成一系列的随机串,这样不利于我们辨识哪些数据卷挂在哪个容器中,为了解决这个问题,我们可以在添加数据数据卷的时候,为数据卷起个辨识的名称,比如

1
$ docker run -d -P --name my-shop-web -v shopwebdata:/data/shop sqgulj/shop-web

我们发现,这次我们为容器添加一个数据的时候我们在前面加了一个 shopwebdata ,注意shopwebdata前面没有斜杠( / ),如果有斜杠那是把本地的一个目录挂载到容器内的一个目录

1
2
3
$ docker volume ls 
DRIVER VOLUME NAME
local shopwebdata

这次我们就能看到我们添加的数据卷就是我们起的名称,而不是一些列的随机串

  • 使用命令 docker inspect 查看数据卷
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# my-shop-web 为容器的名称
$ docker inspect my-shop-web
............

"Mounts": [
{
"Type": "volume",
"Name": "b202765855d3b77e574284fde0079d31fb0258997a7b72eb6f7c8299053f216b",
"Source": "/var/lib/docker/volumes/b202765855d3b77e574284fde0079d31fb0258997a7b72eb6f7c8299053f216b/_data",
"Destination": "/data/shop",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],

.............

这里只列出了其中的 Mounts 属性,从中可以看到Source字段显示了数据卷存放的位置

删除数据卷

数据卷是被设计来持久化数据的,因此,删除容器并不会删除数据卷。如果想要在删除容器时同时删除数据卷,可使用如下命令:

1
$ docker rm -v 容器ID

这样既可在删除容器的同时也将数据卷删除

我们还可以使用 docker volume rm 命令删除数据卷

1
2
3
4
5
# 查看数据卷
$ docker volume ls

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

删除所有的数据卷

1
docker volume rm $(docker volume ls -f dangling=true -q)

数据卷容器

如果有些数据,需要在多个容器之间进行共享,这时候可以使用数据卷容器
创建数据卷容器

1
docker run --name mysql-volume -v /data mysql

其它容器可以使用–volumes-from标识,来使用通过刚刚创建的数据卷容器,来挂载对应的数据卷

1
2
3
$ docker run -d --volumes-from mysql-volume --name db1 mysql

$ docker run -d --volumes-from mysql-volume --name db2 mysql

这样 db1 和 db2 两个容器都共享 mysql-volume 这个容器中的文件

挂载相同数据卷的容器,容器的停止和删除,不会对数据卷产生影响

我们也还可以对一个容器使用多个 –volumes-from 标识,来将多个数据卷桥接到这个容器中