本文將介紹一個困擾筆者近半年的虛擬化環境下的疑難故障,最後排查出來的故障原因和修復手段也讓人啼笑皆非。並非因為這個過程有多複雜,而是分享一個心理歷程,思考在遇到故障時如何兼顧業務和技術,如何正確使用搜尋引擎。
故障現象我們有一套高性能代理集群,之前內測階段運行穩定,結果等正式上線後不到半個月,提供代理服務的宿主突然接二連三死機,導致宿主上的所有服務全部中斷。
故障分析故障時宿主直接死機,無法遠端登錄,機房現場敲擊鍵盤業務反應。由於宿主syslog已接入ELK,所以我們採集了當時當機前後的各種syslog。
報錯日誌#透過查看死機宿主的syslog發現機器死機前有以下kernel報錯:
Nov 12 15:06:31 hello-worldkernel: [6373724.634681] BUG: unable to handle kernel NULL pointer dereferenceat 0000000000000078 Nov 12 15:06:31 hello-world kernel: [6373724.634718] IP: []pick_next_task_fair+0x6b8/0x820 Nov 12 15:06:31 hello-world kernel: [6373724.634749] PGD 10561e4067 PUDffdb46067 PMD 0 Nov 12 15:06:31 hello-world kernel: [6373724.634780] Oops: 0000 [#1] SMP
顯示訪問了核心空指標後觸發系統bug,然後引起一系列呼叫堆疊報錯,最後當機。
為進一步分析故障現象,首先需要理解這套高效能代理叢集的架構。
架構介紹#單一節點,是在萬兆網路卡的宿主機上執行Docker容器,然後在容器中執行Haproxy實例,每個節點、實例的設定資訊、業務資訊都託管在調度器上。
特別之處在於:宿主使用Linux Bridge直接給Docker容器配置IP位址,所有對外服務的IP,包括宿主自己的外網IP都綁在Linux Bridge上。
應用介紹每台宿主的作業系統、硬體、Docker版本全部一致,其中作業系統和Docker版本如下:
[操作系统] System : Linux Kernel : 3.16.0-4-amd64 Version : 8.5 Arch : x86_64 [Docker版本] Docker version 1.12.1, build 6b644ec初步分析
此集群的宿主配置一致,故障現像也一致,疑點有三:
1、Docker版本與宿主核心版本不相容#三台宿主的環境本來一致,但1台穩定跑服務2個月才死機,1台跑服務1個月後死機,另外1台上線跑服務一周便會死機。
發現每台宿主除了死機的異常日誌,平常也有相同報錯日誌:
time=”2016-09-07T20:22:19.450573015+08:00″level=warning msg=”Your kernel does not support cgroup memory limit” time=”2016-09-07T20:22:19.450618295+08:00″ level=warningmsg=”Your kernel does not support cgroup cfs period” time=”2016-09-07T20:22:19.450640785+08:00″ level=warningmsg=”Your kernel does not support cgroup cfs quotas” time=”2016-09-07T20:22:19.450769672+08:00″ level=warningmsg=”mountpoint for pids not found”
根據上面提示,應該是作業系統核心版本對該版本的Docker不支援某些功能所導致。不過在搜尋引擎上搜尋這並不影響Docker的功能,更不加影響系統穩定性。
例如:
time=”2017-01-19T18:16:30+08:00″level=error msg=”containerd: notify OOM events” error=”openmemory.oom_control: no such file or directory” time=”2017-01-19T18:22:41.368392532+08:00″level=error msg=”Handler for POST /v1.23/containers/338016c68da6/stopreturned error: No such container: 338016c68da6″
是Docker 1.9以來就有的問題,1.12.3修復了。
例如Github上有人回覆:
“I have been update my docker from 1.11.2 to 1.12.3, This issue is fixed. BTW, this error message can be ignored, it should really just be a warning.”
但這裡所說的都只是v1.12.2版本就能修復的問題,我們升級Docker版本後發現當機依舊。
於是,我們接著透過各種Google確認了很多與我們存在相同故障現象的問題,初步確認故障與Docker的相關性,又根據官方issue初步確認Docker版本與系統內核版本不相容可引發宕機的關聯性;接著,透過官方的changelog和issue確認宿主所使用Docker版本與系統核心版本不相容問題,出於嘗試心理,我們把Docker版本升級到1.12.2後,未出意外仍出現死機。
2.使用Linux bridge方式改造宿主網路卡可能觸發bug找了那台宿主跑服務一週就會死機的宿主,停止運作Docker,只改造網絡,穩定跑了一週未發現異常。
3.使用pipework為Docker容器配置IP可能觸發bug由於給容器分配IP時我們採用了開源的pipework腳本,因此懷疑pipework的工作原理有bug,所以嘗試不使用pipework分配IP位址,發現宿主仍出現當機。
於是初步排查陷入困境,眼看著宿主每月至少死機一次,非常鬱悶。
故障定位因为还有线上业务在跑,所以没有贸然升级所有宿主内核,而是期望能通过升级Docker或者其它热更新的方式修复问题。但是不断的尝试并没有带来理想中的效果。
直到有一天,在跟一位对Linux内核颇有研究的老司机聊起这个问题时,他三下五除二,Google到了几篇文章,然后提醒我们如果是这个 bug,那是在 Linux 3.18 内核才能修复的。
原因:从sched: Fix race between task_group and sched_task_group的解析来看,就是parent 进程改变了它的task_group,还没调用cgroup_post_fork()去同步给child,然后child还去访问原来的cgroup就会null。
不过这个问题发生在比较低版本的Docker,基本是Docker 1.9以下,而我们用的是Docker1.11.1/1.12.1。所以尽管报错现象比较相似,但我们还是没有100%把握。
但是,这个提醒却给我们打开了思路:去看内核代码,实在不行就下掉所有业务,然后全部升级操作系统内核,保持一个月观察期。
于是,我们开始啃Linux内核代码之路。先查看操作系统本地是否有源码,没有的话需要去Linux kernel官方网站搜索。
下载了源码包后,根据报错syslog的内容进行关键字匹配,发现了以下内容。由于我们的机器是x86_64架构,所以那些avr32/m32r之类的可以跳过不看。结果看下来,完全没有可用信息。
/kernel/linux-3.16.39#grep -nri “unable to handle kernel NULL pointer dereference” * arch/tile/mm/fault.c:530: pr_alert(“Unable to handlekernel NULL pointer dereference/n”); arch/sparc/kernel/unaligned_32.c:221: printk(KERN_ALERT “Unable to handle kernel NULL pointerdereference in mna handler”); arch/sparc/mm/fault_32.c:44: “Unable to handle kernel NULL pointer dereference/n”); arch/m68k/mm/fault.c:47: pr_alert(“Unable tohandle kernel NULL pointer dereference”); arch/ia64/mm/fault.c:292: printk(KERN_ALERT “Unable tohandle kernel NULL pointer dereference (address %016lx)/n”, address); debian/patches/bugfix/all/mpi-fix-null-ptr-dereference-in-mpi_powm-ver-3.patch:20:BUG:unable to handle kernel NULL pointer dereference at (null)
最后,我们还是下线了所有业务,将操作系统内核和Docker版本全部升级到最新版。这个过程有些艰难,当初推广这个系统时拉的广告历历在目,现在下线业务,回炉重造,挺考验勇气和决心的。
故障处理下面是整个故障处理过程中,我们进行的一些操作。
升级操作系统内核对于Docker 1.11.1与内核4.9不兼容的问题,可以删除原有的Docker配置,然后使用官方脚本重新安装最新版本Docker
/proxy/bin#ls /var/lib/dpkg/info/docker-engine. docker-engine.conffiles docker-engine.md5sums docker-engine.postrm docker-engine.prerm docker-engine.list docker-engine.postinst docker-engine.preinst #Getthe latest Docker package. $curl -fsSL https://get.docker.com/ | sh #启动 nohupdocker daemon -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock-s=devicemapper&
这里需要注意的是,Docker安装方式在不同操作系统版本上不尽相同,甚至相同发行版上也有不同,比如原来我们使用以下方式安装Docker:
apt-get install docker-engine
然后在早些时候,还有使用下面的安装方式:
apt-get install lxc-docker
可能是基于原来安装方式的千奇百怪导致问题丛出,所以Docker官方提供了一个脚本用于适配不同系统、不同发行版本Docker安装的问题,这也是一个比较奇怪的地方,所以Docker生态还是蛮乱的。
验证16:44:15 up 28 days, 23:41, 2 users, load average: 0.10, 0.13, 0.15 docker 30320 1 0 Jan11 ? 00:49:56 /usr/bin/docker daemon -p/var/run/docker.pid
Docker内核升级到1.19,Linux内核升级到3.19后,保持运行至今已经2个月多了,都是ok的。
总结这个故障的处理时间跨度很大,都快半年了,想起今年除夕夜收到服务器死机报警的情景,心里像打破五味瓶一样五味杂陈。期间问过不少研究Docker和操作系统内核的同事,往操作系统内核版本等各个方向进行了测试,但总与正确答案背道而驰或差那么一点点。最后发现原来是处理得不够彻底,比如升级不彻底,环境被污染;比如升级的版本不够新,填的坑不够厚。回顾了整个故障处理过程,总结下来大概如下:
回归运维的本质运维要具有预见性、长期规划,而不能仅仅满足于眼前:
#在處理這個故障的過程中,會發現不同人使用Google搜出來的東西並不一樣,為什麼呢?我覺得這就是搜尋引擎槽點滿滿,或者說靈活之處。像這次的故障,我用Linux Docker Unable to handle kernel NULL pointer dereference去搜索,與別人用”Unable to handle kernel NULL pointer dereference”結果就不同。原因在於增加了””之後,搜尋更加精確了。關於Google的正確開啟方式。
以上是一個困擾了我半年的難題的詳細內容。更多資訊請關注PHP中文網其他相關文章!