站内链接:

Intro

原因

  • docker 中的image都是Readonly
  • docker 中的数据存储层在运行时创建, 此时状态为:Read-and-write
  • docker 中的数据存储层会跟随container的生命周期终止而销毁, 注意容器停止并不代表容器生命周期就已经结束

但是数据存储层因为文件系统隔离会有如下的问题:

  • 随着容器的消亡(dead)而销毁
  • 容器中数据不能在不同主机之间进行数据迁移
  • 数据写入数据存储层需要内核提供 UFS, 会有额外的性能损耗
  • 数据不能在多个容器中进行共享

为了解决如上的问题, docker 提供了类似外接数据盘的方式来进行数据持久化, 数据迁移等概念. 另外, 在讲解下面的内容之前我们先简单的介绍一下容器的生命周期:

container_timeline

其中 Deleted 代表着 Dead, Stoped 代表着 Exited, 只有容器被删除才代表着一个容器的生命周期结束, 此时容器中相关的文件系统资源才会被释放.

卷创建方式

docker 提供 3 中不同的方式将数据盘挂载到 docker 中

  • volume: 使用 docker 默认的文件中存储数据卷, /var/lib/docker/volumes/
  • bind mount: 将主机上的任何文件或者目录(绝对路径)挂载到容器中
  • tmpfs: 将外接数据存储到内存中, 参考 redis, 不会写入文件系统中

上述三者分别指向不同的区域, 一个为内存, 一个为文件系统, 一个为 docker area, 见
volume

Macos

mac 上的 docker 历史版本:

  • boot2docker: 自带一个最小化的 VirtualBox 的 Linux 镜像, 在虚拟机中运行 docker daemon.
  • Doocker Toolbox: boot2docker 的继任者, 原理一致, 但是封装了很多工具, 增加可用性
  • Docker for Mac: 内嵌虚拟机, 使用 XHYVE 虚拟机, 并做了很多定制化

正因为 mac 或者 windows 系统中对 docker 外层进行的包装, 所以在使用卷的时候跟 ubuntu 系统有很大的不同, 下面以 mongodb 为例简单介绍下因为虚拟机导致的一些在 linux 系统上碰不到的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 1. ubuntu上启动mongodb, 将数据目录bind到宿主机, mac上也是同样方式启动mongodb
docker run --name mongodb_nopasswd -p 27017:27017 -v /data/db/mongodb/data:/data/db -d mongo:latest

# 2. 重启容器, 此时会发现ubuntu系统没有任何问题, 但是mac系统上重启时报错: Operation not permitted, 具体原因见docker mongodb主页的warning说明
docker stop mongodb_nopasswd
docker start mongodb_nopasswd

# 3. 根据官网建议采用数据卷的方式
docker volume create mongovolume
docker run --name mongodb_nopasswd -p 27017:27017 -v mongovolume:/data/db -d mongo:latest

# 4. 重启正常
docker restart mongodb_nopasswd

但是, 如果使用数据卷挂载容器内的数据目录, 在 ubuntu 系统上通过docker inspect mongovolume可以直接在宿主机上找到输出的 mount 目录, 但是在 mac 系统上需要通过额外的操作:

1
2
3
4
5
# 1. 方式1(不推荐, 有乱码)
nc -U ~/Library/Containers/com.docker.docker/Data/debug-shell.sock
# 2. 推荐
docker run -it --rm --privileged --pid=host justincormack/nsenter1
# 3. 此时就可以在虚拟机中找到mount目录

最后, 这里再提及一些 mac 的 docker 虚拟机产生的另外一个问题, 那就是 IO 非常非常慢, 我在 mac 跑一个 flask 应用服务, 然后连接 docker 中的 mysql 服务, 页面每次刷新都需要好久, 此时需要使用docker-sync来辅助实现 IO 的同步操作, 具体原因和操作间参考链接中的说明.

volume

Intro

volume是 docker 中数据持久化的最佳方式(除了 Mac 之外), 数据卷是可以供一个或者多个容器使用的特殊目录, 它绕过 UFS, 提供了很多特性:

  • 在容器中共享和重用, 可以安全的在容器之间共享
  • 类似文件系统, 在 volume 中的改动会立刻生效
  • 根据容器存储层概念, 对数据卷的更新, 不会影响镜像
  • 根据持久化概念, 数据卷默认会一直存在, 即使容器被删除
  • 内置docker volume来进行数据卷的管理
  • volume 在生成时, 默认会随机生成一个名称
  • 相比 bind volume, volume 更容易备份和迁移

参数

  • -v/--volume:格式由三个字段组成, 卷名:容器路径:选项列表
  • --mount: 由多个键值对组成, 由,进行分隔, 格式为key=value

其中 mount 中的键值对可以为如下的值:

  • type: bind, volume, tmpfs
  • source: 如果忽略, 则为匿名卷, 否则为卷名
  • destination/dst/target: 文件或者目录会被挂载到的容器中的 path
  • volume-opt: 操作变量

另外 MAC 上查看 volume 的路径值的方式见 1.3 节的介绍.

Example

创建一个卷: docker volume create unusebamboo

获取卷列表: docker volume ls

查看某一个数据卷的详细信息: docker volume inspect unusebamboo

删除数据卷: docker volume rm unusebamboo

挂载卷:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# --mount
# 其中name 表示启动的容器名
docker run -d -it \
--name bamboo \
--mount source=unusebamboo,target=/container-path/docker \
--mount source=unuse1,target=/container-path/unusedata \
ubuntu:bamboo

# -v, 使用组合模式
docker run -d -it \
--name bamboo \
-v unusebamboo:/container-path/docker \
-v unuse1:/container-path/unusedata
ubuntu:bamboo

# 查看容器中的卷信息
docker inspect unusebamboo

默认情况下, volume 创建的 Driver 为local, 此时任何容器不能共享此数据卷数据.

容器共享

多个容器之间可以共享一个数据卷, 通过--volumes-from实现父子容器的数据卷共享操作

1
2
3
4
5
6
7
8
9
10
# 1. 启动一个父容器, 设置共享目录
docker run -dt --name con-father -v /data/db/test:/data/ ubuntu:latest
# 2. 进入con-father, 可以看到/data下有同名目录以及子文件
docker exec con-father ls /data

# 3. 启动子容器, 设置容器卷共享
docker run -dt --name con-child --volumes-from con-father ubuntu:latest

# 4. 进入子容器, 进入/data目录创建文件会发现宿主机和父容器相应目录中也存在文件
echo "child" > /data/child.md

bind

Intro

使用bind mount方式, 可以将当前宿主机上的任何文件/目录挂载到容器中, 注意, 它同 volume 有如下的区别:

  • 挂载的文件和目录可以被任何进程更改, 注意这里同 volume 的区别
  • 如果文件或者目录不存在, 自动创建
  • 该方式不能使用docker volume进行管理

bind mount 的使用场景如下:

  • 宿主机共享配置到容器中, 例如/etc/resolv.conf用于 DNS 解析
  • 宿主机与容器共享代码或构建工具, 这是大部分的需求
  • 宿主机和容器的文件和目录结构需要保持一致时

参数

  • -v/--volume: 主机文件目录:容器文件目录:选项列表
  • --mount: 格式为key=value

其中选项列表可以为: ro, consistent, delegated, cached, z, Z. 其中键值对的类型:

  • type: bind, volume, tmpfs
  • source: 主机路径
  • destination/dst/target: 容器路径
  • readonly: 存在时更改 Propagation
  • consistently: 在 MAC 中生效, 可以为 consistent, delegated, cached

Example

启动 container, 并绑定共享磁盘, 此时不会提前创建 volume

1
2
3
4
5
6
7
8
9
10
11
12
13
# --mount
# 其中name 表示启动的容器名
docker run -d -it \
--name bamboo \
--mount type=bind,source=/data/docker,target=/container-path/docker ubuntu:bamboo

# -v
docker run -d -it \
--name bamboo \
-v /data/docker:/container-path/docker ubuntu:bamboo

# 查看容器中的Mounts部分
docker inspect bamboo

tmpfs

Intro

使用tmpfs, 存储在内存中的临时文件系统, 一般在注重安全, 不需要持久化的情况下使用.

限制: tmpfs 挂载不能再容器之间共享, 仅能在 Linux 下生效

参数

挂载tmpfs时, 可以使用--mount来增加两个配置项

  • tmpfs-size: 默认无限制, 表示 tmpfs 的最大大小
  • tmpfs-mode: 8 进制文件模式

Example

1
2
3
4
5
6
7
8
9
10
11
12
# --mount
docker run -d -it \
-name bamboo-tmpfs \
--mount type=tmpfs,destination=/container-path/docker,tmpfs-mode=1770 ubuntu:bamboo

# tmpfs
docker run -d -it \
-name bamboo-tmpfs \
--tmpfs /container-path/docker ubuntu:bamboo

# 查看容器的Tmpfs部分
docker inspect bamboo-tmpfs

Inspect

配置

1
2
3
4
5
6
# 获取配置
docker container inspect --format='{{json .Config}}' test1|python -m json.tool

# 2. 查看数据卷配置
docker volume ls
docker inspect volumename1

挂载与网络

1
2
3
4
5
# 获取某一个容器的 ip
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' test1

# 获取挂载
docker container inspect --format='{{json .Mounts}}' test1|python -m json.tool

清理

1
2
# 删除所有container:
docker kill $(docker ps -q) ; docker rm $(docker ps -a -q) ; docker rmi $(docker images -q -a)

参考