主頁 >  其他 > 【Pod Terminating原因追蹤系列之二】exec連接未關閉導致的事件阻塞

【Pod Terminating原因追蹤系列之二】exec連接未關閉導致的事件阻塞

2020-09-18 07:41:19 其他

前一陣有客戶docker18.06.3集群中出現Pod卡在terminating狀態的問題,經過排查發現是containerd和dockerd之間事件流阻塞,導致后續事件得不到處理造成的,

定位問題的程序極其艱難,其中不乏大量工具的使用和大量的原始碼閱讀,本文將梳理排查此問題的程序,并總結完整的dockerd和contaienrd之間事件傳遞流程,一步一步找到問題產生的原因,特寫本文記錄分享,希望大家在有類似問題發生時,能夠迅速定位、解決,

對于本文中提到的問題,在docker19中已經得到解決,但docker18無法直接升級到docker19,因此本文在結尾參考docker19給出了一種簡單的解決方案,

洗掉不掉Pod

相信大家在解決現網問題時,經常會遇到Pod卡在terminating不動的情況,產生這種情況的原因有很多,比如《Pod Terminating原因追蹤系列之一》中提到的containerd沒有正確處理錯誤資訊,當然更常見的比如umount失敗、dockerd卡死等等,

遇到此類問題時,通常通過kubelet或dockerd日志、容器和Pod狀態、堆疊資訊等手段來排查問題,本問題也不例外,首先登錄到Pod所在節點,使用以下兩條指令查看容器狀態:

#查看容器狀態,看到容器狀態為up
docker ps | grep <container-id>
#查看task狀態,顯示task的狀態為STOPPED
docker-container-ctr --namespace moby --address var/run/docker/containerd/docker-containerd.sock task ls | grep <container-id>

1

可以看到在dockerd中容器狀態為up,但在containerd中task狀態為STOPPED,兩者資訊產生了不一致,也就是說由于某種原因containerd中的狀態資訊沒有同步到dockerd中,為了探究為什么兩者狀態產生了不一致,首先需要了解從dockerd到containerd的整體呼叫鏈:

2

當啟動dockerd時,會通過NewClient方法創建一個client,該client維護一條到containerd的gRPC連接,同時起一個協程processEventStream訂閱(subscribe)來自containerd的task事件,當某個容器的狀態發生變化產生了事件,containerd會回傳事件到client的eventQ佇列中,并通過ProcessEvent方法進行處理,processEventStream協程在除優雅退出以外永遠不會退出(但在有些情況下還是會退出,在后續會推出一篇文章,恰好是這種情況,敬請期待~),

當容器行程退出時,containerd會通過上述gRPC連接回傳一個exit的task事件給client,client接收到來自containerd的exit事件之后由ProcessEvent呼叫DeleteTask介面洗掉對應task,至此完成了一個容器的洗掉,

由于containerd一直處于STOPPED狀態,因此通過上面的呼叫鏈猜測會不會是task exit事件因為某種原因而阻塞掉了?產生的結果就是在containerd側由于發送了exit事件而進入STOPPED狀態,但由于沒有呼叫DeleteTask介面,因此本task還存在,

模擬task exit事件

通過發送task exit事件給一個卡住的Pod,來模擬容器結束的情況:

/tasks/exit {"container_id":"23bd0b1118238852e9dec069f8a89c80e212c3d039ba030cfd33eb751fdac5a7","id":"23bd0b1118238852e9dec069f8a89c80e212c3d039ba030cfd33eb751fdac5a7","pid":17415,"exit_status":127,"exited_at":"2020-07-17T12:38:01.970418Z"}

我們可以手動將上述事件publish到containerd中,但是需要注意的一點的是,在publish之前需要將上述內容進行一下編碼(參考containerd/cmd/containerd-shim/main_unix.go Publish方法),得到的結果如下圖,可以看到事件成功的被publish,也被dockerd捕獲到,但容器的狀態仍然沒有變化,

#將file檔案中的事件發送到containerd
docker-containerd --address var/run/docker/containerd/docker-containerd.sock publish --namespace moby --topic /tasks/exit < ~/file

3

當我們查看docker堆疊日志(向dockerd行程發送SIGUSR1信號),發現有大量的Goroutine卡在append方法,每次publish新的exit事件都會增加一個append方法的堆疊資訊:

4

通過查看append方法的原始碼發現,append方法負責將收到的containerd事件放入eventQ,并執行回呼函式,對收到的不同型別事件進行處理:

func (q *queue) append(id string, f func()) {
    q.Lock()
    defer q.Unlock()
    if q.fns == nil {
        q.fns = make(map[string]chan struct{})
    }
    done := make(chan struct{})
    fn, ok := q.fns[id]
    q.fns[id] = done
    go func() {
        if ok {
            <-fn
        }
        f()
        close(done)
        q.Lock()
        if q.fns[id] == done {
            delete(q.fns, id)
        }
        q.Unlock()
    }()
}

形參中的id為container的id,因此對于同一個container它的事件是串行處理的,只有前一個事件處理結束才會處理下一個事件,且沒有超時機制,

因此只要eventQ中有一個事件發生了阻塞,那么在它后面所有的事件都會被阻塞住,這也就解釋了為什么每次publish新的對于同一個container的exit事件,都會在堆疊中增加一條append的堆疊資訊,因為它們都被之前的一個事件阻塞住了,

深入原始碼定位問題原因

為了找到阻塞的原因,我們找到阻塞的第一個exit事件append的堆疊資訊再詳細的看一下:

5

通過堆疊可以發現代碼卡在了docker/daemon/monitor.go檔案的123行(省略了不重要的代碼):

func (daemon *Daemon) ProcessEvent(id string, e libcontainerd.EventType, ei libcontainerd.EventInfo) error {
    ......
    case libcontainerd.EventExit:
        ......
        if execConfig := c.ExecCommands.Get(ei.ProcessID); execConfig != nil {
            ......
123行        execConfig.StreamConfig.Wait()
            if err := execConfig.CloseStreams(); err != nil {
                logrus.Errorf("failed to cleanup exec %s streams: %s", c.ID, err)
            }
            ......
        } else {
            ......
        }
    ......
    return nil
}

可以看到收到的事件為exit事件,并在第123行streamConfig在等待一個wg,這里的streamconfig為一個記憶體佇列,負責收集來自containerd的輸出回傳給客戶端,具體是如何處理io的在后面會細講,這里先順藤摸瓜查一下wg在什么時候add的:

func (c *Config) CopyToPipe(iop *cio.DirectIO) {
    copyFunc := func(w io.Writer, r io.ReadCloser) {
        c.Add(1)
        go func() {
            if _, err := pools.Copy(w, r); err != nil {
                logrus.Errorf("stream copy error: %v", err)
            }
            r.Close()
            c.Done()
        }()
    }
    if iop.Stdout != nil {
        copyFunc(c.Stdout(), iop.Stdout)
    }
    if iop.Stderr != nil {
        copyFunc(c.Stderr(), iop.Stderr)
    }
    .....
}

CopyToPipe是用來將containerd回傳的輸出copy到streamconfig的方法,可以看到當來自containerd的io流不為空,則會對wg add1,并開啟協程進行copy,copy結束后才會done,因此一旦阻塞在copy,則對exit事件的處理會一直等待copy結束,我們再回到docker堆疊中進行查找,發現確實有一個IO wait,并阻塞在polls.Copy函式上:

6

至此造成dockerd和containerd狀態不一致的原因已經找到了!我們來梳理一下,

首先通過查看dockerd和containerd狀態,發現兩者狀態不一致,由于containerd處于STOPPED狀態因此判斷在containerd發送task exit事件時可能發生阻塞,因此我們構造了task exit事件并publish到containerd,并查看docker堆疊發現有大量阻塞在append的堆疊資訊,證實了我們的猜想,

最后我們通過分析代碼和堆疊資訊,最終定位在ProcessEvent由于pools.Copy的阻塞,也會被阻塞,直到copy結束,而事件又是串行處理的,因此只要有一個事件處理被阻塞,那么后面所有的事件都會被阻塞,最終表現出的現象就是dockerd和containerd狀態不一致,

找出罪魁禍首

我們已經知道了阻塞的原因,但是究竟是什么操作阻塞了事件的處理?其實很簡單,此exit事件是由exec退出產生的,我們通過查看堆疊資訊,發現在堆疊有為數不多的ContainerExecStart方法,說明有exec正在執行,推測是客戶行為:

7

ContainerExecStart方法中第二個引數為exec的id值,因此可以使用gdb查找對應地址內容,查看其引數中的execId和terminating Pod中的容器的exexId(docker inspect可以查看execId,每個exec操作對應一個execId)是否一致,結果發現execId相同!因此可以斷定是由于exec退出,產生的exit事件阻塞了ProcessEvent的處理邏輯,通過閱讀原始碼總結出exec的處理邏輯:

8

那么為什么exec的exit會導致Write阻塞呢?我們需要梳理一下exec的io處理流程看看究竟Write到了哪里,下圖為io流的處理程序:

9

首先在exec開始時會將socket的輸出流attach到一個記憶體佇列,并啟動了?個goroutine用來把記憶體佇列中的內容輸出到socket中,除了記憶體佇列外還有一個FIFO佇列,通過CopyToPipe開啟協程copy到記憶體佇列,FIFO佇列用來接收containerd-shim的輸出,之后由記憶體佇列寫入socket,以response的方式回傳給客戶端,但我們的問題還沒有解決,還是不清楚為什么Write會阻塞住,不過可以通過gdb來定位到Write函式打開的fd,查看一下socket的狀態:

n, err := syscall.Write(fd.Sysfd, p[nn:max])
type FD struct {
    // Lock sysfd and serialize access to Read and Write methods.
    fdmu fdMutex
    // System file descriptor. Immutable until Close.
    Sysfd int
    ......
}

Write為系統呼叫,其引數中第一位即打開的fd號,但需要注意,Sysfd并非FD結構體的第一個引數,因此需要加上偏移量16位元組(fdMutex占16位元組)

10

11

發現該fd為一個socket連接,使用ss查看一下socket的另一端是誰:

12

發現該fd為來自kubelet的一個socket連接,且沒有被關閉,因此可以判斷Write阻塞的原因正是客戶端exec退出以后,該socket沒有正常的關閉,使Write不斷地向socket中寫資料,直到寫滿阻塞造成的,

通過詢問客戶是否使用過exec,發現客戶自己寫了一個客戶端并通過kubelet exec來訪問Pod,與上述排查結果相符,因此反饋客戶可以排查下客戶端代碼,是否正確關閉了exec的socket連接,

修復與反思

其實docker的這個事件處理邏輯設計并不優雅,客戶端的行為不應該影響到服務端的處理,更不應該造成服務端的阻塞,因此本打算提交pr修復此問題,發現在docker19中已經修復了此問題,而docker18的集群無法直接升級到docker19,因為docker會持久化資料到硬碟上,而docker19不支持docker18的持久化資料,

雖然不能直接升級到docker19,不過我們可以參考docker19的實作,在docker19中通過添加事件處理超時的邏輯避免事件一直阻塞,在docker18中同樣可以添加一個超時的邏輯!

對exit事件添加超時處理:

#/docker/daemon/monitor.go
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
execConfig.StreamConfig.WaitWithTimeout(ctx)
cancel()
#/docker/container/stream/streams.go
func (c *Config) WaitWithTimeout(ctx context.Context) {
    done := make(chan struct{}, 1)
    go func() {
        c.Wait()
        close(done)
    }()
    select {
    case <-done:
    case <-ctx.Done():
        if c.dio != nil {
            c.dio.Cancel()
            c.dio.Wait()
            c.dio.Close()
        }
    }
}

這里添加了一個2s超時時間,超時則優雅關閉來自containerd的事件流,

至此一個棘手的Pod terminating問題已經解決,后續也將推出小版本修復此問題,雖然修復起來比較簡單,但問題分析的程序卻無比艱辛,希望本篇文章能夠對大家今后的問題定位打開思路,謝謝觀看~
【騰訊云原生】云說新品、云研新術、云游新活、云賞資訊,掃碼關注同名公眾號,及時獲取更多干貨!!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/71948.html

標籤:其他

上一篇:二叉樹基本操作問題

下一篇:Microsoft 365 解決方案:如何退出其他組織的Microsoft Teams?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more