有两台跑了docker容器的宿主机发生了自动重启,上面的容器都被迫关机了,影响了业务稳定。其中一台已经不止一次自动重启。
我们的宿主机使用的CentOS。如何确定它发生过自动重启?使用 last reboot命令可以看到每次系统启动的时间,即可看到那些计划外的重启时间。
1)无故重启,首先想到的是查看物理机的/var/log/messages日志。但是在重启时间点附近没有找到与重启相关的信息。
2)在/var/crash目录下,保存了内核crash的日志vmcore-dmesg.txt ,内核crash时会保存crash信息到这个文件中。日志的生成时间与宿主机重启时间一致,可以说明宿主机是发生了kernel crash然后导致的自动重启。
从vmcore-dmesg日志中可以看到crash的堆栈:
<2>kernel BUG at drivers/md/persistent-data/dm-btree-remove.c:181!
<4>invalid opcode: 0000 [#1] SMP
<4>last sysfs file: /sys/devices/system/cpu/online
<4>CPU 13
<4>Modules linked in: bonding ip6_tables ebtable_nat ebtables ipt_REJECT xt_CHECKSUM xt_comment xt_multiport vhost_net macvtap macvlan tun kvm_intel kvm nfs lockd fscache auth_rpcgss nfs_acl sunrpc ipv6 veth ipt_addrtype bridge dm_thin_pool dm_bio_prison dm_persistent_data dm_bufio libcrc32c 8021q garp stp llc acpi_cpufreq freq_table mperf microcode iTCO_wdt iTCO_vendor_support acpi_pad serio_raw i2c_i801 i2c_core sg lpc_ich mfd_core shpchp e1000e ptp pps_core sr_mod cdrom usb_storage ext4 jbd2 mbcache sd_mod crc_t10dif ahci wmi dm_mirror dm_region_hash dm_log dm_mod [last unloaded: bonding]
<4>
<4>Pid: 30790, comm: dm-thin Not tainted 2.6.32-431.5.1.el6.x86_64 #1 ASUSTeK Computer INC. ASR1600/Z9PR-D16 Series
<4>RIP: 0010:[<ffffffffa01e3fd1>] [<ffffffffa01e3fd1>] shift+0xa1/0xc0 [dm_persistent_data]
<4>RSP: 0018:ffff880872459b70 EFLAGS: 00010297
<4>RAX: 00000000000000fc RBX: 00000000ffffffff RCX: 00000000000000fc
<4>RDX: 00000000ffffffff RSI: ffff8803017b9000 RDI: ffff8802a6903000
<4>RBP: ffff880872459ba0 R08: ffff8803017a3000 R09: 00000000000000fc
<4>R10: 00000000000000fb R11: 00000000000000fc R12: ffff8803017b9000
<4>R13: ffff8802a6903000 R14: 00000000000000fc R15: 00000000000000fd
<4>FS: 0000000000000000(0000) GS:ffff8800282e0000(0000) knlGS:0000000000000000
<4>CS: 0010 DS: 0018 ES: 0018 CR0: 000000008005003b
<4>CR2: 00007f7cb87a2e78 CR3: 0000000b3fd0e000 CR4: 00000000001407e0
<4>DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
<4>DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400
<4>Process dm-thin (pid: 30790, threadinfo ffff880872458000, task ffff8804126a8080)
<4>Stack:
<4> ffff880872459b90 ffff880872459ce0 ffff880a146e2640 ffff880872459c68
<4><d> ffff8802f85d8000 0000000000013a5a ffff880872459ca0 ffffffffa01e4876
<4><d> ffff880872459bf0 ffffffff81058d53 ffff88022a09f400 ffff880870d7cd14
<4>Call Trace:
<4> [<ffffffffa01e4876>] remove_raw+0x5f6/0x810 [dm_persistent_data]
<4> [<ffffffff81058d53>] ? __wake_up+0x53/0x70
<4> [<ffffffff81266c40>] ? generic_make_request+0x240/0x5a0
<4> [<ffffffffa01e4b40>] dm_btree_remove+0xb0/0x150 [dm_persistent_data]
<4> [<ffffffffa01fb727>] dm_thin_remove_block+0x87/0xb0 [dm_thin_pool]
<4> [<ffffffffa01f7702>] process_prepared_discard+0x22/0x60 [dm_thin_pool]
<4> [<ffffffffa01f6a37>] process_prepared+0x87/0xa0 [dm_thin_pool]
<4> [<ffffffffa01f93e0>] ? do_worker+0x0/0x260 [dm_thin_pool]
<4> [<ffffffffa01f9432>] do_worker+0x52/0x260 [dm_thin_pool]
<4> [<ffffffffa01f93e0>] ? do_worker+0x0/0x260 [dm_thin_pool]
<4> [<ffffffff81094d10>] worker_thread+0x170/0x2a0
<4> [<ffffffff8109b290>] ? autoremove_wake_function+0x0/0x40
从堆栈可以看出内核是在做dm_thin操作时出的问题,问题出现在存储操作上。
堆栈表明,内核在进行process_prepared_discard -> dm_thin_remove_block -> dm_btree_remove -> remove_raw 这是在删除块的操作。
在我们的内核源码中找到remove_raw函数,在代码上不能直接看出明显的bug。对比我们的centos内核代码和kernel最新的代码drivers/md/persistent-data/dm-btree-remove.c和drivers/md/dm-thin.c,并查看这两个文件的更新记录,想查看一下是否是内核近期修复的bug,最好能找到修复的补丁。
对比内核代码发现我们使用的CentOS版本的内核代码已经接近最新的kernel代码,并且dm-thin相关的严重bug补丁我们的内核代码都已经覆盖了。事实上dm-btree-remove.c在2013年后就没怎么更新过,dm-thin.c到现在还会有更新,但是直到2015年1月份的绝大多数补丁,我们使用的CentOS都已经合入了。也就是说我们目前宿主机使用的内核在dm-thin这个模块已经用了社区最新的代码。
所以寻找内核补丁的这条路行不通。
在google上搜索kernel crash的堆栈,搜到了基本相同堆栈的问题 https://www.redhat.com/archives/dm-devel/2013-February/msg00063.html 问题是2013年提的,最终并没有明确地解决。
从堆栈可以看出在做dm-thin的discard操作(process_prepared_discard),虽然不知道引起bug的根本原因,但是直接原因是discard操作引发的,可以关闭discard support来规避。
thin-provisioning的discard功能是什么?可以参考这两篇文档
https://www.kernel.org/doc/Documentation/device-mapper/thin-provisioning.txt
http://hustcat.github.io/docker-devicemapper3/
docker容器使用的存储是稀疏文件,镜像池文件虽然看上去有100G,但是实际占用的空间可能会很小,取决于容器内的文件占用空间。例如用ls -l 查看容器的镜像data文件大小为100G,但是用du -hs看空间占用可能只有2G,这就是稀疏文件,未使用的空间都用0来填充。discard的功能就是当容器内删除文件时,文件池会释放这部分空间,减小空间占用。在删除整个容器的镜像时,discard功能也会发挥释放占用空间的作用。discard功能是在内核2.6.X版本中引入的,在3.8中dm-thin支持了discard。我们使用的CentOS也有dm-thin的discard功能。那么解决(规避)整个问题的思路就是,通过关闭discard功能,从而就不会走到kernel crash的那段代码流程。
我们使用的docke有禁用discard的选项。在启动docker daemon时,使用--storage-opt参数。--storage-opt详解请见 https://github.com/docker/docker/tree/master/daemon/graphdriver/devmapper
关闭discard功能:
1)修改/etc/sysconfig/docker-storage, 添加 DOCKER_STORAGE_OPTIONS="--storage-opt dm.mountopt=nodiscard --storage-opt dm.blkdiscard=false"
2)关闭docker。 service docker stop
3)删除已有的镜像 rm -rf /var/lib/docker 。 执行这步时偶尔会提示有些文件busy删不掉,重启物理机后再重新执行一遍即可。
4)启动docker。 service docker start
验证禁用discard功能生效:
1)在容器中,删除一个文件,用 du -s来观察 /var/lib/docker/devicemapper/devicemapper/data文件大小,如果data文件大小不变,则说明已经关闭了discard(dm.mountopt=nodiscard生效了)。在discard功能启用时,在容器中删除文件后,data的占用空间也会变小。
2)关闭并删除一个容器,则容器的镜像也会被删除。用 du -s来观察 /var/lib/docker/devicemapper/devicemapper/data文件大小,如果data文件大小不变,则说明已经关闭了discard(dm.blkdiscard=false生效了)。如果discard功能生效时,删除容器镜像后,data的占用空间也会变小。
所以dm.mountopt=nodiscard 和dm.blkdiscard=false 从两个方面来关闭discard
将所有的宿主机的docker都配置成禁用discard功能,目前运行了3个月,没有再出现过kernerl crash问题。
有的人担心,既然discard功能是在删除文件时释放占用的空间,那么禁用discard后,容器内删除了文件,空间不释放,会不会导致不停地创建和删除文件把容器的空间占满。例如100G的容器镜像,创建个50G的文件,再删除它,再创建个50G,这时会不会提示空间不足?实际上虽然镜像池不会释放这部分空间,但是当空间增长到每个容器镜像大小上限(例如100G)时,那些已经删除的文件占用的空间是可以被重新使用的,就好比是脏数据,只是一开始没去回收他,当空间不足需要使用时会把脏数据清空供新数据使用。
获取CentOS内核源码的方法: http://wiki.centos.org/HowTos/I_need_the_Kernel_Source