数据持久化
一、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 是一个难题,因为:
1.没有哪个 driver 能够适应所有的场景。
2.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 有以下特点:
1.Data Volume 是目录或文件,而非没有格式化的磁盘(块设备)。
2.容器可以读写 volume 中的数据。
3. 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 html
[root@docker01 ~]# cd html/
[root@docker01 ~]# vim index.html
添加:
This is a testfile in dockerHost.
[root@docker01 ~]# docker run -itd --name testweb -v /root/html/:/usr/share/nginx/html -p 80 nginx:latest
[root@docker01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a9bf2a40ee4a nginx:latest "/docker-entrypoint.…" 2 minutes ago Up 2 minutes 0.0.0.0:32768->80/tcp testweb
[root@docker01 ~]# curl 172.17.0.1:32768
This is a testfile in dockerHost.
PS: DockerHost上需要被挂着的源文件或目录,必须是已经存在,否则,
当做的一个目录挂着到容器中。
host 中的修改确实生效了,bind mount 可以让 host 与容器共享数据。这在管理上是非常方便的。
即使容器没有了,bind mount 也还在。这也合理,bind mount 是 host文件系统中的数据,只是借给容器用用,哪能随便就删了啊。
默认挂载到容器内的文件,容器是有读写权限。可以在运行容器是-v后边加“:ro” 限制容器的写入权限。
//限制容器对挂载目录或文件只读权限。
[root@docker01 ~]# docker run -itd --name testweb2 -p 80 -v /tmp/html:/usr/share/nginx/html:ro nginx
并且还可以挂载单独的文件到容器内部,一般它的使用场景是:如果不想对整个目录进行覆盖,而只希望添加某个文件,就可以使用挂载单个文件。
//挂载单个文件。
[root@docker01 ~]# docker run -itd --name testweb3 -p 80 -v /tmp/html/index.html:/usr/share/nginx/html/index.html:ro nginx
mount point 有很多应用场景,比如我们可以将源代码目录 mount 到容器中,在 host 中修改代码就能看到应用的实时效果。再比如将 mysql 容器的数据放在 bind mount 里,这样 host 可以方便地备份和迁移数据。
bind mount 的使用直观高效,易于理解,但它也有不足的地方:bindmount 需要指定 host 文件系统的特定路径,这就限制了容器的可移植性,当需要将容器迁移到其他 host,而该 host 没有要 mount 的数据或者数据不在相同的路径时,操作会失败。
2、Docker Manager Volume
docker managed volume 与 bind mount 在使用上的最大区别是不需要指定 mount 源,指明 mount point 就行了。
[root@docker01 ~]# docker run -itd --name t1 -P -v /usr/share/nginx/html nginx:latest
我们通过 -v 告诉 docker 需要一个 data volume,并将其 mount 到/usr/share/nginx/html。那么这个 data volume 具体在哪儿呢?
这个答案可以在容器的配置信息中找到,执行 docker inspect 命令:
[root@docker01 ~]# docker inspect t1
每当容器申请 mount docker manged volume 时,docker 都会在/var/lib/docker/volumes 下生成一个目录(例子中是"/var/lib/docker/volumes/f4a0a1018968f47960efe760829e3c5738c702
533d29911b01df9f18babf3340/_data ),这个目录就是 mount 源。
删除容器的操作,默认不会对dockerHost上的源文件操作,如果想要在删除容器时把源文件也删除,可以在删除容器时添加 -v 选项(一般不推荐使用这种方式,因为文件有可能被其他容器使用)。
除了上述这种方式之外,还可以手动创建volume。
//手动创建volume
[root@docker01 ~]# docker volume create testweb4
testweb4
[root@docker01 ~]# docker volume ls
DRIVER VOLUME NAME
local 47786c758c43ee7395d22b69d47e47c9cffc4ef7fe0518b922608ec861f6c97d
local testweb4
[root@docker01 ~]# docker volume inspect testweb4
[
{
"CreatedAt": "2021-02-10T04:00:02+08:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/testweb4/_data",
"Name": "testweb4",
"Options": {},
"Scope": "local"
}
]
[root@docker01 ~]# docker run -itd -P -v testweb4:/usr/share/nginx nginx:latest
PS: 注意一下,运行容器时使用的我们手动创建的volume,在书写格式上和bind mount一样,但查看容器的详细信息,其实它仍然使用的是dockermanager volume这种方式。
总结:
Bind Mount 和 Docker Manager Volume的特点;
bind mount | docker managed volume | |
---|---|---|
volume 位置 | 可任意指定 | /var/lib/docker/volumes/… |
有mountpoint 影响 | 隐藏并替换为volume | 原有数据复制到 volume |
是否支持单个文件 | 支持 | 不支持,只能是目录 |
权限控制 | 可设置为只读,默认为读写权限 | 无控制,均为读写权限 |
移植性 | 移植性弱,与 hostpath 绑定 | 移植性强,无需指定 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 t5 -P --volumes-from vc_data nginx
容器的跨主机数据共享
docker01 | docker02 | docker03 |
---|---|---|
httpd | httpd | nfs |
要求:docker01和docker02上基于httpd镜像运行2个或多个容器,保证的主目录(默认访问界面内容)是一样的。
[root@docker01 ~]# docker pull httpd
[root@docker02 ~]# docker pull httpd
docker03操作
[root@docker0 ~]# yum -y install nfs-utils
[root@docker0 ~]# mkdir /datashare
[root@docker0 ~]# vim /etc/exports
[root@docker0 ~]# systemctl start rpcbind
[root@docker0 ~]# systemctl enable rpcbind
[root@docker0 ~]# systemctl start nfs-server
[root@docker0 ~]# systemctl enable nfs-server
[root@docker0 ~]# vim /datashare/index.html
添加:
bdqn-webshare
docker01操作
[root@docker01 ~]# yum -y install showmount
[root@docker01 ~]# showmount -e 192.168.1.22
Export list for 192.168.1.22:
/datashare *
[root@docker01 ~]# mkdir /htdocs
[root@docker01 ~]# mount -t nfs 192.168.1.22:/datashare /htdocs/
[root@docker01 ~]# cat /htdocs/index.html
bdqn-webshare
docker02操作(跟docker01操作一样)
[root@docker02 ~]# showmount -e 192.168.1.22
Export list for 192.168.1.22:
/datashare *
[root@docker02 ~]# yum -y install showmount
[root@docker02 ~]# mkdir /htdocs
[root@docker02 ~]# mount -t nfs 192.168.1.22:/datashare /htdocs/
[root@docker02 ~]# cat /htdocs/index.html
bdqn-webshare
这里先不考虑将代码写入镜像,先以这种方式,分别在docker01和docker02部署httpd服务
//docker01
[root@docker01 ~]# docker run -itd --name bdqn-web1 -P -v /htdocs:/usr/local/apache2/htdocs httpd:latest
PS: 查看端口映射0.0.0.0:32774->80/tcp
[root@docker01 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ace81a2ceae5 httpd:latest "httpd-foreground" 33 seconds ago Up 32 seconds 0.0.0.0:32774->80/tcp bdqn-web1
//docker02
[root@docker02 ~]# docker run -itd --name bdqn-web2 -P -v /htdocs:/use/local/apache2/htdocs httpd:latest
PS: 查看端口映射0.0.0.0:32768->80/tcp
[root@docker02 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a58abe97bd04 httpd:latest "httpd-foreground" 5 seconds ago Up 3 seconds 0.0.0.0:32768->80/tcp bdqn-web2
此时,用浏览器访问,两个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 .
[root@docker01 htdocs]# docker create --name back_container1 back_data:latest
//docker02一样(直接从docker复制过来)
[root@docker01 htdocs]# scp /htdocs/Dockerfile 192.168.1.21:/htdocs/
[root@docker02 htdocs]# docker build -t back_data .
[root@docker02 htdocs]# docker create --name back_container1 back_data:latest
总结:
1)解决容器跨主机数据共享的方案: NFS
2)容器与容器的数据共享: 基于某个容器而来(–volumes-from选项),意味着这些容器和vc容器的数据存储是一样的。