1.COW机制
Docker镜像由多个只读层叠加而成,启动容器时,Docker会加载只读镜像层并在镜像栈顶部添加一个读写层。
如果运行中的容器修改了现有的一个已经存在的文件,那么该文件将会从读写层下面的只读层复制到读写层,该文件的只读版本依然存在,只是已经被读写层中该文件的副本所隐藏,这就是“写时复制(COW)”机制。
2.存储卷
存储卷就是将宿主机的本地文件系统中存在的某个目录直接与容器内部的文件系统上的某一目录建立绑定关系。这就意味着,当我们在容器中的这个目录下写入数据时,容器会将其内容直接写入到宿主机上与此容器建立了绑定关系的目录。
3. 使用存储卷的好处
- 当容器关闭甚至被删除时,只要不删除与此容器绑定的在宿主机上的这个存储目录,我们就不用担心数据丢失了
- 通过这种方式管理容器,容器就可以脱离主机的限制,可以在任意一台部署了docker的主机上跑容器,而其数据则可以置于一个共享存储文件系统上,比如nfs。
- Docker的存储卷默认情况下是使用其所在的宿主机上的本地文件系统目录的,也就是说宿主机上有一块属于自己的硬盘,这个硬盘并没有共享给其他的Docker主机,而在这台主机上启动的容器所使用的存储卷是关联到此宿主机硬盘上的某个目录之上。
- 这就意味着容器在这台主机上停止运行或者被删除了再重建,只要关联到硬盘上的这个目录下,那么其数据还存在。但如果在另一台主机上启动一个新容器,那么数据就没了。而如果在创建容器的时候我们手动的将容器的数据挂载到一台nfs服务器上,那么这个问题就不再是问题了。
关闭并重启容器,其数据不受影响,但删除Docker容器,则其更改将会全部丢失。
Docker存在的问题有:
- 存储于联合挂载文件系统中,不易于宿主机访问
- 容器间数据共享不便
- 删除容器其数据会丢失
4. 存储卷管理方式
存储卷(Data Volume)于容器初始化时被自动创建,由base image提供的卷中的数据会于此期间完成复制。
Volume的初衷是独立于容器的生命周期实现数据持久化,因此删除容器之时既不会删除卷,也不会对未被引用的卷做垃圾回收操作。
存储卷为Docker提供了独立于容器的数据管理机制,我们可以把镜像想象成静态文件,例如“程序”,把卷类比为动态内容,例如“数据”。所以镜像可以重用,而卷则可以共享。
卷实现了“程序(镜像)”和“数据(卷)”的分离,以及“程序(镜像)”和“制作镜像的主机”的分离,用户制作镜像时无须再考虑镜像运行的容器所在的主机的环境。
5. 存储卷的分类
Docker有两种类型的卷,每种类型都在容器中存在一个挂载点,但其在宿主机上的位置有所不同:
- 绑定挂载卷
- 指向主机文件系统上用户指定位置的卷
- Docker 管理的卷
- Docker 守护进程在 Docker 拥有的主机文件系统的一部分中创建托管卷
6.容器数据管理
容器中管理数据主要有两种方式:
- 数据卷(Data Volumes)
- 数据卷容器(Data Volumes Containers)
容器Volume使用语法:
Docker-managed volume
docker run -it --name CONTAINER_NAME -v VOLUMEDIR IMAGE_NAME
[root@localhost ~]# docker run -dit --name b1 -v /data busybox
3027832f86c18ff9269235c3cb9219a5cfee4a9a8a532a37a5ad22bf5f9160fd
# 进入容器并在/data下创建一个a.txt的文件
[root@localhost ~]# docker exec -it b1 /bin/sh
/ # ls
bin data dev etc home proc root sys tmp usr var
/ # cd data
/data # echo 'haihaihai' > a.txt
/data # cat a.txt
haihaihai
# 另起一个终端进入b1容器查看刚刚创建的a.txt
[root@localhost ~]# docker inspect b1
"Mounts": [
{
"Type": "volume",
"Name": "838f6b862407a4e2d33cafce7a58f89fa8a9438f4aaece0cacc2643c7bd2c6c4",
"Source": "/var/lib/docker/volumes/838f6b862407a4e2d33cafce7a58f89fa8a9438f4aaece0cacc2643c7bd2c6c4/_data", // 找到文件存放位置
"Destination": "/data",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
[root@localhost ~]# cd /var/lib/docker/volumes/838f6b862407a4e2d33cafce7a58f89fa8a9438f4aaece0cacc2643c7bd2c6c4/_data
[root@localhost _data]# ls
a.txt
[root@localhost _data]# cat a.txt
haihaihai
绑定挂载卷
docker run -it --name CONTAINER_NAME -v HOSTDIR:VOLUMEDIR IMAGE_NAME
```cpp
# 加载宿主机的/opt/dtat目录到容器的/data目录
[root@localhost ~]# docker run -dit --name b2 -v /opt/dtat:/data busybox
ef3e6b22396e9afd117a799ade885a0b5b68b3e0d3a11d90621adaf025bd1ff1
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ef3e6b22396e busybox "sh" 6 seconds ago Up 5 seconds b2
3027832f86c1 busybox "sh" 25 minutes ago Up 25 minutes b1
b5661ce9d25b httpd "httpd-foreground" 16 hours ago Up 45 minutes 80/tcp web
# 进入b2容器里面的/data目录下创建一个文件一个目录
[root@localhost ~]# docker exec -it b2 /bin/sh
/ # cd data
/data # touch a
/data # mkdir b
/data # ls
a b
# 另起一个终端在宿主机上查看刚刚在b2容器里面创建的文件和目录,并在宿主机的/opt/dtat目录下也创建文件
[root@localhost dtat]# ls
a b
[root@localhost dtat]# touch c d
[root@localhost dtat]# ls
a b c d
# 返回b2容器查看
/data # ls
a b c d
//这里宿主机的/opt/dtat和容器的/data目录可以事先存在也可以不存在
7. 在容器中使用数据卷
- 在容器内创建一个数据卷
# 使用httpd镜像创建一个v2容器,并创建一个数据卷挂载到容器的/dtat目录下
[root@localhost ~]# docker run -dit -P --name v2 -v /data httpd
04af3dd4de1cde9d4abf8118abdd92a18c7383670c1515f268b546b52f8e4bcf
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
04af3dd4de1c httpd "httpd-foreground" 3 seconds ago Up 1 second 0.0.0.0:49153->80/tcp, :::49153->80/tcp v2
04ffcc991c83 busybox "sh" 55 seconds ago Up 53 seconds v1
3027832f86c1 busybox "sh" 56 minutes ago Up 56 minutes b1
b5661ce9d25b httpd "httpd-foreground" 16 hours ago Up About an hour 80/tcp web
[root@localhost ~]# ss -antl
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:49153 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 [::]:49153 [::]:*
LISTEN 0 128 *:2375 *:*
LISTEN 0 128 [::]:22 [::]:*
挂载一个主机目录作为数据卷
[root@localhost ~]# docker run -dit --name web1 -P -v /opt/web1:/web1 httpd
1ea5f104d4619c8061360db50f82e2c7a14b8e187abeabd72c90a187a7d1a26f
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1ea5f104d461 httpd "httpd-foreground" 10 seconds ago Up 10 seconds 0.0.0.0:49154->80/tcp, :::49154->80/tcp web1
这个功能在进行测试的时候非常方便,比如用户可以放置一些程序或数据到本地目录中,然后在容器内运行和使用。另外,本地目录的路径必须是绝对路径,如果目录不存在,Docker会自动创建
Docker挂载数据卷的默认权限是读写(rw),用户也可以通过(ro)指定为只读
[root@localhost ~]# docker run -dit --name web1 -v /opt/web1:/usr/local/apache2/htdocs:ro httpd
de030d79824865de84ed0f28a6b6bb29f1038969baf288d07120662d62ef0228
[root@localhost ~]# cd /opt/web1
[root@localhost web1]# echo 'hiahiahia' > index.html
[root@localhost web1]# cat index.html
hiahiahia
# 进入到容器里
[root@localhost ~]# docker exec -it web1 /bin/bash
root@de030d798248:/usr/local/apache2# cd htdocs/
root@de030d798248:/usr/local/apache2/htdocs# ls
index.html
root@de030d798248:/usr/local/apache2/htdocs# cat index.html
hiahiahia
root@de030d798248:/usr/local/apache2/htdocs# rm -rf index.html
rm: cannot remove 'index.html': Read-only file system
//因为设置了只读权限所以容器不可以删除index.html
挂载一个本地主机文件作为数据卷
-v选项也可以从主机挂载单个文件到容器中作为数据卷:
[root@localhost ~]# docker run -dit --name web1 -v ~/.bash_history:/.bash_history httpd
e59f92434ee4108ba24bc3af37dd8c663673da2e0fe7cf915a0e143a802a8026
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e59f92434ee4 httpd "httpd-foreground" 23 seconds ago Up 23 seconds 80/tcp web1
[root@localhost ~]# cd /opt/web1
[root@localhost web1]# history
···
····
130 cd /opt/web1
131 history
如果直接挂载一个文件到容器,使用文件编辑工具,包括vi或者sed去修改文件内容的时候,可能会造成inode的改变,这样将会导致错误。所以推荐的方式是直接挂载文件所在的目录。
8. 数据卷容器
- 创建一个数据卷容器aaa,并在其中创建一个数据卷挂载到/aaa
[root@localhost ~]# docker run -it -d --name aaa -v /aaa centos
2826a312be313e6d78dc7a022815894f826ed0be21bc5663f4313e739e8f19d8
- 可以多次使用–volumes-from参数来从多个容器挂载多个数据卷。还可以从其他已挂载了容器卷的容器来挂载数据卷例如创建db1和db2两个容器例如创建db1和db2两个容器并从aaa容器挂载数据卷:
[root@localhost ~]# docker run -dit --name db1 --volumes-from aaa centos
4f00547982855434db0042757a24cb474f29dcf70532b97f8a9a8ac5b5a6d77b
[root@localhost ~]# docker run -dit --name db2 --volumes-from aaa centos
f4265f3685bfb62fbd7f7517cb47df132a7b7d829ce9f6fbe4ec6c2fa9ce3a5b
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f4265f3685bf centos "/bin/bash" 9 seconds ago Up 6 seconds db2
4f0054798285 centos "/bin/bash" 3 minutes ago Up 3 minutes db1
2826a312be31 centos "/bin/bash" 4 minutes ago Up 4 minutes aaa
此时,容器db1和db2都挂载同一个数据卷到相同的/dbdata目录。三个容器任何一方在该目录下的写入,其他容器都可以看到。
# 进入容器 在db1容器中创建一个test文件:
[root@localhost ~]# docker exec -it db1 /bin/bash
[root@4f0054798285 /]# ls
aaa bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
[root@4f0054798285 /]# cd aaa
[root@4f0054798285 aaa]# ls
[root@4f0054798285 aaa]# touch test
[root@4f0054798285 aaa]# ls
test
# 在db2上查看
[root@localhost ~]# docker exec -it db2 /bin/bash
[root@f4265f3685bf /]# ls
aaa bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
[root@f4265f3685bf /]# cd aaa
[root@f4265f3685bf aaa]# ls
test
可以多次使用–volumes-from参数来从多个容器挂载多个数据卷。还可以从其他已挂载了容器卷的容器来挂载数据卷:
[root@localhost ~]# docker run -dit --name db3 --volumes-from db1 centos
4c86c29fd7839b7e677a55a5afccee3ac876bc379b7d4804879ad556fcb937e5
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4c86c29fd783 centos "/bin/bash" 5 seconds ago Up 3 seconds db3
f4265f3685bf centos "/bin/bash" 12 minutes ago Up 12 minutes db2
4f0054798285 centos "/bin/bash" 16 minutes ago Up 16 minutes db1
2826a312be31 centos "/bin/bash" 16 minutes ago Up 16 minutes aaa
[root@localhost ~]# docker exec -it db3 /bin/bash
[root@4c86c29fd783 /]# ls --color
aaa bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
[root@4c86c29fd783 /]# cd aaa/
[root@4c86c29fd783 aaa]# ls
test
使用–volumes-from参数所挂载数据卷的容器自身并不需要保持在运行状态。
如果删除了挂载的容器(包括dbdata、db1和db2),数据卷并不会被自动删除。如果要删除一个数据卷,必须在删除最后一个还挂载着它的容器时显式使用docker rm -v命令来指定同时删除关联的容器。
9. 利用数据卷容器迁移数据
使用下面的命令来备份aaa数据卷容器内的数据卷:
[root@localhost ~]# docker run --name worker --volumes-from aaa -v $(pwd):/backup centos tar cvf /backup/backup.tar /aaa
tar: Removing leading `/' from member names
/aaa/
/aaa/test
/aaa/qqq/
[root@localhost ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3810814f67ad centos "tar cvf /backup/bac…" 48 seconds ago Exited (0) 46 seconds ago worker
7d6f3031cab2 centos "cvf /backup/backup.…" About a minute ago Created qqq
4c86c29fd783 centos "/bin/bash" 6 minutes ago Up 6 minutes db3
f4265f3685bf centos "/bin/bash" 19 minutes ago Up 19 minutes db2
4f0054798285 centos "/bin/bash" 22 minutes ago Up 22 minutes db1
2826a312be31 centos "/bin/bash" 23 minutes ago Up 23 minutes aaa
baf246b5d823 httpd "httpd-foreground" 18 hours ago Exited (0) 18 hours ago w1
[root@localhost ~]# ls
anaconda-ks.cfg backup.tar passwd
首先利用centos镜像创建了一个容器worker。使用–volumes-from dbdata参数来让worker容器挂载aaa容器的数据卷(即aaa数据卷);使用-v $(pwd):/backup参数来挂载本地的当前目录到worker容器的/backup目录。
worker容器启动后,使用了tar cvf /backup/backup.tar /aaa命令来将/aaa下内容备份为容器内的/backup/backup.tar,即宿主主机当前目录下的backup.tar。
10. 恢复
如果要恢复数据到一个容器,可以按照下面的操作。首先创建一个带有数据卷的容器aaa2
[root@localhost ~]# docker run -it --name aaa2 -v /aaa centos /bin/bash
[root@ff3b38ac6b6a /]#
创建另一个新的容器,挂载aaa2容器,并使用untar解压备份文件到所挂载的容器卷中即可
[root@localhost ~]# docker run --name aaa3 --volumes-from aaa2 -v $(pwd):/backup centos tar xf /backup/backup.tar
[root@localhost ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7c4ed4229d48 centos "tar xf /backup/back…" 18 seconds ago Exited (0) 15 seconds ago aaa3
ff3b38ac6b6a centos "/bin/bash" 4 minutes ago Up 4 minutes aaa2
3810814f67ad centos "tar cvf /backup/bac…" 9 minutes ago Exited (0) 9 minutes ago worker
7d6f3031cab2 centos "cvf /backup/backup.…" 10 minutes ago Created qqq
4c86c29fd783 centos "/bin/bash" 15 minutes ago Up 15 minutes db3
f4265f3685bf centos "/bin/bash" 28 minutes ago Up 28 minutes db2
4f0054798285 centos "/bin/bash" 31 minutes ago Up 31 minutes db1
2826a312be31 centos "/bin/bash" 32 minutes ago Up 32 minutes aaa
baf246b5d823 httpd "httpd-foreground" 18 hours ago Exited (0) 18 hours ago w1
到aaa2容器查看/aaa里面的内容
[root@localhost ~]# docker run -it --name aaa2 -v /aaa centos /bin/bash
[root@ff3b38ac6b6a /]#
[root@ff3b38ac6b6a /]# cd aaa
[root@ff3b38ac6b6a aaa]# ls
qqq test