一個疑難故障,坑了我半年青春-----知識就是生產力

做者介紹html

林偉壕,網易遊戲資深運維工程師。現任職於網易遊戲,從事遊戲運維相關工做;曾就任於中國電信,負責數據網絡維護、網絡安全防護等工做。深刻研究Linux運維、虛擬化等,現致力於企業級網絡安全防禦自動化體系構建。linux

相對物理環境,虛擬化環境更加錯綜複雜。以前弄KVM虛擬化時常常遇到好屢次莫名其妙的網絡故障,查出來的緣由要麼是操做系統內核bug,要麼是KVM與操做系統內核版本不兼容,最後是經過升級操做系統內核或者KVM版本修復了。沒想到,轉型到Docker後,又重蹈覆轍了。git

本文將介紹一個困擾筆者近半年的虛擬化環境下的疑難故障,最後排查出來的故障緣由和修復手段也讓人哭笑不得。並不是由於這個過程有多複雜,而是分享一個心理歷程,思考在遇到故障時如何兼顧業務和技術,如何正確使用搜索引擎。github

故障現象

咱們有一套高性能代理集羣,以前內測階段運行穩定,結果等正式上線後不到半個月,提供代理服務的宿主忽然連續不斷死機,致使宿主上的全部服務所有中斷。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,而後引發一系列調用棧報錯,最後死機。架構

爲進一步分析故障現象,首先須要理解這套高性能代理集羣的架構。app

架構介紹

架構

單個節點,是在萬兆網卡的宿主機上跑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

初步分析

該集羣的宿主配置一致,故障現象也一致,疑點有三個:

一、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

相關文章
相關標籤/搜索