Docker存储
Docker运行容器时,一般是一个容器执行一个程序。对于容器的启动,依赖于不止一种镜像联合挂载并启动。能存储此类n层镜像构建联合挂载的文件系统包括aufs,overlayfs2,dm等等,并且最上层需要构建可写层。
对于读写层,所有容器的操作都是保存在最上层的。对下层内容的操作比如删除最下层镜像本来已经存在的文件,则基于cow的方式实现。
在到达最上层之前,如果将文件标记为删除,则最上层是不可见的。如果删除后,用户又创建一个相同的文件,如此才能可见。底层存在的文件,如果被修改,用户也是可见的。
这种方式来修改,访问和删除,其效率会很低,因此,对于IO要求较高的应用,比如redis,它势必会对底层应用数据存储时,对IO性能要求高。比如存储服务如MySQL MariaDB。如果运行在容器上的文件系统之上,在数据存取时效率必然很低。如果需要绕过限制,可以使用存储卷的形式来实现。
另外,要实现持久性存储时,也需要使用存储卷的形式来完成。
存储卷
在宿主机中,找一个本地文件系统的某一目录,将目录与容器内部的文件系统之上的某一访问路径建立绑定关系。如此前的挂载方式一般,将容器内的/data/web 和宿主机的/container/data/web 建立绑定关系。容器内的进程向目录写数据时,实际上是写到宿主机的目录中。
因此,容器内的进程在实现数据存取时,能绕过容器的文件系统的限制,从而与宿主机内的本地文件系统建立关联关系。如此容器和宿主机共享数据内容,二者是同步的。
也可以让两个本来隔离的文件系统,在某个子路径上建立绑定关系,让两个容器的文件系统子路径实现共享的效果。这个子路径,称为存储卷 volume。
如此一来,当容器关闭或者删除时,也无需担心数据丢失。只要数据目录存在,即可实现数据脱离容器的生命周期而实现持久化。只要重建容器时,只要关联到此前的存储卷上即可。如此可复现一个相同的容器。
当启动容器时,添加的命令和存储卷如果可以保存在一个配置文件中,将大大简化重现容器的难度。这就是容器编排工具的作用。
容器关联的卷不一定是host本地文件系统,也可以是NFS共享存储。只要host挂载NFS即可。如此某个host之上的某个NFS路径,关联到容器的某个目录上。当容器如redis保存数据时,数据实际上是保存在NFS之上,当容器退出时,自动删除。
当容器再次启动时,可以全局调度某台host来启动此容器。如此分配存储计算和内存资源时,不局限于单个host,而是集群范围内。这就是云的范畴。
主流的docker编排工具,都能实现此类功能。不过,这严重依赖于共享存储。
有状态和无状态
如果容器的应用是有状态的,当前的连接处理与此前的连接状态是有关联关系的。而大多数有状态的应用都需要存储数据的,比如MySQL;而无状态的应用比如nginx反向代理;而tomcat的会话可以保存在内存中,这类是有状态,而无需持久存储。
有状态的应用,一般都需要持久存储。并且,是否有状态,是否需要持久存储,可以使用正交关系来联合。
对于有状态的应用,对运维人员的技术能力要求是很高的。比如数据库集群,主从,扩容等等。而无状态的应用,则无需太多要求。比如反向代理nginx。
如果期望应用能跨主机启动时,必须要求该应用在下次启动时能够找回原来使用的持久存储的数据,才能实现复现应用。
Docker默认存储卷是使用容器所在宿主机的本地文件系统目录,但是如此就无法跨主机。这是docker无法解决的问题。
存储卷类型
Docker有两种类型的卷,每种类型都在容器中存在一个挂载点,但其在宿主机上的位置有所不同。
绑定挂载卷
挂载点为宿主机上的路径,需要人工指定特定路径。容器也需要指定特定路径,然后二者建立关联关系。
docker管理卷
在容器内指定挂载点,而宿主机的哪个目录建立关联关系则无需管理,由docker自行创建目录。这是docker daemon所维护的,一般是在特定的路径下。如此极大解脱了用户使用卷时的耦合关系,但是用户无法指定目录。
作为临时存储而言,第二种方式很有效。如果需要复现容器时,则需要使用第一种存储,否则第二种存储可能重新初始化一个新的挂载目录。
在容器中使用Volumes
在docker run 命令使用-v选项即可使用volumes
docker管理卷
创建容器时,指定特定的目录作为存储卷:
root@eto:~# docker run --name b2 -it -v /data busybox
/ #
此路径是共享的,并且是宿主机自动生成的,这就是docker管理的卷
"Mounts": [
{
"Type": "volume",
"Name": "a11c30a5ec5a2e4bd7ac76f5d41b177d4504c463720532b90e89721c62e9f9bb",
"Source": "/var/lib/docker/volumes/a11c30a5ec5a2e4bd7ac76f5d41b177d4504c463720532b90e89721c62e9f9bb/_data",
"Destination": "/data",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
绑定挂载卷
root@eto:~# docker run --rm --name b3 -it -v /data/volumes/b3:/data busybox
/ # ls /data
"Mounts": [
{
"Type": "bind",
"Source": "/data/volumes/b3",
"Destination": "/data",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
如果宿主机上的路径不存在,则会自动创建。
如此即使容器删除,保存在宿主机上的文件依然存在。
可是使用go模板,来过滤相关信息:
root@eto:~# docker inspect -f {{.Mounts}} b3
[{bind /data/volumes/b3 /data true rprivate}]
root@eto:~# docker inspect -f {{.NetworkSettings.IPAddress}} b3
172.17.0.2
让两个docker容器,同时关联到宿主机的目录,实现容器间的目录共享:
root@eto:~# docker run --rm --name b4 -it -v /data/volumes/b3:/data busybox
/ # ls /data
mydata
期望需要让多个容器,使用一个卷,并且如果不希望人为记住-v指定的路径。Docker支持容器可以去复制另一个容器的存储卷的路径,通过如下方式:
- 创建容器,指定它应该使用何种存储卷。它无需启动,而是作为其他容器的基础支撑容器(所谓的pause容器)
- 其他存储卷直接复制第一个容器的存储卷
在联盟式容器中,共享名称空间(IPS,Network,UTS)。比如nginx对外提供服务,tomcat容器仅服务于nginx。这两个容器使用同一个网络名称空间,如此可以隐藏了tomcat容器。而最底下的,由提供存储卷的容器作为基础支撑,如此就可以将nginx容器和tomcat容器联合工作,并打包镜像。
在同一个基础上,建立MariaDB,仅监听lo网卡即可。无需对外服务,以此实现nmt。看起来,如同一个主机,仅跑了三个应用。