一个疑难故障,坑了我半年青春-----知识就是生产力

作者介绍

林伟壕,网易游戏资深运维工程师。现任职于网易游戏,从事游戏运维相关工作;曾就职于中国电信,负责数据网络维护、网络安全防御等工作。深入研究Linux运维、虚拟化等,现致力于企业级网络安全防护自动化体系构建。

相对物理环境,虚拟化环境更加错综复杂。之前弄KVM虚拟化时经常遇到好多次莫名其妙的网络故障,查出来的原因要么是操作系统内核bug,要么是KVM与操作系统内核版本不兼容,最后是通过升级操作系统内核或者KVM版本修复了。没想到,转型到Docker后,又重蹈覆辙了。

本文将介绍一个困扰笔者近半年的虚拟化环境下的疑难故障,最后排查出来的故障原因和修复手段也让人啼笑皆非。并非因为这个过程有多复杂,而是分享一个心理历程,思考在遇到故障时如何兼顾业务和技术,如何正确使用搜索引擎。

故障现象

我们有一套高性能代理集群,之前内测阶段运行稳定,结果等正式上线后不到半个月,提供代理服务的宿主突然接二连三死机,导致宿主上的所有服务全部中断。

故障分析

故障时宿主直接死机,无法远程登录,机房现场敲键盘业务反应。由于宿主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修复了。参考https://github.com/docker/docker/ issues/24211

比如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的相关性:

http://serverfault.com/questions/709926/bug-unable-to-handle-kernel-null-pointer-dereference-at-on-google-compute-eng

https://support.mayfirst.org/ticket/10872

又根据以下官方issue初步确认Docker版本与系统内核版本不兼容可引发宕机的关联性:

https://github.com/docker/docker/issues/19910

接着,通过官方的changelog和issue确认宿主所使用Docker版本与系统内核版本不兼容问题:

https://github.com/docker/docker/blob/v1.12.2-rc1/CHANGELOG.md

出于尝试心理,我们把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 内核才能修复的。

参考:

  • https://lists.gt.net/linux/kernel/2256803
  • https://lkml.org/lkml/2014/2/15/217
  • https://github.com/docker/docker/issues/21081
  • https://github.com/torvalds/linux/commit/eeb61e53ea19be0c4015b00b2e8b3b2185436f2b

原因:

从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官方网站搜索。

“`

apt-cache search linux-image-3.16.0-4-amd64

apt-get source linux-image-3.16.0-4-amd64

“`

下载了源码包后,根据报错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和操作系统内核的同事,往操作系统内核版本等各个方向进行了测试,但总与正确答案背道而驰或差那么一点点。最后发现原来是处理得不够彻底,比如升级不彻底,环境被污染;比如升级的版本不够新,填的坑不够厚。回顾了整个故障处理过程,总结下来大概如下:

回归运维的本质

运维要具有预见性、长期规划,而不能仅仅满足于眼前:

  1. 应急预案:针对可能系统上线后可能发生的故障类型进行总结,并提供应急预案。
  2. 抢通业务:优先抢通业务,再处理故障。
  3. 应用版本选择等技术选型问题:在环境部署和应用选型时需要特别注意各种版本,最好采用社区通用或者公司其他同学已经测试或验证可行的版本。
  4. 操作系统内核:要合理升级内核,只有定位到确定版本存在的问题,才能有针对性的升级内核版本,不然一切徒劳。
  5. 在我们原来的设计中,不同用户调度器针对同一个容器同时操作没有加锁机制,也没有按照对源判断原则,也曾出现过迁移失败的情况。迁移时判断迁往的目的地址是否就是本地地址,如果是本地地址应该拒绝操作的。这个问题不知你是否觉得眼熟。我倒是发现,很多人程序开发过程中,就经常不对输入源或者操作的源状态进行判断,结果出现了各种bug。

Google的能力

在处理这个故障的过程中,会发现不同人使用Google搜出来的东西并不一样,为什么呢?我觉得这就是搜索引擎槽点满满,或者说灵活之处。像这次的故障,我用Linux Docker Unable to handle kernel NULL pointer dereference去搜索,与别人用”Unable to handle kernel NULL pointer dereference”结果就不同。原因在于增加了””之后,搜索更加精确了。关于Google的正确打开方式,建议参考:

    • https://www.zhihu.com/question/20161362?rf=19798921

http://www.yunweipai.com/archives/18950.html

 

时间: 2025-01-20 12:33:49

一个疑难故障,坑了我半年青春-----知识就是生产力的相关文章

android-关于NFC开发的一个疑难问题

问题描述 关于NFC开发的一个疑难问题 最近接手了其他人已经完成的一个APP,然后要求在里面加上NFC扫描的功能,只是加个NFC的话倒是简单了,但是现在出了问题,之前的同事通过TabHost和RadioButton做了一个底部导航栏,然后NFC读取是在其中的一个Activity完成的,点击屏幕开始扫描NFC,但是NFC读到标签时会优先把这个Activity放到前台来拦截NFC的Intent,问题就来了,每次点击屏幕后扫描到标签的话会先把NFC读取的那个Activity提到前台来覆盖掉原来的Tab

善于重启网络设备 解决网络的疑难故障

网络设备的工作状态直接关系着网络的运行状态,在长时间工作后,网络设备的工作状态很容易出错,事实上,在解决由网络设备工作状态引起的网络故障时,我们应该善于"重启"网络设备,以便让网络设备的工作状态快速恢复正常,从而在转瞬之间就能解决看上去非常奇怪的网络故障.本文就从实战角度出发,希望下面的内容能给各位带来收获! 故障回放 单位的一位员工说他使用的计算机不能访问Internet网络了,而且单位的局域网也不能访问了.原以为这只是个别现象,可谁曾想到,单位的其他同事陆陆续续打来电话,向笔者反映

一个SEO项目管理人员所需具备的知识和技能

优秀的SEO项目经理或SEO主管,是大多数组织中最难找到或提拔的人才,这是因为有实践经验,并且有SEO理论知识的项目经理十分稀少,而且即使有这样的人,身价也很高,所以在很多公司或组织中,合格的SEO项目经理寥寥无几.SEO项目经理不仅是项目的执行者,更应该是项目管理者,负责从项目开始到项目结束的整个过程.所以,一个优秀的SEO项目经理,应该具备以下素质. 1.系统的SEO技术知识和项目管理知识 SEO技术知识:掌握系统的SEO知识,如果对SEO知识分为三级,了解,掌握,精通,至少需要到掌握的级别

使用JavaScript制作一个简单的计数器的方法_基础知识

设计思想 该方法的关键是Cookie技术和动态图像特性的综合运用.使用Cookie,可以在用户端的硬盘上记录用户的数据,下次访问此站点时,即可读取用户端硬盘的Cookie,直接得知来访者的身份和访问次数等有关信息.JavaScript中通过document.cookie属性访问Cookie,这个属性包括名字.失效日期.有效域名.有效URL路径等.用等号分开的名字和其值是Cookie的实际数据,本例中用来存储该访问者访问该页面的次数.通过把Web页中的图像映射到一个Images数组,一定条件下修改

介绍一个简单的JavaScript类框架_基础知识

 在写work-in-progress JavaScript book一书时,对于javascript继承体系,我花费了相当的时间,并在该过程中研究了各种不同的模拟经典类继承的方案.这些技术方案中,我最为推崇的是base2与Prototype的实现. 从这些方案中,应该能提炼出一个具有其思想内涵的框架,该框架须具有简单.可重用.易于理解并无依赖等特点,其中简单性与可用性是重点.以下是使用示例:   var Person = Class. extend ( { init: function (is

家不是一个讲理的地方_结婚后必备知识_经典网摘

已婚要看..未婚更要看 这是我的好友在异国结婚时,他父亲从大陆寄来的信,结婚当日司仪当众就把全文朗诵出来,那是我见过最安静的一次请客场合,在场学生无不为其父母的挚爱而感动,虽然事隔多年但那个场景仍然深深印在脑海中,这篇文章后来流传开了,我则是一直保存到现在,希望能对你们有所启示.  <<家不是一个讲理的地方. 一份不在婚礼上的家长致辞>>  萍儿:  爸妈听到你要结婚喜讯,真为你高兴,远隔千里,我们不能参加你的婚礼,不能在婚礼上献上我们的家长致辞,这是我们内心的一点遗憾. 但你是我

一个JavaScript变量声明的知识点_基础知识

上周四吃完午饭,leader发了一道JavaScript的题目给我们做,我们Team里面有做前端的,有做后台的,也有坐mobile web的,所以大家对题目的理解各自都不一样,然后在QQ讨论组里面进行讨论.发现虽然很基础,但是通过讨论收获不少,分享出来.当然在有开发经验的开发者看来,这些都是学习JavaScript最基础的东西.因为平时都是用jQuery或者第三JS组件,所以对JavaScript基础学习不够重视.题目如下,问题是:2次alert分别输出什么结果? 复制代码 代码如下: <scr

怎样在JavaScript里写一个swing把数据插入数据库_基础知识

最终的目标是想这样的,在JavaScript里写一个swing来实现确定取消,来决定是否执行这个功能的,但是在执行的过程中,出现了一点问题,每次执行时,都是直接就会插入把数据插入数据库,不能控制了,想要知道应该怎样来解决这个问题,详情要参考下面的代码详情: 复制代码 代码如下: <% boolean foo=false; if (((theqingjiadays<3)&&(thetiqiandays>=1))||((theqingjiadays<10) &&

Javascript之旅——第八站:说说instanceof踩了一个坑

原文:Javascript之旅--第八站:说说instanceof踩了一个坑 前些天写js遇到了一个instanceof的坑,我们的页面中有一个iframe,我在index页面中计算得到了一个array,然后需要传递到Flight页面 这个嵌套的iframe中的一个函数(SearchFlight)中,作为防御性编程,我需要在SearchFlight函数中进行参数检测,也就是判断过来的参数一 定是Array类型.   一:抛出问题 举个例子,下面有两个页面. Index.html页面 1 <!DO