跟我一起学Docker——Volume

转自

http://www.binss.me/blog/learn-docker-with-me-about-volume/

在使用Docker的过程中,常常需要通过Volume来挂载目录。可以通过以下两种方式创建Volume:

在Dockerfile中指定VOLUME /some/dir
生成容器时执行docker run -v /some/dir命令来指定
有个问题我纠结了很久:Dockerfile中使用VOLUME指令挂载目录和docker run时通过-v参数指定挂载目录有什么区别?

区别
根据官方文档,Dockerfile生成目标镜像的过程就是不断docker run + docker commit的过程,当Dockerfile执行到VOLUME /some/dir(这里为/var/lib/mysql)这一行时,输出

Step 6 : VOLUME /var/lib/mysql
 ---> Running in 0c842ec90849
 ---> 214e3dccd0f2

在这一步,docker生成了临时容器0c842ec90849,然后commit容器得到镜像214e3dccd0f2。因此VOLUME /var/lib/mysql是通过docker run -v /var/lib/mysql,即第二种方式来实现的,随后由于容器的提交,该配置被保存到了镜像214e3dccd0f2中,通过inspcet可以查看到:

"Volumes": {
    "/var/lib/mysql": {},
}

通过inspect该镜像生成的容器可以发现挂载配置:

"Mounts": [
    {
        "Name": "8827c361d103c1272907da0b82268310415f8b075b67854f27dbca0b59a31a1a",
        "Source": "/mnt/sda1/var/lib/docker/volumes/8827c361d103c1272907da0b82268310415f8b075b67854f27dbca0b59a31a1a/_data",
        "Destination": "/var/lib/mysql",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
    }
]

由于没有指定挂载到的宿主机目录,因此会默认挂载到宿主机的/var/lib/docker/volumes下的一个随机名称的目录下,在这里为/mnt/sda1/var/lib/docker/volumes/8827c361d103c1272907da0b82268310415f8b075b67854f27dbca0b59a31a1a/_data。因此Dockerfile中使用VOLUME指令挂载目录和docker run时通过-v参数指定挂载目录的区别在于,run的-v可以指定挂载到宿主机的哪个目录,而Dockerfile的VOLUME不能,其挂载目录由docker随机生成。

若指定了宿主机目录,比如:

docker run --name mysql -v ~/volume/mysql/data:/var/lib/mysql -d mysql:5.7

那么inspect如下:

"Mounts": [
    {
        "Source": "/Users/binss/volume/mysql/data",
        "Destination": "/var/lib/mysql",
        "Mode": "",
        "RW": true,
        "Propagation": "rprivate"
    }
]

这里将/var/lib/mysql挂载到宿主机的/Users/binss/volume/mysql/data目录下,而不再是默认的/var/lib/docker/volumes目录。这样做有什么好处呢?我们知道,/var/lib/mysql是mysql在linux下默认的数据存储目录。将该目录挂载到宿主机,可以使数据在容器被移除时得以保留,而不会随着容器go die。下次新建mysql容器,只需同样挂载到/Users/binss/volume/mysql/data,即可复用原有数据。

有童鞋可能会问,那VOLUME /some/dir和不指定宿主机目录的docker run -v /some/dir有什么用?我们不可能为了复用该目录的数据,先inspect找到目录在哪,然后将新容器挂载上去,多麻烦呀。

查阅官方文档,发现还有另外一种挂载方式:

docker run --name mysql2 --volumes-from mysql -d mysql:5.7
docker inspect mysql2发现,mysql2的/var/lib/mysql和mysql的/var/lib/mysql都挂载到了相同的目录下,因此在mysql2中可以复用mysql的数据:

"Mounts": [
    {
        "Name": "8827c361d103c1272907da0b82268310415f8b075b67854f27dbca0b59a31a1a",
        "Source": "/mnt/sda1/var/lib/docker/volumes/8827c361d103c1272907da0b82268310415f8b075b67854f27dbca0b59a31a1a/_data",
        "Destination": "/var/lib/mysql",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
    }
]

值得注意的是,虽然/var/lib/mysql没有在run的指令中出现,但其出现在了生成mysql:5.7镜像的Dockerfile中,所以即使在run时忘记使用volume,该目录依然能够从宿主机直接访问,官方真是用心良苦啊。另外,由于复用了mysql的所有数据,因此连接数据库用户名密码也和mysql一样,而不会是run时传入的参数,一切都和原来mysql中的一样。

备份与恢复
既然有了—volumes-from,那么备份也不成问题。

docker run --rm --volumes-from mysql -v $(pwd):/backup mysql:5.7 tar cvf /backup/backup.tar /var/lib/mysql

该指令将mysql容器的/var/lib/mysql目录打包成backup.tar,存放在宿主机的当前目录下。实现方法很简单,创建一个新容器,通过—volumes-from将新容器的/var/lib/mysql挂载到mysql的相同目录下,同时又将宿主机的当前目录挂载到容器的/backup目录下,然后通过tar将/var/lib/mysql打包存放到容器的/backup目录下,这样宿主机的当前目录就得到了打包文件backup.tar。以上流程完成后,容器由于设置了—rm而被删除,只留下backup.tar,毫无痕迹地备份了容器mysql的数据文件。

恢复时,只需执行以下命令:

docker run --rm --volumes-from mysql -v $(pwd):/backup mysql:5.7 bash -c "cd /var/lib/mysql && tar xvf /backup.tar --strip 1"
同样是创建了一个临时容器,将刚才打包backup.tar解压到mysql容器的/var/lib/mysql中。因为backup.tar解压出来是backup文件夹,所以通过—strip 1跳过这一级目录。

移除无用的挂载目录
由于poll下来的镜像常常都设置了VOLUME指令,所以如果我们创建容器时没有为其指定宿主机挂载目录,如前文所述,会在/var/lib/docker/volumes目录下生成挂载目录。而在删除容器时,如果忘记使用-v,则该挂载目录会残留在宿主机中,占用硬盘空间。例如/var/lib/mysql文件夹,一个就好几百M。

通过以下指令可以移除这些残余的无用挂载目录:

docker volume rm $(docker volume ls -qf dangling=true)

总结
通过VOLUME,我们得以绕过docker的Union File System,从而直接对宿主机的目录进行直接读写,实现了容器内数据的持久化和共享化。

# Docker 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×