devicemapper的经验

Docker需要union fs来做容器的存储,各个发行版不一样,ubuntu上使用AUFS,centos上默认使用devicemapper(关于这俩的对比,可以参加infoQ的文章)。AUFS据说因为代码写的太糟糕,一直被linus拒绝接纳进内核,所以在Redhat系列上,需要留神,比如我们就有几次遇到了这么一个问题。

在centos 7.2上,默认配置安装docker,当运行的docker容器数量较多(15-20个)时,docker进程会挂死,体现为只要是需要跟docker daemon交互命令,如docker ps, docker info, 都会挂住。查看内核的dmesg信息,会看到dockerd-current进程已经block了很久,日志里通常会出现xfs的函数。

Description of problem:
caiqian> [ 1554.834403] XFS (dm-4): xfs_log_force: error -5 returned.
<caiqian> [ 1560.557408] INFO: task dockerd-current:8562 blocked for more than 120 seconds.
<caiqian> [ 1560.558532] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
<caiqian> [ 1560.559145] dockerd-current D ffff880228b53c90     0  8562      1 0x00000004
<caiqian> [ 1560.559732]  ffff88023029bd90 0000000000000082 ffff880033738000 ffff88023029bfd8
<caiqian> [ 1560.560365]  ffff88023029bfd8 ffff88023029bfd8 ffff880033738000 ffff880228b53c80
<caiqian> [ 1560.560979]  ffff88022c312d10 ffff880228b53cc0 ffff880228b53ce8 ffff880228b53c90
<caiqian> [ 1560.561597] Call Trace:
<caiqian> [ 1560.561798]  [<ffffffff8168b879>] schedule+0x29/0x70
<caiqian> [ 1560.562225]  [<ffffffffa02debb1>] xfs_ail_push_all_sync+0xc1/0x110 [xfs]
<caiqian> [ 1560.562754]  [<ffffffff810b1720>] ? wake_up_atomic_t+0x30/0x30
<caiqian> [ 1560.563223]  [<ffffffffa02c7b61>] xfs_unmountfs+0x71/0x1c0 [xfs]
<caiqian> [ 1560.563709]  [<ffffffffa02c866b>] ? xfs_mru_cache_destroy+0x6b/0x90 [xfs]
<caiqian> [ 1560.564249]  [<ffffffffa02cacc2>] xfs_fs_put_super+0x32/0x90 [xfs]
<caiqian> [ 1560.564736]  [<ffffffff812007f2>] generic_shutdown_super+0x72/0xf0
<caiqian> [ 1560.565218]  [<ffffffff81200c37>] kill_block_super+0x27/0x70
<caiqian> [ 1560.565664]  [<ffffffff81200f79>] deactivate_locked_super+0x49/0x60
<caiqian> [ 1560.566153]  [<ffffffff81201576>] deactivate_super+0x46/0x60
<caiqian> [ 1560.566600]  [<ffffffff8121e9b5>] mntput_no_expire+0xc5/0x120
<caiqian> [ 1560.567056]  [<ffffffff8121faf0>] SyS_umount+0xa0/0x3b0
<caiqian> [ 1560.567471]  [<ffffffff816967c9>] system_call_fastpath+0x16/0x1b
<caiqian> [ 1584.914502] XFS (dm-4): xfs_log_force: error -5 returned.
<caiqian> [ 1614.994477] XFS (dm-4): xfs_log_force: error -5 returned.

为什么会出现这个问题呢?

实际上这是docker的一个bug。在解释这个bug之前,有必要先说明下docker容器的存储到底是怎么回事。

Docker容器有自己的存储空间,我们也知道这是一个分层的用户文件系统,但它总归是需要有磁盘空间来存储的;centos 7.2版本上的docker默认使用的是devicemapper(Device mapper 是 Linux 2.6 内核中提供的一种从逻辑设备到物理设备的映射框架机制,在该机制下,用户可以很方便的根据自己的需要制定实现存储资源的管理策略),简单来说就是先创建一个大文件(thin poll,看上去是100GB,实际按照容器实际使用的空间来计算磁盘的占用空间),然后每个容器分配10GB空间。

[root@liubei devicemapper]# ll -h
total 11G
-rw------- 1 root root 100G May 15 22:18 data
-rw------- 1 root root  20G May 15 22:18 metadata
[root@liubei devicemapper]# du -s -h *
11G	data
16M	metadata

ls看到的大小和du看到的大小不一样,du才是真实占用磁盘的空间。

Centos 7.2默认会在/var/lib/docker/devicemapper下创建这个文件,大小100GB;devicemapper的文件系统格式是xfs。显然,当容器跑的比较多的时候,这个空间就不够用了,我们的问题也就是在这个时候出现的。

当devicemapper空间不足的时候,xfs会返回ENOSPC,并且会不断的重试IO,所以会挂住dockerd-current进程。Docker社区有一个PR来解决这个问题,方法就是增加一个dm.xfs_nospace_max_retries参数,设置为0可以停止重试。

社区在1.12.6版本解决了这个问题,不过我们的版本是1.10.3,升级版本不太现实,更实际的做法是修改devicemapper的文件系统格式为ext4(devicemapper只支持ext4和xfs),并且扩大devicemapper的最大空间。centos 7.2上修改/etc/sysconfig/docker-storage文件:

$ cat /etc/sysconfig/docker-storage
DOCKER_STORAGE_OPTIONS=--storage-driver=devicemapper --graph=/home/docker --storage-opt dm.loopdatasize=300G --storage-opt dm.loopmetadatasize=50G --storage-opt dm.fs=ext4

再说点题外话

实际上在linux kernel 3.10版本时,内核合入了一个叫做overlayFS的特性,它也是一种union文件系统。overlayFS合入内核还有一段故事,VFS的维护者 Al Viro 并不喜欢overlayFS,但由于群众的呼声太高,linus还是接纳了这一特性,在邮件中他是这么写的:

Yes, I think we should just do it. It's in use, it's pretty small, and the other alternatives are worse. Let's just plan on getting this thing done with.

Al, I realize you may not love this, but can you please give this alook? People clearly want to use it. In particular the new interfaces, like the inode ops open function with dentry passed in or whatever? The changes outside of overlayfs looked fine to me.

and the other alternatives are worse 是有多看不上AUFS啊。

我们用的centos 7.2的内核版本是3.10.0,也已经支持了overlayFS,虽然没有默认加到内核里,但可以通过modprobe overlay来加载,然后修改下docker-storage的参数–storage-driver=overlay,重启docker进程就可以了。

!!!但我建议你不要在centos 7.2上使用overlayFS。我们曾经有段时间改为overlayFS,但我的一个mysql双主集群在重启之后,出现了docker容器无法启动的问题,由于不熟悉mysql,最终还是切回了devicemapper。centos 7.2的release note里提到overlayFS是一个Technology Preview,尽量不要用,要用的话务必升级内核。

据说在kernel 3.18版本之后,overlayFS就比较稳定了,所以你可以试试新发行的centos 7.3版本。

再再说点题外话

devicemapper支持使用loop-lvm和direct-lvm,默认使用loop-lvm设备。Docker 在初始化的过程中,创建 data 和 metadata 这两个稀疏文件,并分别附加到回环设备 /dev/loop0 和 /dev/loop1 上,然后基于回环设备创建 thin pool。 info 来看看我们的docker info:

[root@liubei sysconfig]# docker info
...
Server Version: 1.10.3
Storage Driver: devicemapper
 Pool Name: docker-8:4-156237828-pool
 Pool Blocksize: 65.54 kB
 Base Device Size: 10.74 GB
 Backing Filesystem: ext4
 Data file: /dev/loop3
 Metadata file: /dev/loop4
 Data Space Used: 11.61 GB
 Data Space Total: 322.1 GB
 Data Space Available: 310.5 GB
 Metadata Space Used: 15.8 MB
 Metadata Space Total: 17.05 GB
 Metadata Space Available: 17.03 GB

data、metadata分别对应loop3/loop4。

docker官方建议,生产环境上不要使用loop-lvm,而应该使用direct-lvm,有关他们的对比可以参加redhat的说明

二者的使用可以参考这篇文章,由于需要单独一个磁盘分区,而我们后续也会升级为overlayFS,所以我没有使用direct-lvm,先在这里记一下。