使用 Tini 清理 Docker 容器僵死進(jìn)程
最近在Tini的倉(cāng)庫(kù)下看到作者對(duì)Tini優(yōu)勢(shì)的精彩回復(fù),搬運(yùn)過來(lái),粗糙翻譯,獻(xiàn)給擁有同樣疑惑的你。
寫在前面
我們?cè)诓榭匆恍┐箜?xiàng)目的Dockerfile時(shí)經(jīng)常發(fā)現(xiàn),它們的ENTRYPOINT中往往都有tini的身影:


那Tini到底是什么?為什么大家都喜歡在鏡像中使用它呢?
開發(fā)者的疑問
我注意到Jenkins的官方鏡像中使用了Tini,所以我很好奇它是什么。它看起來(lái)一定很有用,可能解決了一些我不知道的問題。你能用“說(shuō)人話”的方式簡(jiǎn)單解釋一下Tini相對(duì)于直接以CMD運(yùn)行shell腳本的優(yōu)勢(shì)嗎?
我的幾個(gè)容器的ENTRYPOINT都設(shè)置了一個(gè) docker-entrypoint.sh 腳本,里面基本上都是以“exec "$@"”的方式在運(yùn)行,我應(yīng)該使用Tini來(lái)代替嗎?
來(lái)自作者的回復(fù)
問得好!但這解釋可能有點(diǎn)長(zhǎng),所以請(qǐng)耐心聽我說(shuō)(我知道你要求簡(jiǎn)短,但我真的做不到,捂臉~)。
首先,我們先簡(jiǎn)單聊聊Jenkins。當(dāng)您運(yùn)行Docker容器時(shí),Docker會(huì)將它與系統(tǒng)的其他部分隔離開來(lái)。這種隔離發(fā)生在不同的級(jí)別(例如網(wǎng)絡(luò)、文件系統(tǒng)、進(jìn)程)。
但Tini并不真正關(guān)注網(wǎng)絡(luò)或文件系統(tǒng),所以讓我們把注意力放在Tini的一個(gè)重要概念上:進(jìn)程。
每個(gè)Docker容器都是一個(gè)PID命名空間,這意味著容器中的進(jìn)程與主機(jī)上的其他進(jìn)程是隔離的。PID命名空間是一棵樹,從PID 1開始,通常稱為init。
注意:當(dāng)你運(yùn)行一個(gè)Docker容器時(shí),鏡像的ENTRYPOINT就是你的根進(jìn)程,即PID 1(如果你沒有ENTRYPOINT,那么CMD就會(huì)作為根進(jìn)程,你可能配置了一個(gè)shell腳本,或其他的可執(zhí)行程序,容器的根進(jìn)程具體是什么,完全取決于你的配置)。
與其他進(jìn)程不同的是,PID 1有一個(gè)獨(dú)特的職責(zé),那就是收割“僵尸進(jìn)程”。
那何為“僵尸進(jìn)程”呢?
“僵尸進(jìn)程”是指:
已經(jīng)退出。 沒有被其父進(jìn)程wait(wait是指syscall父進(jìn)程用于檢索其子進(jìn)程的退出代碼)。 父進(jìn)程已丟失(也就是說(shuō),它們的父進(jìn)程已經(jīng)不存在了),這意味著他們永遠(yuǎn)不會(huì)被其父進(jìn)程處理。
當(dāng)“僵尸進(jìn)程”被創(chuàng)建時(shí)(也就是說(shuō),一旦它的父進(jìn)程非正常退出了,它也就跟著無(wú)法正常退出了),它會(huì)繼承成為PID 1的子級(jí),最后PID 1會(huì)負(fù)責(zé)關(guān)閉它。
換句話說(shuō),有人必須在“不負(fù)責(zé)任”的父進(jìn)程離開后,對(duì)這些“孤兒”進(jìn)行清理,這是PID 1的作用。
請(qǐng)注意,創(chuàng)建“僵尸進(jìn)程”通常是不被允許的(也就是說(shuō),理想情況下,您應(yīng)該修復(fù)代碼,這樣就不會(huì)創(chuàng)建“僵尸進(jìn)程”),但是對(duì)于像Jenkins這種應(yīng)用來(lái)說(shuō),它們是不可避免的:因?yàn)镴enkins通常運(yùn)行的代碼不是由Jenkins維護(hù)者編寫的(也就是您的Jenkins構(gòu)建腳本),所以他們也無(wú)法“修復(fù)代碼”。
這就是Jenkins使用Tini的原因:在構(gòu)建了創(chuàng)建“僵尸進(jìn)程”的腳本后進(jìn)行清理。
但其實(shí)Bash實(shí)際上也做同樣的事情(收割“僵尸進(jìn)程”),所以你可能會(huì)想:為什么不把Bash當(dāng)作PID 1呢?
第一個(gè)問題是,如果您將Bash作為PID 1運(yùn)行,那么您發(fā)送到Docker容器的所有信號(hào)(例如,使用docker stop或docker kill)最終都會(huì)發(fā)送到Bash,Bash默認(rèn)不會(huì)將它們轉(zhuǎn)發(fā)到任何地方(除非您自己編寫代碼實(shí)現(xiàn))。換句話說(shuō),如果你使用Bash來(lái)運(yùn)行Jenkins,那么當(dāng)你運(yùn)行docker stop的時(shí)候,Jenkins將永遠(yuǎn)收不到停止信號(hào)!
而Tini通過“信號(hào)轉(zhuǎn)發(fā)”解決了這個(gè)問題:如果你向Tini發(fā)送信號(hào),那么它也會(huì)向你的子進(jìn)程發(fā)送同樣的信號(hào)(在你的例子中是Jenkins)。
第二個(gè)問題是,一旦您的進(jìn)程退出,Bash也會(huì)繼續(xù)退出。如果您不小心,Bash可能會(huì)退出,退出代碼為0,而您的進(jìn)程實(shí)際上崩潰了(但0表示“一切正常”;這將導(dǎo)致Docker重啟策略不符合您的預(yù)期)。因?yàn)槟嬲胍目赡苁荁ash返回與您的進(jìn)程相同的退出代碼。
請(qǐng)注意,您可以通過在Bash中創(chuàng)建信號(hào)處理程序來(lái)實(shí)際執(zhí)行轉(zhuǎn)發(fā),并返回適當(dāng)?shù)耐顺龃a來(lái)解決這個(gè)問題。另一方面,這需要做更多的工作,而添加Tini只是文檔中的幾行。
其實(shí)還有另一個(gè)解決方案可以將Jenkins作為PID 1運(yùn)行,即在Jenkins中添加另一個(gè)線程來(lái)負(fù)責(zé)收割“僵尸進(jìn)程”。
但這也不理想,原因有二:
首先,如果將Jenkins以PID 1的身份運(yùn)行,那么很難區(qū)分繼承給Jenkins的進(jìn)程(應(yīng)該被收割)和Jenkins自己產(chǎn)生的進(jìn)程(不應(yīng)該被收割,因?yàn)檫€有其他代碼已經(jīng)在等待它們執(zhí)行)。我相信你可以用代碼來(lái)解決這個(gè)問題,但還是要問一遍:當(dāng)你可以把Tini放進(jìn)去的時(shí)候,為什么還要寫呢?
其次,如果Jenkins以PID 1運(yùn)行,那么它可能不會(huì)接收到您發(fā)送的信號(hào)!
這是PID 1進(jìn)程中的微妙之處。與其他進(jìn)程不同的是,PID 1沒有默認(rèn)的信號(hào)處理程序,這意味著如果Jenkins沒有明確地為SIGTERM安裝信號(hào)處理程序,那么該信號(hào)在發(fā)送時(shí)將被丟棄(而默認(rèn)行為是終止該過程)。
Tini確實(shí)安裝了顯式信號(hào)處理程序(順便說(shuō)一下,是為了轉(zhuǎn)發(fā)信號(hào)),所以這些信號(hào)不再被丟棄。相反,它們被發(fā)送到Jenkins,Jenkins并不像PID 1(Tini )那樣運(yùn)行,因此有默認(rèn)的信號(hào)處理程序(注意:這不是Jenkins使用Tini的原因,Jenkins使用它來(lái)獲取信號(hào),但在RabbitMQ的鏡像中是這個(gè)作用)。
請(qǐng)注意,Tini中還有一些額外的功能,在Bash或Java中很難實(shí)現(xiàn)(例如,Tini可以注冊(cè)為“子收割者”,因此它實(shí)際上不需要作為PID 1運(yùn)行來(lái)完成“僵尸進(jìn)程”收割工作),但是這些功能對(duì)于一些高級(jí)應(yīng)用場(chǎng)景來(lái)說(shuō)非常有用。
希望以上內(nèi)容對(duì)你有所幫助!
如果您有興趣了解更多,以下是一些可供參考的資料:
僵尸進(jìn)程詳解:https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/ 更簡(jiǎn)潔的解釋:https://github.com/docker-library/official-images#init
最后,請(qǐng)注意Tini還有更多的選擇(比如Phusion的基礎(chǔ)鏡像)。
Tini的主要特性是:
做PID 1需要做的一切,而不做其他任何事情。像讀取環(huán)境文件、改變用戶、過程監(jiān)控等事情不在Tini的范圍內(nèi)(還有其他更好的工具); 零配置就能上手(如果運(yùn)行不正常,Tini >= 0.6也會(huì)警告您); 它有豐富的測(cè)試。
原文鏈接
What is advantage of Tini?:https://github.com/krallin/tini/issues/8#issuecomment-146135930譯文來(lái)源:https://zhuanlan.zhihu.com/p/59796137
