Docker 入门系列(4)- Docker 数据管理(挂载目录、挂载文件、数据卷挂载、数据卷共享、数据卷删除、数据卷容器备份和恢复)

基于底层存储实现,Docker 提供了三种适用于不同场景的文件系统挂载方式:Bind MountVolumeTmpfs Mount

Docker 入门系列(4)- Docker 数据管理(挂载目录、挂载文件、数据卷挂载、数据卷共享、数据卷删除、数据卷容器备份和恢复)_第1张图片

  • Bind Mount 能够直接将宿主操作系统中的目录和文件挂载到容器内的文件系统中,通过指定容器外的路径和容器内的路径,就可以形成挂载映射关系,在容器内外对文件的读写,都是相互可见的。

  • Volume 也是从宿主操作系统中挂载目录到容器内,只不过这个挂载的目录由 Docker 进行管理,我们只需要指定容器内的目录,不需要关心具体挂载到了宿主操作系统中的哪里。

  • Tmpfs Mount 支持挂载系统内存中的一部分到容器的文件系统里,不过由于内存和容器的特征,它的存储并不是持久的,其中的内容会随着容器的停止而消失。

1. Bind Mount

1.1 挂载目录到容器

要将宿主操作系统中的目录挂载到容器之后,我们可以在容器创建的时候通过传递 -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 的时候,一定要特别注意挂载的外部目录选择。当然,在保证安全性的前提下,有几种常见场景非常适合使用这种挂载方式。

  • 当我们需要从宿主操作系统共享配置的时候。对于一些配置项,我们可以直接从容器外部挂载到容器中,这利于保证容器中的配置为我们所确认的值,也方便我们对配置进行监控。例如,遇到容器中时区不正确的时候,我们可以直接将操作系统的时区配置,也就是 /etc/timezone 这个文件挂载并覆盖容器中的时区配置。
  • 当我们需要借助 Docker 进行开发的时候。虽然在 Docker 中,推崇直接将代码和配置打包进镜像,以便快速部署和快速重建。但这在开发过程中显然非常不方便,因为每次构建镜像需要耗费一定的时间,这些时间积少成多,就是对开发工作效率的严重浪费了。如果我们直接把代码挂载进入容器,那么我们每次对代码的修改都可以直接在容器外部进行。

1.2 挂载文件到容器

-v 标记也可以从主机挂载单个文件到容器中(不推荐) 。

docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash

这样就可以记录在容器输入过的命令历史了。注意如果直接挂载一个文件到容器,使用文件编辑工具,包括 vi 或者 sed--in-place 的时候,可能会造成文件 inode 的改变,从 Docker 1.1.0 起 这会导致报错误信息。

所以推荐的方式是直接挂载文件所在的目录。

2. Tmpfs Mount

Tmpfs Mount 是一种特殊的挂载方式,它主要利用内存来存储数据。由于内存不是持久性存储设备,所以其带给 Tmpfs Mount 的特征就是临时性挂载。

与挂载宿主操作系统目录或文件不同,挂载临时文件目录要通过 --tmpfs 这个选项来完成。由于内存的具体位置不需要我们来指定,这个选项里我们只需要传递挂载到容器内的目录即可。

docker run -d --name webapp --tmpfs /webapp/cache webapp:latest

容器已挂载的临时文件目录我们也可以通过 docker inspect 命令查看。

$ docker inspect webapp
[
    {
## ......
         "Tmpfs": {
            "/webapp/cache": ""
        },
## ......
    }
]

挂载临时文件首先要注意它不是持久存储这一特性,在此基础上,它有几种常见的适应场景。

  • 应用中使用到,但不需要进行持久保存的敏感数据,可以借助内存的非持久性和程序隔离性进行一定的安全保障。

  • 读写速度要求较高,数据变化量大,但不需要持久保存的数据,可以借助内存的高读写速度减少操作的时间。

3. Volume mount

容器中管理数据主要有两种方式:

  • 数据卷 (Data Volumes):容器内数据直接映射到本地主机环境;
  • 数据卷容器 (Data Volume Containers):使用特定容器维护数据卷。

3.1 数据卷挂载

数据卷的本质其实依然是宿主操作系统上的一个目录,只不过这个目录存放在 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": ""
            }
        ],
## ......
    }
]

其中 SourceDocker 为我们分配用于挂载的宿主机目录,其位于 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 MountBind 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 是将容器服务暴露的端口,是自动映射到本地主机的临时端口。

3.2 数据卷共享

由于数据卷的命名在 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

3.3 删除数据卷

我们可以直接通过 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
## ......

4. 数据卷容器

如果用户需要在多个容器之间共享一些持续更新的数据,最简单的方式是使用数据卷容器。数据卷容器也是一个容器,但是它的目的是专门用来提供数据卷供其他容器挂载。

首先,创建一个数据卷容器 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 容器中的数据卷,例如创建 db1db2 两个容器,并从 dbdata 容器挂载数据卷:

$ docker run -it --volumes-from dbdata --name db1 ubuntu
$ docker run -it --volumes-from dbdata --name db2 ubuntu

此时,容器 db1db2 都挂载同一个数据卷到相同的 /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 命令来指定同时删除关联的容器。

5. 利用数据卷容器来迁移数据

可以利用数据卷容器对其中的数据卷进行备份、恢复、以实现数据的迁移。

5.1 备份

使用下面的命令来备份 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 选项,我们可以让容器在停止后自动删除,而不需要我们再使用容器删除命令来删除它,这对于我们使用一些临时容器很有帮助。

5.2 恢复

如果要将数据恢复到一个容器,可以按照下面的步骤操作。首先创建一个带有数据卷的容器 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

你可能感兴趣的:(Docker,Docker数据管理)