Docker 环境 Storage Pool 用完解决方案:resize-device-mapper

问题引出

今日笔者docker下出现诡异问题,在容器中vim编辑文件,保存是,一直提示 “E667: Fsync failed”,并且在同一个宿主机上的容器均有此问题,遂怀疑环境故障,且与存储相关,后偶然运行命令“docker info”,发现异常信息:git

Containers: 26
Images: 12
Storage Driver: devicemapper
 Pool Name: docker-8:3-6684808-pool
 Pool Blocksize: 65.54 kB
 Backing Filesystem: extfs
 Data file: 
 Metadata file: 
 Data Space Used: 107.4 GB
 Data Space Total: 107.4 GB
 Metadata Space Used: 65.48 MB
 Metadata Space Total: 2.147 GB
 Udev Sync Supported: true
 Library Version: 1.02.89-RHEL6 (2014-09-01)
Execution Driver: native-0.2
Kernel Version: 2.6.32-504.el6.x86_64
Operating System: <unknown>
CPUs: 8
Total Memory: 15.58 GiB
Name: lx-vm01.lianjia.com.vm
ID: T2YG:HFF3:DEZP:5SDU:4B2K:B76F:AUHD:YYVA:DXPS:AVZL:X4WQ:BQFZ
Username: liuyanglong
Registry: [https://index.docker.io/v1/]

其中可看到,Data Space Used 已和 Data Space Total同样,storage Pool用完,呃。。。。
docker start默认分配的device mapper的storage pool就是100G,它会把你全部的容器存储到一个 100G 的简短文件中,而且限制每一个容器最大为 10GBgithub

如下解决方案来自:http://jpetazzo.github.io/2014/01/29/docker-device-mapper-resize/docker

它的工做原理

要真正理解咱们要作的事情,首先来了解 Device Mapper 插件的工做原理。ubuntu

它是基于 Device Mapper 的“精简目标”的特性。它其实是目标块设备的快照,之因此被称为“精简”是由于它容许精简配置。精简配置意味着你有一个(但愿很大)可用存储块的池,接着你能够从那个池中建立任意大小的块设备(虚拟磁盘,若有须要);在你实际读写后,这些存储块将会被标记为已使用(或者从池中拿走)。vim

这意味着你是能够超额使用这个池,好比在一个 100GB 的池里面建立几千个 10GB 的卷,甚至多是一个 100TB 的卷在一个 1GB 的池里面。只要你的实际读写的块的容量不大于池的大小,你怎么作都 OK 。app

除此以外,精简目标的方式是能够作快照的。这代表不管什么时候,你均可以建立一个存在的卷的浅拷贝。在用户看来,就像你有两个同样的卷,它们能够独立地各自修改。即便你作了一个完整的拷贝,除了在时间上它是瞬间发生的(即便是很大的卷),它们不会两次重复使用存储。额外的存储只有当其中任何一卷有变化的时候才会发生,而后精简目标会从池里面分配一个存储快。性能

从本质上来看,“精简目标”实际上使用了两个存储设备:一个(大)的是存储块池本身,还有一个小的存储了一些元数据。这些元数据中包括了卷、快照、以及每一个卷的块或者快照同存储池中块的映射信息。ui

当 Docker 使用 Device Mapper 存储插件的时候,它会在 /var/lib/docker/devicemapper/devicemapper/data/var/lib/docker/devicemapper/devicemapper/metadata 下建立两个文件(若是它们不存在)来存储对应的存储池和相关的元数据。这很是方便,你不须要作任何安装部署的工做(你不须要额外的分区来存储 Docker 容器,或者创建 LVM 或其余相似的东西)。然而它也有两个缺点:插件

  • 存储池会有一个默认 100GB 的容量
  • 它将会被稀疏文件所支持。从磁盘的使用效率的观点来看,这还不错的(就像在精简池中的卷,它一开始是小的,只有当实际须要写的时候才会使用磁盘的存储块)。可是从性能的角度来看就不那么好了,由于 VFS 增长了一些额外的负担,特别是"第一次写的时候"。

在了解如何调整容器的大小以前,咱们来试试看如何给池增长更多空间。3d

咱们须要一个更大的池

警告:下面的操做会删除你全部的容器和镜像,确保你已经把以前的数据作了备份!

记住上面说过的,当数据和元类信息文件不存在的时候 Docker 会建立它们,因此解决方案很是简单:在启动它们以前,在 Docker 里建立这些文件!

中止 Docker 守护进程,由于咱们将要从新设置咱们的存储插件,若是咱们在运行的时候移除文件,那么糟糕的事情就将发生。

  1. 擦去 /var/lib/docker。(警告:正如前面提到的,这个操做会把你全部的容器和镜像都删除掉。)

  2. 建立存储目录: mkdir -p /var/lib/docker/devicemapper/devicemapper

  3. 建立你的池: dd if=/dev/zero of=/var/lib/docker/devicemapper/devicemapper/data bs=1G count=0 seek=250 ,建立一个 250G 的稀疏文件。若是你指定 bs=1G count=250(不使用 seek 选项),那么它会建立一个普通文件(而不是一个稀疏文件)。

  4. 重启 Docker 守护进程。提示:在默认状况下,若是你有 AUFS 的支持, Docker 会使用它;因此若是你要强制使用 Device Mapper 的插件,须要在启动 Docker 的命令中增长 -s devicemapper 的选项。

  5. 使用 docker info 来检查 Data Space Total 的值是否正确。

咱们须要一个更快的池

警告:下面的操做也会删除你全部的容器和镜像。确保把你重要的镜像保存在 registry 中,保存你容器里面的重要数据。

要得到一个更快速的池,最简单的办法就是使用一个真实的设备而不是一个基于文件的循环设备。过程几乎同样。假设你有一个彻底空的硬盘, /dev/sdb,你想把它彻底用于容器的存储,你能够这样作:

  1. 中止 Docker 守护进程
  2. 移除 /var/lib/docker (似曾相识,对么?)
  3. 建立一个存储目录: mkdir -p /var/lib/docker/devicemapper/devicemapper
  4. 在目录下建立一个数据软连接,指向设备:

    ln -s /dev/sdb /var/lib/docker/devicemapper/devicemapper/data
  5. 重启 Docker
  6. 使用 docker info 来检查 Data Space Total 的值是否正确

使用 RAID 和 LVM

若是你但愿合并多块类似的磁盘,可使用 RADID10 软件,这个会经过连接到 /dev/md 而实现。另一个很是好的选择是把你的磁盘(或者RAID磁盘阵列)放到 LVM 的物理卷中,而且建立两个逻辑卷:一个是数据,一个是元数据。对于元数据池的最佳的大小我没有什么特别的建议,不过占数据池的 1% 看起来不错。

就像前面同样,中止 Docker ,移除它的数据目录,而后建立一个指向 /dev/mapper 设备的符号连接,而后重启 Docker 。

ln -s /dev/mapper/vg_dockerdev-LogVol01 /var/lib/docker/devicemapper/devicemapper/data

以后重启docker便可。

扩容容器

默认来讲,若是你使用 Device Mapper 的存储插件,全部的镜像和容器是从一个初始 10G 的文件系统中建立的。让咱们来看看如何从一个更大的文件系统中建立一个容器。

首先,咱们用 Ubuntu 的镜像来建立咱们的容器。咱们不须要在这个容器里运行任何东西,只须要这个文件(或者关联的文件系统)存在。为了演示,咱们会在这个容器里运行 df ,来看一下根文件系统的大小。

$ docker run -d ubuntu df -h /
4ab0bdde0a0dd663d35993e401055ee0a66c63892ba960680b3386938bda3603

因为须要修改 Device Mapper 管理中的一些卷的信息,咱们如今用 root 的身份来运行一些命令。全部以#开头的命令都必须以 root 身份来执行。只要能访问 Docker 的 Socket 服务,你也能够用普通用户的身份来执行其余的命令(以$开头)。

让咱们看一下 /dev/mapper ,那里应该有一个对应容器文件系统的符号连接,以 docker-X:Y-Z- 开头:

ls -l /dev/mapper/docker-*-4ab0bdde0a0dd663d35993e401055ee0a66c63892ba960680b3386938bda3603
lrwxrwxrwx 1 root root 7 Jan 31 21:04 /dev/mapper/docker-0:37-1471009-4ab0bdde0a0dd663d35993e401055ee0a66c63892ba960680b3386938bda3603 -> ../dm-8

注意记住那个全名,咱们将来会用到。

首先让咱们来看一下当前卷的信息表:

dmsetup table docker-0:37-1471009-4ab0bdde0a0dd663d35993e401055ee0a66c63892ba960680b3386938bda3603
0 20971520 thin 254:0 7

第二个数字是设备的大小,表示有多少个 512-bytes 的扇区. 这个值略高于 10GB 的大小。

咱们来计算一下一个 42GB 的卷须要多少扇区,

$ echo $((42*1024*1024*1024/512))
88080384

精简快照目标的一个神奇的特色是它不会限制卷的大小。当你建立它的时候,一个精简的卷使用0个块,当你开始往块里面写入的时候,它们会从共用的块池中进行分配。你能够写0个块,或者是10亿个块,这个和精简快照目标不要紧。文件系统的大小只和 Device Mapper 表有关系。

以为困惑?不要担忧。咱们只是须要装载一个新的表,这个彻底和以前的是同样的,可是有更多的扇区。仅此而已。

旧表是 0 20971520 thin 254:0 7 。咱们会改变第二个数字,要很是当心保持其余的值不变。你的卷可能不是 7 ,因此要使用正确的值!

这样操做:

# echo 0 88080384 thin 254:0 7 | dmsetup load docker-0:37-1471009-4ab0bdde0a0dd663d35993e401055ee0a66c63892ba960680b3386938bda3603

如今若是咱们再次检查表的信息,步骤和前面同样。首先使用下面的命令激活新表:

# dmsetup resume docker-0:37-1471009-4ab0bdde0a0dd663d35993e401055ee0a66c63892ba960680b3386938bda3603

执行完命令后,再次检查一下表的信息,发现它会使用新的扇区数量。

咱们已经调整了块设备的大小,可是咱们仍然须要调整文件系统的大小,咱们使用 resize2fs 来操做:

# resize2fs /dev/mapper/docker-0:37-1471009-4ab0bdde0a0dd663d35993e401055ee0a66c63892ba960680b3386938bda3603
resize2fs 1.42.5 (29-Jul-2012)
Filesystem at /dev/mapper/docker-0:37-1471009-4ab0bdde0a0dd663d35993e401055ee0a66c63892ba960680b3386938bda3603 is mounted on /var/lib/docker/devicemapper/mnt/4ab0bdde0a0dd663d35993e401055ee0a66c63892ba960680b3386938bda3603; on-line resizing required
old_desc_blocks = 1, new_desc_blocks = 3
The filesystem on /dev/mapper/docker-0:37-1471009-4ab0bdde0a0dd663d35993e401055ee0a66c63892ba960680b3386938bda3603 is now 11010048 blocks long

做为一个可选步骤,咱们会重启容器,检查一下咱们的确有了正确大小的空闲空间:

$ docker start 4ab0bdde0a0dd663d35993e401055ee0a66c63892ba960680b3386938bda3603
$ docker logs 4ab0bdde0a0dd663d35993e401055ee0a66c63892ba960680b3386938bda3603
df: Warning: cannot read table of mounted file systems: No such file or directory
Filesystem      Size  Used Avail Use% Mounted on
-               9.8G  164M  9.1G   2% /
df: Warning: cannot read table of mounted file systems: No such file or directory
Filesystem      Size  Used Avail Use% Mounted on
-                42G  172M   40G   1% /

想把整个过程自动化起来?固然没问题。

CID=$(docker run -d ubuntu df -h /)
DEV=$(basename $(echo /dev/mapper/docker-*-$CID))
dmsetup table $DEV | sed "s/0 [0-9]* thin/0 $((42*1024*1024*1024/512)) thin/" | dmsetup load $DEV
dmsetup resume $DEV
resize2fs /dev/mapper/$DEV
docker start $CID
docker logs $CID

扩容镜像

不幸的是,当前版本的 Docker 不能让咱们很方便地扩容镜像。你能够把镜像对应的块设备进行扩容,而后从它来建立一个容器,可是新的容器不会有正确的大小。

一样,若是你提交了一个很大的容器,最后生成的镜像也不会很大(这是由 Docker 为镜像准备文件系统的方法形成的)。

这意味着若是一个容器真的超过了 10GB ,在不使用一些其余的小技巧的状况下,你无法正确的把它提交为一个镜像。

总结

Docker 未来确定会提供一些更好的方法来扩容容器,所需的代码变更是很小的。管理一个精简的池和对应的元信息比较复杂(由于这个须要不少不一样的操做流程,以及一个潜在的数据迁移。鉴于移除了全部的东西来构件新的池,也就没有在本文说起),可是咱们今天提到的一些解决方案相信已经对你有所帮助。

相关文章
相关标签/搜索