基于底层存储实现,Docker
提供了三种适用于不同场景的文件系统挂载方式:Bind Mount
、Volume
和 Tmpfs Mount
。
Bind Mount
能够直接将宿主操作系统中的目录和文件挂载到容器内的文件系统中,通过指定容器外的路径和容器内的路径,就可以形成挂载映射关系,在容器内外对文件的读写,都是相互可见的。
Volume
也是从宿主操作系统中挂载目录到容器内,只不过这个挂载的目录由 Docker
进行管理,我们只需要指定容器内的目录,不需要关心具体挂载到了宿主操作系统中的哪里。
Tmpfs Mount
支持挂载系统内存中的一部分到容器的文件系统里,不过由于内存和容器的特征,它的存储并不是持久的,其中的内容会随着容器的停止而消失。
要将宿主操作系统中的目录挂载到容器之后,我们可以在容器创建的时候通过传递 -v
或 --volume
选项来指定内外挂载的对应目录或文件。
docker run -d --name nginx -v /webapp/html:/usr/share/nginx/html nginx:1.12
使用 -v
或 --volume
来挂载宿主操作系统目录的形式是 -v
或 --volume
,其中
host-path
代表宿主操作系统中的目录;container-path
代表容器中的目录;这里需要注意的是,为了避免混淆,
Docker
这里强制定义目录时必须使用绝对路径,不能使用相对路径。
在 docker inspect
的结果里,我们可以看到有关容器数据挂载相关的信息。
$ docker inspect nginx
[
{
## ......
"Mounts": [
{
"Type": "bind",
"Source": "/webapp/html",
"Destination": "/usr/share/nginx/html",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
## ......
}
]
实际操作中,Docker
还支持以只读的方式挂载,通过只读方式挂载的目录和文件,只能被容器中的程序读取,但不接受容器中程序修改它们的请求。在挂载选项 -v
后再接上 :ro
就可以只读挂载了。
docker run -d --name nginx -v /webapp/html:/usr/share/nginx/html:ro nginx:1.12
使用 -v 标记也可以指定挂载一个本地的已有目录到容器中去作为数据卷 (推荐方式)。
wohu@iZ:~/docker/code$ docker run -d -P -i -t --name python -v /home/wohu/docker/code:/opt ubuntu:16.04
c2e114c09291abaa2c3bbd9c7318a317749ab851511d0cdd57484a7c128a19cc
wohu@iZm:~/docker/code$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c2e114c09291 ubuntu:16.04 "/bin/bash" 6 seconds ago Up 5 seconds python
wohu@iZ:~/docker/code$ docker exec -ti c2 /bin/bash
root@c2e114c09291:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@c2e114c09291:/# pwd
/
root@c2e114c09291:/# cd /opt/
root@c2e114c09291:/opt# ls
test.py
root@c2e114c09291:/opt# python test.py
bash: python: command not found
root@c2e114c09291:/opt# cat test.py
#!/usr/bin/env python
print "this is test py file"
root@c2e114c09291:/opt# exit
exit
wohu@iZ:~/docker/code$ cat test.py
#!/usr/bin/env python
print "this is test py file"
# 主机环境下对 test.py 文件进行修改
wohu@iZ:~/docker/code$ echo "print 'new containers'" >> test.py
wohu@iZ:~/docker/code$ cat test.py
#!/usr/bin/env python
print "this is test py file"
print 'new containers'
# 进入容器查看该文件是否被修改?
wohu@iZ:~/docker/code$ docker exec -it c2 /bin/bash
root@c2e114c09291:/# cd /opt/
root@c2e114c09291:/opt# ls
test.py
root@c2e114c09291:/opt# cat test.py
#!/usr/bin/env python
print "this is test py file"
print 'new containers' # 该文件已经被修改
root@c2e114c09291:/opt# exit
exit
wohu@iZ:~/docker/code$
上面的命令加载主机的 /home/wohu/docker/code
目录到容器的 /opt
目录。这个功能在进行测试的时候十分方便,比如用户可以将一些程序或数据放到本地目录中,然后在容器内运行和使用。另外,本地目录的路径必须是绝对路径,如果目录不存在 Docker
会自动创建。
Docker 挂载数据卷的默认权限是读写(rw
),用户也可以通过 ro
指定为只读:
docker run -d -P -i -t --name python -v /home/xwr/docker/code:/opt:ro ubuntu:16.04
加了 :ro
之后,容器内对所挂载数据卷内的数据就无法修改了。
由于宿主操作系统文件挂载在权限允许的情况下能够挂载任何目录或文件,这给系统的安全性造成了一定的隐患,所以我们在使用 Bind Mount 的时候,一定要特别注意挂载的外部目录选择。当然,在保证安全性的前提下,有几种常见场景非常适合使用这种挂载方式。
-v
标记也可以从主机挂载单个文件到容器中(不推荐) 。
docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash
这样就可以记录在容器输入过的命令历史了。注意如果直接挂载一个文件到容器,使用文件编辑工具,包括 vi
或者 sed--in-place
的时候,可能会造成文件 inode
的改变,从 Docker 1.1.0
起 这会导致报错误信息。
所以推荐的方式是直接挂载文件所在的目录。
Tmpfs Mount
是一种特殊的挂载方式,它主要利用内存来存储数据。由于内存不是持久性存储设备,所以其带给 Tmpfs Mount
的特征就是临时性挂载。
与挂载宿主操作系统目录或文件不同,挂载临时文件目录要通过 --tmpfs
这个选项来完成。由于内存的具体位置不需要我们来指定,这个选项里我们只需要传递挂载到容器内的目录即可。
docker run -d --name webapp --tmpfs /webapp/cache webapp:latest
容器已挂载的临时文件目录我们也可以通过 docker inspect
命令查看。
$ docker inspect webapp
[
{
## ......
"Tmpfs": {
"/webapp/cache": ""
},
## ......
}
]
挂载临时文件首先要注意它不是持久存储这一特性,在此基础上,它有几种常见的适应场景。
应用中使用到,但不需要进行持久保存的敏感数据,可以借助内存的非持久性和程序隔离性进行一定的安全保障。
读写速度要求较高,数据变化量大,但不需要持久保存的数据,可以借助内存的高读写速度减少操作的时间。
容器中管理数据主要有两种方式:
Data Volumes
):容器内数据直接映射到本地主机环境;Data Volume Containers
):使用特定容器维护数据卷。数据卷的本质其实依然是宿主操作系统上的一个目录,只不过这个目录存放在 Docker
内部,接受 Docker
的管理。
数据卷是一个可供容器使用的特殊目录,它将主机操作系统目录直接映射进容器,类似于 Linux
中的 mount
操作。
数据卷有如下特性:
在使用数据卷进行挂载时,我们不需要知道数据具体存储在了宿主操作系统的何处,只需要给定容器中的哪个目录会被挂载即可。
我们依然可以使用 -v
或 --volume
选项来定义数据卷的挂载。在用 docker run
命令的时候,使用 -v
标记可以在容器内创建一个数据卷。多次重复使用 -v
标记可以创建多个数据卷。
docker run -d --name webapp -v /webapp/storage webapp:latest
数据卷挂载到容器后,我们可以通过 docker inspect
看到容器中数据卷挂载的信息。
$ docker inspect webapp
[
{
## ......
"Mounts": [
{
"Type": "volume",
"Name": "2bbd2719b81fbe030e6f446243386d763ef25879ec82bb60c9be7ef7f3a25336",
"Source": "/var/lib/docker/volumes/2bbd2719b81fbe030e6f446243386d763ef25879ec82bb60c9be7ef7f3a25336/_data",
"Destination": "/webapp/storage",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
## ......
}
]
其中 Source
是 Docker
为我们分配用于挂载的宿主机目录,其位于 Docker
的资源区域 ( 这里是默认的 /var/lib/docker
) 内。当然,我们并不需要关心这个目录,一切对它的管理都已经在 Docker
内实现了。
为了方便识别数据卷,我们可以像命名容器一样为数据卷命名,这里的 Name
就是数据卷的命名。在我们未给出数据卷命名的时候,Docker
会采用数据卷的 ID
命名数据卷。我们也可以通过 -v
这种形式来命名数据卷。
docker run -d --name webapp -v appdata:/webapp/storage webapp:latest
由于 -v
选项既承载了 Bind Mount
的定义,又参与了 Volume
的定义,所以其传参方式需要特别留意。前面提到了,-v
在定义绑定挂载时必须使用绝对路径,其目的主要是为了避免与数据卷挂载中命名这种形式的冲突。
Volume Mount
与 Bind Mount
的区别在于数据卷 Volume Mount
可以在多个容器之间共享。
下面使用 ubuntu:16.04 镜像创建一个 web 容器,并创建一个数据卷挂载到容器的 /webapp 目录:
wohu@iZ:~/docker$ docker run -d -P --name python -it -v /opt ubuntu:16.04
6798b1ee4ea73622dfcdaf7b2bdab8844da4d527399229417f424e755b5be11d
wohu@iZ:~/docker$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6798b1ee4ea7 ubuntu:16.04 "/bin/bash" 7 seconds ago Up 6 seconds python
wohu@iZ:~/docker$ docker exec -it 67 /bin/bash
root@6798b1ee4ea7:/# pwd
/
root@6798b1ee4ea7:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@6798b1ee4ea7:/# exit
exit
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6798b1ee4ea7 ubuntu:16.04 "/bin/bash" 52 seconds ago Up 51 seconds python
-P
是将容器服务暴露的端口,是自动映射到本地主机的临时端口。
由于数据卷的命名在 Docker
中是唯一的,所以我们很容易通过数据卷的名称确定数据卷,这就让我们很方便的让多个容器挂载同一个数据卷了。
$ sudo docker run -d --name webapp -v html:/webapp/html webapp:latest
$ sudo docker run -d --name nginx -v html:/usr/share/nginx/html:ro nginx:1.12
我们使用 -v
选项挂载数据卷时,如果数据卷不存在,Docker
会为我们自动创建和分配宿主操作系统的目录,而如果同名数据卷已经存在,则会直接引用。
如果有朋友觉得这样对数据卷的操作方式还不够直接和准确,我们还可以通过 docker volume
下的几个命令专门操作数据卷。
通过 docker volume create
我们可以不依赖于容器独立创建数据卷。
$ sudo docker volume create appdata
通过 docker volume ls
可以列出当前已创建的数据卷。
$ sudo docker volume ls
DRIVER VOLUME NAME
local html
local appdata
我们可以直接通过 docker volume rm
来删除指定的数据卷。
docker volume rm appdata
在删除数据卷之前,我们必须保证数据卷没有被任何容器所使用 ( 也就是之前引用过这个数据卷的容器都已经删除 ),否则 Docker
不会允许我们删除这个数据卷。
在 docker rm 删除容器的命令中,我们可以通过增加 -v 选项来删除容器关联的数据卷。
docker rm -v webapp
如果我们没有随容器删除这些数据卷,Docker
在创建新的容器时也不会启用它们,即使它们与新创建容器所定义的数据卷有完全一致的特征。也就是说,此时它们已经变成了孤魂野鬼,纯粹的占用着硬盘空间而又不受管理。
此时我们可以通过 docker volume rm
来删除它们,但前提时你能在一堆乱码般的数据卷 ID 中找出哪个是没有被容器引用的数据卷。
为此,Docker
向我们提供了 docker volume prune
这个命令,它可以删除那些没有被容器引用的数据卷。
$ docker volume prune -f
Deleted Volumes:
af6459286b5ce42bb5f205d0d323ac11ce8b8d9df4c65909ddc2feea7c3d1d53
0783665df434533f6b53afe3d9decfa791929570913c7aff10f302c17ed1a389
65b822e27d0be93d149304afb1515f8111344da9ea18adc3b3a34bddd2b243c7
## ......
如果用户需要在多个容器之间共享一些持续更新的数据,最简单的方式是使用数据卷容器。数据卷容器也是一个容器,但是它的目的是专门用来提供数据卷供其他容器挂载。
首先,创建一个数据卷容器 dbdata
,并在其中创建一个数据卷挂载到 /dbdata
(没有该目录会自动创建),并在其中创建一个dbdata.txt
文件。
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker/code$ docker run -ti --name dbdata -v /dbdata ubuntu:16.04
root@531dabd7eaa8:/# cd /d
dbdata/ dev/
root@531dabd7eaa8:/# cd /dbdata/
root@531dabd7eaa8:/dbdata# touch dbdata.txt
root@531dabd7eaa8:/dbdata# echo "this is dbdata.txt" > dbdata.txt
root@531dabd7eaa8:/dbdata# exit
exit
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker/code$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
531dabd7eaa8 ubuntu:16.04 "/bin/bash" 50 seconds ago Exited (0) 4 seconds ago dbdata
c2e114c09291 ubuntu:16.04 "/bin/bash" 17 minutes ago Up 17 minutes python
然后,可以在其他容器中使用 --volumes-from
来挂载 dbdata
容器中的数据卷,例如创建 db1
和 db2
两个容器,并从 dbdata
容器挂载数据卷:
$ docker run -it --volumes-from dbdata --name db1 ubuntu
$ docker run -it --volumes-from dbdata --name db2 ubuntu
此时,容器 db1
和 db2
都挂载同一个数据卷到相同的 /dbdata
目录。三个容器任何一方在该目录下的写入,其他容器都可以看到。
例如,在 dbdata
容器中创建一个 test
文 件,如下所示:
wohu@iZ:~/docker/code$ docker run -ti --name db1 --volumes-from dbdata ubuntu:16.04
root@eb16116ec6ae:/# ls
bin boot dbdata dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@eb16116ec6ae:/# cd dbdata/
root@eb16116ec6ae:/dbdata# ls
dbdata.txt
root@eb16116ec6ae:/dbdata# cat dbdata.txt
this is dbdata.txt
root@eb16116ec6ae:/dbdata# exit
exit
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker/code$ docker run -ti --name db2 --volumes-from dbdata ubuntu:16.04
root@c178579dcdb2:/# ls
bin boot dbdata dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@c178579dcdb2:/# cd dbdata/
root@c178579dcdb2:/dbdata# ls
dbdata.txt
root@c178579dcdb2:/dbdata# cat dbdata.txt
this is dbdata.txt
root@c178579dcdb2:/dbdata# exit
exit
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker/code$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c178579dcdb2 ubuntu:16.04 "/bin/bash" 25 seconds ago Exited (0) 7 seconds ago db2
eb16116ec6ae ubuntu:16.04 "/bin/bash" 57 seconds ago Exited (0) 41 seconds ago db1
531dabd7eaa8 ubuntu:16.04 "/bin/bash" 4 minutes ago Exited (0) 3 minutes ago dbdata
c2e114c09291 ubuntu:16.04 "/bin/bash" 21 minutes ago Up 20 minutes python
wohu@iZm5egn5zptnov4j3oxh4fZ:~/docker/code$
可以多次使用 --volumes-from
参数来从多个容器挂载多个数据卷。还可以从其他已经挂载了容器卷的容器来挂载数据卷。
注意:使用
--volumes-from
参数所挂载数据卷的容器自身并不需要保持在运行状态。如果删除了挂载的容器 (包括 dbdata、db1 和 db2),数据卷并不会被自动删除。如果要删除一个数据卷,必须在删除最后一个还挂载着它的容器时显式使用docker rm -v
命令来指定同时删除关联的容器。
可以利用数据卷容器对其中的数据卷进行备份、恢复、以实现数据的迁移。
使用下面的命令来备份 dbdata
数据卷容器内的数据卷:
docker run --volumes-from dbdata -v (pwd):/backup --name worker ubuntu tar
cvf /backup/backup.tar /dbdata
首先利用 ubuntu
镜像创建了一个容器 worker
。使用 --volumes-from dbdata
参数来让 worker
容器挂载 dbdata
容器的数据卷(即 dbdata
数据卷)。
使用 -v$(pwd):/backup
参数来挂载本地的当前目录到 worker
容器的 /backup
目录。worker
容器启动后,使用了 tar cvf /backup/backup.tar /dbdata
命令来将 /dbdata
下内容备份为容器内的 /backup/backup.tar
,即宿主主机当前目录下的 backup.tar
。
$ sudo docker run --rm --volumes-from appdata -v /backup:/backup ubuntu tar cvf /backup/backup.tar /webapp/storage
通过 --rm
选项,我们可以让容器在停止后自动删除,而不需要我们再使用容器删除命令来删除它,这对于我们使用一些临时容器很有帮助。
如果要将数据恢复到一个容器,可以按照下面的步骤操作。首先创建一个带有数据卷的容器 dbdata2
docker run -v /dbdata --name dbdata2 ubuntu /bin/bash
然后创建另一个新的容器,挂载 dbdata2 的容器,并使用 untar 解压备份文件到所挂载的容器卷中。
docker run --volumes-from dbdata2 -v (pwd):/backup busybox tar xvf /backup/backup.tar
或者下面命令
$ docker run --rm --volumes-from appdata -v /backup:/backup ubuntu tar xvf /backup/backup.tar -C /webapp/storage --strip