数据持久化
一、Storage Driver
数据存储
CentOS7版本的docker,Storage Driver为: Overlay2 backing filesystem: xfs
正常情况下,只有很少量的数据被写入到容器最上层的写入层,并且通过 volume 来写数据,然而也会遇到一些情况需要可以直接写入到容器的写入层,这就需要到了 storage driver 来帮忙啦
Docker 使用一些不同的 storage driver 来管理镜像层和容器层,这些 storage driver 不同于前面说到的 volume
- 一个镜像是有若干镜像层组成
- Dockerfile 中的每条指令都会生成一个镜像层,除了最上面的一层之外,其他的都是只读的
- 最上一层主要是镜像运行时的一些命令
- 每一层只是与它之前的层有一些不同,层层堆叠在一起
- 创建容器的时候,只是在底层上添加一个新的可写层。这一层通常称为“容器层”
Docker 支持多种 storage driver,有 AUFS、Device Mapper、Btrfs、OverlayFS、VFS 和 ZFS。它们都能实现分层的架构,同时又有各自的特性。对于 Docker 用户来说,具体选择使用哪个 storage driver 是一个难题,因为:
- 没有哪个 driver 能够适应所有的场景。
- driver 本身在快速发展和迭代。
不过 Docker 官方给出了一个简单的答案:
优先使用 Linux 发行版默认的 storage driver
Docker 安装时会根据当前系统的配置选择默认的 driver。默认 driver 具有最好的稳定性,因为默认 driver 在发行版上经过了严格的测试。
Ubuntu 用的 AUFS,底层文件系统是 extfs,各层数据存放在 /var/lib/docker/aufs。
Redhat/CentOS 的默认 driver 是 Device Mapper,SUSE 则是 Btrfs。
对于某些容器,直接将数据放在由 storage driver 维护的层中是很好的选择,比如那些无状态的应用。无状态意味着容器没有需要持久化的数据,随时可以从镜像直接创建。
比如 busybox,它是一个工具箱,我们启动 busybox 是为了执行诸如 wget,ping 之类的命令,不需要保存数据供以后使用,使用完直接退出,容器删除时存放在容器层中的工作数据也一起被删除,这没问题,下次再启动新容器即可。
但对于另一类应用这种方式就不合适了,它们有持久化数据的需求,容器启动时需要加载已有的数据,容器销毁时希望保留产生的新数据,也就是说,这类容器是有状态的。
这就要用到 Docker 的另一种存储机制:Data Volume。
二、Data Volume
Data Volume 本质上是 Docker Host 文件系统中的目录或文件,能够直接被 mount 到容器的文件系统中。Data Volume 有以下特点:
- Data Volume 是目录或文件,而非没有格式化的磁盘(块设备)
- 容器可以读写 volume 中的数据
- volume 数据可以被永久的保存,即使使用它的容器已经销毁
现在有数据层(镜像层和容器层)和 volume 都可以用来存放数据,具体使用的时候要怎样选择呢?考虑下面几个场景:
Database 软件 vs Database 数据
Web 应用 vs 应用产生的日志
数据分析软件 vs input/output 数据
Apache Server vs 静态 HTML 文件
相信会做出这样的选择:
前者放在数据层中。因为这部分内容是无状态的,应该作为镜像的一部分。
后者放在 Data Volume 中。这是需要持久化的数据,并且应该与镜像分开存放。
还有个大家可能会关心的问题:如何设置 volume 的容量?
因为 volume 实际上是 docker host 文件系统的一部分,所以 volume 的容量取决于文件系统当前未使用的空间,目前还没有方法设置 volume 的容量。
1、Bind mount
持久化存储:本质上是DockerHost文件系统中的目录或文件,能够直接被Mount到容器的文件系统中。在运行容器时,可以通过-v 实现。
小实验:运行一个nginx服务,做数据持久化
[root@docker01 ~]# mkdir /data/html -p
[root@docker01 ~]# cd /data/html/
[root@docker01 html]# cat >index.html<<EOF
> This is a testfile in dockerhost.
> EOF
[root@docker01 html]# docker run -itd --name t1 -p 80 -v /data/html:/usr/share/nginx/html nginx:latest
[root@docker01 html]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e7a9c8dcfe94 nginx "/docker-entrypoint.…" 13 seconds ago Up 12 seconds 0.0.0.0:32768->80/tcp t1
[root@docker01 html]# curl 127.0.0.1:32768
This is a testfile in dockerhost.
[root@docker01 html]# docker exec -it t1 bash
root@e7a9c8dcfe94:/# echo update1 > /usr/share/nginx/html/index.html
root@e7a9c8dcfe94:/# exit
exit
[root@docker01 html]# curl 127.0.0.1:32768
update1
PS:DockerHost上需要被挂载的源文件或目录,必须是已经存在,否则,当做的一个目录挂载到容器中。
host 中的修改确实生效了,bind mount 可以让 host 与容器共享数据。这在管理上是非常方便的。
即使容器没有了,bind mount 也还在。这也合理,bind mount 是 host 文件系统中的数据,只是借给容器用用,哪能随便就删了啊。
默认挂载到容器内的文件,容器是有读写权限。可以在运行容器时 -v 后边加“:ro” 限制容器的写入权限。
限制容器对挂载目录或文件只读权限:
[root@docker01 html]# docker run -itd --name t2 -p 80 -v /data/html:/usr/share/nginx/html:ro nginx:latest
[root@docker01 html]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3fb9168d0d31 nginx:latest "/docker-entrypoint.…" 17 seconds ago Up 17 seconds 0.0.0.0:32769->80/tcp t2
e7a9c8dcfe94 nginx "/docker-entrypoint.…" 6 minutes ago Up 6 minutes 0.0.0.0:32768->80/tcp t1
[root@docker01 html]# curl 127.0.0.1:32769
update1
[root@docker01 html]# docker exec -it t2 bash
root@3fb9168d0d31:/# echo update2 > /usr/share/nginx/html/index.html
bash: /usr/share/nginx/html/index.html: Read-only file system
root@3fb9168d0d31:/# exit
exit
[root@docker01 html]# curl 127.0.0.1:32769
update1
并且还可以挂载单独的文件到容器内部,一般它的使用场景是:如果不想对整个目录进行覆盖,而只希望添加某个文件,就可以使用挂载单个文件。
挂载单个文件
[root@docker01 html]# docker run -itd --name t3 -p 80 -v /data/html/index.html:/usr/share/nginx/html/index.html:ro nginx
[root@docker01 html]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
33e3142458af nginx "/docker-entrypoint.…" 14 seconds ago Up 13 seconds 0.0.0.0:32770->80/tcp t3
3fb9168d0d31 nginx:latest "/docker-entrypoint.…" 3 minutes ago Up 3 minutes 0.0.0.0:32769->80/tcp t2
e7a9c8dcfe94 nginx "/docker-entrypoint.…" 9 minutes ago Up 9 minutes 0.0.0.0:32768->80/tcp t1
[root@docker01 html]# curl 127.0.0.1:32770
update1
[root@docker01 html]# docker exec -it t3 bash
root@33e3142458af:/# cat /usr/share/nginx/html/index.html
update1
mount point 有很多应用场景,比如可以将源代码目录 mount 到容器中,在 host 中修改代码就能看到应用的实时效果。再比如将 mysql 容器的数据放在 bind mount 里,这样 host 可以方便地备份和迁移数据。
bind mount 的使用直观高效,易于理解,但它也有不足的地方:bind mount 需要指定 host 文件系统的特定路径,这就限制了容器的可移植性,当需要将容器迁移到其他 host,而该 host 没有要 mount 的数据或者数据不在相同的路径时,操作会失败。
2、Docker Manager Volume
docker managed volume 与 bind mount 在使用上的最大区别是不需要指定 mount 源,指明 mount point 就行了
[root@docker01 html]# docker run -itd --name v1 -P -v /usr/share/nginx/html nginx
[root@docker01 html]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6bb7c4b8d758 nginx "/docker-entrypoint.…" 7 seconds ago Up 6 seconds 0.0.0.0:32771->80/tcp v1
……
[root@docker01 html]# curl 127.0.0.1:32771
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
……
通过 -v 告诉 docker 需要一个 data volume,并将其 mount 到 /usr/share/nginx/html。那么这个 data volume 具体在哪儿呢?
这个答案可以在容器的配置信息中找到,执行 docker inspect 命令:
[root@docker01 ~]# docker inspect v1
……
"Mounts": [
{
"Type": "volume",
"Name": "8a127477cd084543baf1e130633e42d186b624d0ca91551b13e80e59997013d9",
"Source": "/var/lib/docker/volumes/8a127477cd084543baf1e130633e42d186b624d0ca91551b13e80e59997013d9/_data",
"Destination": "/usr/share/nginx/html",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
……
每当容器申请 mount docker manged volume 时,docker 都会在/var/lib/docker/volumes 下生成一个目录(例子中是 “/var/lib/docker/volumes/8a127477cd084543baf1e130633e42d186b624d0ca91551b13e80e59997013d9/_data”),这个目录就是 mount 源。
[root@docker01 ~]# cd /var/lib/docker/volumes/8a127477cd084543baf1e130633e42d186b624d0ca91551b13e80e59997013d9/_data/
[root@docker01 _data]# ls
50x.html index.html
[root@docker01 _data]# cat index.html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
……
删除容器的操作,默认不会对dockerHost上的源文件操作,如果想要在删除容器时把源文件也删除,可以在删除容器时添加 -v 选项(一般不推荐使用这种方式,因为文件有可能被其他容器使用)。
除了上述这种方式之外,还可以手动创建volume
手动创建volume
[root@docker01 ~]# docker volume create testweb
testweb
[root@docker01 ~]# docker volume ls
DRIVER VOLUME NAME
……
local testweb
[root@docker01 ~]# docker volume inspect testweb
[
{
"CreatedAt": "2020-09-09T05:21:48+08:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/testweb/_data",
"Name": "testweb",
"Options": {},
"Scope": "local"
}
]
[root@docker01 ~]# docker run -itd -P -v testweb:/usr/share/nginx nginx
[root@docker01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
caad8f9a76d3 nginx "/docker-entrypoint.…" About a minute ago Up About a minute 0.0.0.0:32772->80/tcp awesome_wiles
6bb7c4b8d758 nginx "/docker-entrypoint.…" 9 minutes ago Up 9 minutes 0.0.0.0:32771->80/tcp v1
……
[root@docker01 ~]# curl 127.0.0.1:32772
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
……
[root@docker01 ~]# cat /var/lib/docker/volumes/testweb/_data/html/index.html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
……
PS: 注意一下,运行容器时使用的手动创建的volume,在书写格式上和bind mount一样,但查看容器的详细信息,其实它仍然使用的是docker manager volume这种方式
总结:
Bind Mount 和 Docker Manager Volume的特点:
bind mount | docker managed volume | |
---|---|---|
volume 位置 | 可任意指定 | /var/lib/docker/volumes/… |
有mount point 影响 | 隐藏并替换为 volume | 原有数据复制到 volume |
是否支持单个文件 | 支持 | 不支持,只能是目录 |
权限控制 | 可设置为只读,默认为读写权限 | 无控制,均为读写权限 |
移植性 | 移植性弱,与 host path 绑定 | 移植性强,无需指定 host 目录 |
容器与容器的数据共享:
volume container:给其他容器提供volume存储卷的容器。并且它可以提供bind mount,也可以提供docker manager volume
创建一个vc_data容器
[root@docker01 ~]# docker create --name vc_data -v ~/html:/usr/share/nginx/html -v /other/useful/tools busybox
使用vc容器
[root@docker01 ~]# docker run -itd --name c1 -P --volumes-from vc_data nginx
容器的跨主机数据共享
docker01:1.128 | docker02:1.129 | docker03:1.150 |
---|---|---|
httpd | httpd | nfs |
要求:docker01和docker02上基于httpd镜像运行2个或多个容器,保证的主目录(默认访问界面内容)是一致的
docker03 的操作
[root@nfs ~]# yum -y install nfs-utils
[root@nfs ~]# mkdir /datashare
[root@nfs ~]# vim /etc/exports
/datashare *(rw,sync,no_root_squash)
[root@nfs ~]# systemctl restart rpcbind
[root@nfs ~]# systemctl enable rpcbind
[root@nfs ~]# systemctl restart nfs-server
[root@nfs ~]# systemctl enable nfs-server
[root@nfs ~]# cat >/datashare/index.html<<EOF
> ghost-webshare
> EOF
docker01 的操作
[root@docker01 ~]# showmount -e 192.168.1.150
Export list for 192.168.1.150:
/datashare *
[root@docker01 ~]# mkdir /htdocs
[root@docker01 ~]# mount -t nfs 192.168.1.150:/datashare /htdocs
[root@docker01 ~]# cat /htdocs/index.html
ghost-webshare
docker02 的操作
[root@docker02 ~]# showmount -e 192.168.1.150
Export list for 192.168.1.150:
/datashare *
[root@docker02 ~]# mkdir /htdocs
[root@docker02 ~]# mount -t nfs 192.168.1.150:/datashare /htdocs
[root@docker02 ~]# cat /htdocs/index.html
ghost-webshare
这里先不考虑将代码写入镜像,先以这种方式,分别在docker01和docker02部署httpd服务
docker01
[root@docker01 ~]# docker run -itd --name ghost-web1 -P -v /htdocs:/usr/local/apache2/htdocs httpd:latest
[root@docker01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5da15dc977b9 httpd:latest "httpd-foreground" 5 seconds ago Up 4 seconds 0.0.0.0:32774->80/tcp ghost-web1
PS: 查看端口映射0.0.0.0:32774->80/tcp
docker02
[root@docker02 ~]# docker run -itd --name ghost-web2 -P -v /htdocs:/usr/local/apache2/htdocs httpd:latest
[root@docker02 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
91dbfe3e3ea2 httpd:latest "httpd-foreground" 5 seconds ago Up 3 seconds 0.0.0.0:32768->80/tcp ghost-web2
PS: 查看端口映射0.0.0.0:32768->80/tcp
此时,用浏览器访问,两个WEB服务的主界面是一样。但如果,NFS服务器上的源文件丢失,则两个web服务都会异常
想办法将源数据写入镜像内,在基于镜像做一个vc_data容器。这里因为没接触到docker-compose和docker swarm等docker 编排工具,所以先在docker01和docker02上先手动创建镜像。
dockre01
[root@docker01 htdocs]# vim Dockerfile
FROM busybox
COPY index.html /usr/local/apache2/htdocs/index.html
VOLUME /usr/local/apache2/htdocs
[root@docker01 htdocs]# docker build -t back_data .
Sending build context to Docker daemon 3.072kB
Step 1/3 : FROM busybox
---> 6858809bf669
Step 2/3 : COPY index.html /usr/local/apache2/htdocs/index.html
---> Using cache
---> 3eedc65187bd
Step 3/3 : VOLUME /usr/local/apache2/htdocs
---> Using cache
---> 273ac7b0bafc
Successfully built 273ac7b0bafc
Successfully tagged back_data:latest
[root@docker01 htdocs]# docker create --name back_container1 back_data:latest
总结:
1)解决容器跨主机数据共享的方案: NFS
2)容器与容器的数据共享: 基于某个容器而来(–volumes-from选项),意味着这些容器和vc容器的数据存储是一样的。