<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Kubernetes 中網(wǎng)站無法訪問,深入排查實(shí)戰(zhàn)

          共 6004字,需瀏覽 13分鐘

           ·

          2021-07-28 04:53

          原文鏈接:https://baijiahao.baidu.com/s?id=1705314379669553507
          開篇點(diǎn)題, 這其實(shí)是一次深入探索問題本質(zhì)的一次排查故事,之所以想寫這個(gè),是因?yàn)檫@個(gè)問題的現(xiàn)象和最后分析出來的原因看起來有點(diǎn)千差萬別。因?yàn)楦杏X排查過程可以抽象成一個(gè)通用的排查思維邏輯, 所以各位看完后可以這個(gè)抽象是否做成功了



          起(問題發(fā)生)


          故事的起因和大多數(shù)排查故事一樣, 并沒有什么特別的.就是普通的一天早上,正帶著愉快心情上班時(shí),突然被拉了一個(gè)會(huì)議,然后老板在會(huì)議中特別著急的表達(dá)了問題以及嚴(yán)重性,于是我也特別著急的開始了排查。

          問題也是很普通,外部大客戶發(fā)現(xiàn)一個(gè)容器里的應(yīng)用無法響應(yīng)請(qǐng)求了,特別著急的找到了我們這邊。

          從會(huì)議中聽到的內(nèi)容總結(jié)了一下, 大致是容器里的一個(gè)server進(jìn)程沒法響應(yīng)http請(qǐng)求,我包括其他同學(xué)理所當(dāng)然的以為容器網(wǎng)絡(luò)可能出問題了,然后我登錄到宿主機(jī)上, 按套路查看容器網(wǎng)絡(luò)聯(lián)通性,路由等,發(fā)現(xiàn)網(wǎng)絡(luò)正常沒有任何問題,折騰完了之后完全一臉懵,不知道到底是啥情況


          承(開始排查,居然不是網(wǎng)絡(luò)問題)


          按正常套路排查沒有任何結(jié)果后,我又咨詢了上層應(yīng)用同學(xué)關(guān)于服務(wù)的信息, 希望從用戶的部署服務(wù)類型看出一點(diǎn)信息,上層應(yīng)用同學(xué)從k8s集群中查看了pod的信息,發(fā)現(xiàn)是一個(gè)普通的java應(yīng)用,參數(shù)也沒有奇怪的地方.

          這里簡直沒有頭緒,我又問了一下問題出現(xiàn)前線上有沒有做變更,結(jié)果果然有做,昨天晚上剛更新了容器引擎的版本,也就是說容器引擎被重啟了.正是因?yàn)槲覍?duì)容器太了解了,理所當(dāng)然的覺得容器引擎重啟不會(huì)對(duì)已運(yùn)行的容器有任何影響,所以暫時(shí)對(duì)這個(gè)線索不是很上心.

          到了這,線上的排查基本結(jié)束,線上的問題只能先重新創(chuàng)建pod來解決.當(dāng)時(shí)對(duì)k8s還不熟,我們請(qǐng)上層同學(xué)先嘗試復(fù)現(xiàn)問題,然后再進(jìn)行線下的排查.也多虧了一位同學(xué)線下復(fù)現(xiàn)出了問題,排查才又有了進(jìn)展

          復(fù)現(xiàn)時(shí)用kubectl查看pod日志時(shí)偶然發(fā)現(xiàn)當(dāng)應(yīng)用請(qǐng)求卡住的時(shí)候,容器的標(biāo)準(zhǔn)輸出也斷了.結(jié)合線上用戶的bug案例,作出了一個(gè)簡單的分析,應(yīng)該是應(yīng)用進(jìn)程在響應(yīng)用戶請(qǐng)求時(shí)需要打印一些內(nèi)容,當(dāng)這個(gè)步驟卡住時(shí),就無法繼續(xù)響應(yīng)請(qǐng)求,表面上看就是用戶的請(qǐng)求卡住了.排查進(jìn)入到這里,距離發(fā)現(xiàn)最終bug的根因就比較近了



          轉(zhuǎn)(定位根因)


          問題的觸發(fā)的條件

          1.   進(jìn)程要向容器標(biāo)準(zhǔn)輸出打印日志
          2.   容器引擎重啟

          問題觸發(fā)是因?yàn)槿萜饕嬷貑⒂|發(fā)的,重啟后發(fā)現(xiàn)容器的標(biāo)準(zhǔn)輸出就斷了,容器里的進(jìn)程也無法響應(yīng)請(qǐng)求了,并且通過debug發(fā)現(xiàn)容器收到了SIGPIPE的信號(hào).再結(jié)合容器是如何轉(zhuǎn)發(fā)stdio到容器引擎的原理,基本上定位了原因,原來是容器用來轉(zhuǎn)發(fā)stdio的fifo(linux 命名管道)斷了.去看了線上shim打開的fifo fd已經(jīng)被關(guān)閉也確認(rèn)了這點(diǎn)

          原因定位了,但是代碼bug還沒有找出來,雖然我當(dāng)時(shí)對(duì)容器非常熟,但是我對(duì)fifo的工作原理可非常不熟,如果當(dāng)時(shí)我對(duì)fifo的原理了解的話,可能下午就定位出了問題,不至于用了一天的時(shí)間(這個(gè)問題后續(xù)又發(fā)生了一次,也是fifo的問題,但是是另外一個(gè)bug,第二次等我自己線下復(fù)現(xiàn)之后,下午就定位出來了).

          下面先介紹一下和問題相關(guān)的fifo部分的工作原理

          不打開fifo讀端或多次重新打開讀端, 只寫方式打開fifo寫端, 若寫入fifo里的數(shù)據(jù)超過緩沖區(qū),fifo寫端報(bào)EPIPE(Broken pipe)錯(cuò)誤退出, 發(fā)出SIGPIPE的信號(hào).如果讀寫方式打開fifo寫端,就不會(huì)有這個(gè)問題

          對(duì)比了問題代碼,打開fifo的方式正是O_WRONLY的方式,之前沒有出問題居然是因?yàn)閺膩頉]有更新過容器引擎,昨天晚上第一次更新直接觸發(fā)了這個(gè)問題.困擾大家一天的問題竟然只需要改一個(gè)單詞,把O_WRONLY -> O_RDWR就可以了

          也可以用下面這段簡單的代碼來自行驗(yàn)證一下

           

          好了,問題分析完了,下面我要開始寫容器引擎接管stdio的原理了,對(duì)容器部分原理沒有興趣的同學(xué)可以直接跳到"合"的章節(jié)了



          原理解析(容器創(chuàng)建原理及接管stdio)


          稍微提一下,出問題的不是runc容器,是kata安全容器, runc容器畢竟用的多bug也比較少了
           
          以下原理解析我都以pouch(https://github.com/alibaba/pouch)+ containerd(https://github.com/containerd/containerd)+ runc(https://github.com/opencontainers/runc.git)的方式來做分析

          從低向上容器1號(hào)進(jìn)程IO的流轉(zhuǎn)

          進(jìn)程的stdio指向pipe一端 -> shim進(jìn)程打開的pipe另一端 ->shim進(jìn)程打開的fifo寫端 -> pouch打開的fifo寫端 -> pouch指定的IO輸出地址,默認(rèn)是json文件

          容器IO的創(chuàng)建和是否需要terminal,是否有stdin有關(guān)系,為了簡單起見,我們下面的流程介紹都是后臺(tái)運(yùn)行一個(gè)容器為例來講解,即只會(huì)創(chuàng)建容器的stdout和stderr,用pouch命令來表述,就是執(zhí)行下面的命令后,如何從pouch logs看到進(jìn)程的日志輸出
          pouch run -d nigix

          pouch創(chuàng)建容器與初始化IO


          簡單介紹一下pouch創(chuàng)建容器的流程,pouch和dokcer一樣,也是基于containerd去管理容器的,即pouch啟動(dòng)會(huì)拉起一個(gè)containerd進(jìn)程,pouch發(fā)起各種容器相關(guān)的請(qǐng)求時(shí),通過grpc和containerd通信,containerd收到請(qǐng)求后,調(diào)用對(duì)應(yīng)的runtime接口操作容器,這里的runtime可以有很多類型,大家最常用的就是runc了,當(dāng)然也可以是上方案例中的kata安全容器,你也可以按照oci標(biāo)準(zhǔn)自己實(shí)現(xiàn)一個(gè)自己runtime,這是題外話了.

          pouch調(diào)用containerd的NewTask接口發(fā)起一個(gè)創(chuàng)建容器命令,這個(gè)函數(shù)的第二個(gè)參數(shù)是初始化IO的函數(shù)指針,看一下代碼,https://github.com/alibaba/pouch/blob/master/ctrd/container.go#L677-L685

          初始化IO,也就是創(chuàng)建fifo并打開fifo讀端的函數(shù)在NewTask執(zhí)行的第一行就會(huì)被調(diào)用

          pouch也是調(diào)用containerd中的cio包去打開fifo的,pouch指定了fifo路徑,最終調(diào)用containerd中的fifo包去創(chuàng)建并打開fifo, 用的圖中的fifo.OpenFifo函數(shù)

           

          看一下圖中的代碼,cio包里代碼是只讀阻塞的模式(雖然flags傳了NONBLOCK,但是在fifo包里會(huì)被去掉)打開stdout和stderr2個(gè)fifo的,pouch打開2個(gè)fifo后,會(huì)開始拷貝2個(gè)容器IO流,io.Copy的讀端是fifo的輸入,寫端是可以自定義的,寫端可以是json文件,syslog或其他.換個(gè)說法,這里的寫端就是容器引擎配置的log-driver

          這里打開fifo的部分要注意一下,containerd fifo包封裝了整個(gè)流程,和直接調(diào)用是不一樣的,最直接看出不同的地方就是打開文件的個(gè)數(shù),重新放一張上面案例中發(fā)過的示例圖,代碼里對(duì)stdout和stderr2個(gè)fifo文件只打開了一次,但是這個(gè)fd顯示文件被打開了2次,這是因?yàn)閒ifo包里對(duì)fifo的處理加了一層,打開了2次,第一次打開的是fifo文件,即下面的路徑,第二次按參數(shù)指定的flag打開了第一次打開的fd文件, 即/proc/self/fd/22.

          之所以打開2次是為了fifo文件在物理上被刪除后,內(nèi)存中打開的fd也可以被關(guān)閉
           

          containerd創(chuàng)建容器與初始化IO

           
          還是先介紹一下containerd創(chuàng)建容器的大致原理,其實(shí)這里還有一個(gè)shim進(jìn)程,準(zhǔn)確來說,shim是實(shí)際管理容器進(jìn)程,也就是說shim是容器1號(hào)進(jìn)程的父進(jìn)程,containerd和shim之間通過ttrpc交互(ttrpc是containerd社區(qū)實(shí)現(xiàn)的低內(nèi)存占用的grpc版本),containerd收到創(chuàng)建容器請(qǐng)求時(shí),會(huì)創(chuàng)建一個(gè)shim進(jìn)程,然后通過ttrpc發(fā)送后續(xù)的相關(guān)請(qǐng)求.

          shim創(chuàng)建容器的同時(shí)會(huì)初始化容器IO,相關(guān)代碼可以看一下這幾個(gè)文件,https://github.com/containerd/containerd/tree/master/pkg/process
          shim先創(chuàng)建os.Pipe,因?yàn)檫@個(gè)容器只需要stdout和stderr,所以這里只會(huì)創(chuàng)建stdout和stderr的2個(gè)pipe,作用是其中一端用來作為容器1號(hào)進(jìn)程的輸入和輸出,另一端輸出到pouch創(chuàng)建的fifo里, 這樣pouch就讀到了容器進(jìn)程的標(biāo)準(zhǔn)輸出

          看一下下面這張圖,cmd封裝了shim調(diào)用runccreate, cmd的stdio就是容器進(jìn)程的stdio, 這里的原因在第3步runc創(chuàng)建容器里細(xì)講

          調(diào)用runc create返回后,shim開始拷貝容器IO到pouch創(chuàng)建的fifo里,代碼在這里,https://github.com/containerd/containerd/blob/master/pkg/process/io.go#L135-L232

          下面這張圖是拷貝stdout的IO流的邏輯, 拷貝stderr也類似,rio.Stdout() 是上面shim創(chuàng)建的pipe的另外一端

          看一下rio.Stdout() 函數(shù)就知道了

          i.out.w作為cmd的STDOUT,就是說容器進(jìn)程輸出到i.out.w,pipe的另一端i.out.r讀到數(shù)據(jù),再把數(shù)據(jù)拷貝到fifo里,wc是只寫方式打開的pouch fifo文件,結(jié)合第一步里的過程, pouch讀方式打開的fifo讀到這里的輸入數(shù)據(jù),就拿到了容器進(jìn)程輸出

          看一下shim進(jìn)程打開的fd,發(fā)現(xiàn)stdout和stderr fifo都打開了2次,這是因?yàn)椴淮蜷_fifo讀端或多次重新打開讀端, 只寫方式打開fifo寫端, 若寫入fifo里的數(shù)據(jù)超過緩沖區(qū),fifo寫端報(bào)EPIPE (Broken pipe)錯(cuò)誤退出,所以這里分別用讀寫方式打開了2次fifo

          runc 創(chuàng)建容器與初始化IO

          這里是最后一個(gè)創(chuàng)建容器的步驟, containerd調(diào)用實(shí)際的容器運(yùn)行時(shí)創(chuàng)建容器,我以大家最常用的runc來做介紹

          這里插一句,案例里的kata安全容器也是一種OCI標(biāo)準(zhǔn)的運(yùn)行時(shí),簡單來說安全容器就是有自己的內(nèi)核,不和宿主機(jī)共享內(nèi)核,這樣才是安全可靠的.kata是基于qemu來做, 可以理解他有2層,第一層在宿主機(jī)上,和qemu以及qemu里的進(jìn)程交互,第二層在qemu里,接收第一層發(fā)來的請(qǐng)求,實(shí)際完成的代碼就是封裝了runc的libcontainer.所以kata的stdio相比于runc多轉(zhuǎn)發(fā)了一次

          同樣我先簡單概括一下runc創(chuàng)建容器的流程,shim創(chuàng)建容器需要調(diào)用2次runc,第一次是runccreate,這個(gè)命令完成后,容器的用戶進(jìn)程還沒有被拉起,runc 啟動(dòng)了一個(gè)init進(jìn)程,這個(gè)init進(jìn)程把容器啟動(dòng)的所有準(zhǔn)備都做完, 包括切換ns,cgroup隔離,掛載鏡像rootfs, volume等,runc init進(jìn)程最后會(huì)向一個(gè)fifo(和pouch fifo沒有關(guān)系, runc自己用的一個(gè)fifo文件)寫0,在0被讀取出來之前runc init會(huì)一直hang著

          shim的第二次調(diào)用是runcstart,runc start做的工作很簡單,從fifo中讀出數(shù)據(jù),這時(shí)hang住的runc init會(huì)往下執(zhí)行,調(diào)用execve加載用戶進(jìn)程, 這時(shí)容器的用戶進(jìn)程才開始運(yùn)行

          在介紹runc創(chuàng)建容器IO之前,我們先看一下容器進(jìn)程的stdio的fd指向吧,因?yàn)闆]有標(biāo)準(zhǔn)輸入,所以進(jìn)程0號(hào)fd是指向/dev/null的,1號(hào)和2號(hào)fd分別指向了一個(gè)pipe,這個(gè)pipe就是第二步里shim創(chuàng)建的pipe

          可以打開shim進(jìn)程的proc文件確認(rèn)一下, 13和15號(hào)fd打開的fd號(hào)是和容器進(jìn)程打開的2個(gè)pipe是一樣的, 說明2個(gè)進(jìn)程打開的是同樣的pipe


          上面說到runc啟動(dòng)的第一個(gè)進(jìn)程是runc init, 啟動(dòng)進(jìn)程的流程同樣也是封裝了一個(gè)cmd命令,cmd的stdio是指向process的stdio

          這個(gè)process在runc中代表了init或exec的進(jìn)程,當(dāng)容器不需要tty時(shí),runc把process的stdio設(shè)置為繼承自身的stdio,圖中的os.Stdin/os.Stdout/os.Stderr指的是本進(jìn)程的0,1,2 fd

          所以當(dāng)真正的容器進(jìn)程啟動(dòng)的時(shí)候自然也繼承了runc init的stdio


          合(后記)


          看起來是個(gè)網(wǎng)絡(luò)問題,最后發(fā)現(xiàn)是一個(gè)fifo的問題,但是循序漸進(jìn)的分析下來,感覺一切都是合情合理的
           
          類似排查網(wǎng)絡(luò)問題的套路一樣,問題排查一樣也有套路(抽象方法)可循,也看過這方面的總結(jié),但還是寫下自己的理解,當(dāng)套路被壓縮到極致之后,就變成了高大上的邏輯思維方式
           
          1.   詳細(xì)分析問題出現(xiàn)的現(xiàn)象,問題進(jìn)程的大致工作流程,問題觸發(fā)的條件
          2.   不要憑經(jīng)驗(yàn)判斷哪些組件不會(huì)出問題,詳細(xì)分析組件日志和代碼,尤其不要對(duì)任何代碼有敬畏之心(不敬畏,但是尊重所有代碼),尤其不要認(rèn)為內(nèi)核,系統(tǒng)庫都是基本穩(wěn)定的
          3.   問題鏈路上涉及的原理最好都去學(xué)習(xí)熟悉

          希望上述排查思路可以給其他同學(xué)定位問題時(shí)帶來靈感

          - END -

           推薦閱讀 

          Kubernetes實(shí)戰(zhàn)指南:從零到架構(gòu)師的進(jìn)階之路 
          Nginx 常用配置清單
          Prometheus + Thanos 多集群架構(gòu)監(jiān)控
          Jenkins 流水線自動(dòng)化部署 Go 項(xiàng)目
          最強(qiáng)整理!常用正則表達(dá)式速查手冊
          運(yùn)維的工作邊界,這次真的搞明白了!
          Linux 這些工具堪稱神器!
          Prometheus + Granafa 構(gòu)建高大上的MySQL監(jiān)控平臺(tái)
          搭建一套完整的企業(yè)級(jí) K8s 集群(v1.20,kubeadm方式)
          12年資深運(yùn)維老司機(jī)的成長感悟



          點(diǎn)亮,服務(wù)器三年不宕機(jī)

          瀏覽 85
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产三级在线播放一 | 三级黄色小说 | 啪啪啪视频官网 | 尻屄尻美女屄屄网 | 精品人妻一区二区三区日产乱码 |