靈魂一問:為什么你的 Docker 容器剛啟動(dòng)就停了
很多docker初學(xué)者,在運(yùn)行容器的時(shí)候,或者是寫第一個(gè)dockerfile的時(shí)候,問題最多的就是容器啟動(dòng)后就停了,怎么看都覺得命令沒有問題,容器也沒有錯(cuò)誤日志,dockerfile也就那么幾條……
其實(shí)你沒有錯(cuò),錯(cuò)的是docker,它執(zhí)行的太快了
這話怎么說呢,我拿nginx官方的dockerfile給你解釋下。
上面是nginx官方的dockerfile文件,我把set部分刪掉了,其他沒啥,主要看下CMD
為什么這里不是systemctl nginx start
,或者/etc/init.d/nginx start
,再或者nginx直接啟動(dòng),而是用daemon off的方式啟動(dòng)?
這是因?yàn)槿绻鹡ginx用后臺(tái)模式運(yùn)行,啟動(dòng)的命令執(zhí)行完之后,這個(gè)啟動(dòng)的命令就退出了,這個(gè)時(shí)候,容器也就跟著退出了
又為什么命令執(zhí)行完,容器就退出了?這個(gè)要從linux內(nèi)核說起。
在linux操作系統(tǒng)中,當(dāng)內(nèi)核初始化完畢之后,會(huì)啟動(dòng)一個(gè)init進(jìn)程,這個(gè)進(jìn)程是整個(gè)操作系統(tǒng)的第一個(gè)用戶進(jìn)程,所以它的進(jìn)程ID為1,也就是我們常說的PID1進(jìn)程,然后所有的用戶態(tài)進(jìn)程,都是這個(gè)進(jìn)程的子進(jìn)程,所以,整個(gè)系統(tǒng)的用戶進(jìn)程,都是由init進(jìn)程作為根進(jìn)程的
要了解這個(gè)PID1進(jìn)程,要從以下幾個(gè)概念了解:
進(jìn)程表項(xiàng)
linux內(nèi)核程序通過進(jìn)程表對(duì)進(jìn)程進(jìn)行管理, 每個(gè)進(jìn)程在進(jìn)程表中占有一項(xiàng),稱為進(jìn)程表項(xiàng),它記錄了進(jìn)程的狀態(tài),打開的文件描述符等等一系統(tǒng)信息。當(dāng)一個(gè)進(jìn)程結(jié)束了運(yùn)行或在半途中終止了運(yùn)行,那么內(nèi)核就需要釋放該進(jìn)程所占用的系統(tǒng)資源。這包括進(jìn)程運(yùn)行時(shí)打開的文件,申請(qǐng)的內(nèi)存等。
但是,這里要注意的是,進(jìn)程表項(xiàng)并沒有隨著進(jìn)程的退出而被清除,它會(huì)一直占用內(nèi)核的內(nèi)存。為什么會(huì)有這么奇怪的行為呢?這是因?yàn)樵谀承┏绦蛑?,我們必須明確地知道進(jìn)程的退出狀態(tài)等信息,而這些信息的獲取是由父進(jìn)程調(diào)用wait/waitpid而獲取的。
設(shè)想這樣一種場(chǎng)景,如果子進(jìn)程在退出的時(shí)候直接清除文件表項(xiàng)的話,那么父進(jìn)程就很可能沒有地方獲取進(jìn)程的退出狀態(tài)了,因此操作系統(tǒng)就會(huì)將文件表項(xiàng)一直保留至wait/waitpid系統(tǒng)調(diào)用結(jié)束。
僵尸進(jìn)程
僵尸進(jìn)程指的是:進(jìn)程退出后,到其父進(jìn)程還未對(duì)其調(diào)用wait/waitpid之間的這段時(shí)間所處的狀態(tài)。一般來說,這種狀態(tài)持續(xù)的時(shí)間很短,所以我們一般很難在系統(tǒng)中捕捉到。但是,一些粗心的程序員可能會(huì)忘記調(diào)用wait/waitpid,或者由于某種原因未執(zhí)行該調(diào)用等等,那么這個(gè)時(shí)候就會(huì)出現(xiàn)長(zhǎng)期駐留的僵尸進(jìn)程了。如果大量的產(chǎn)生僵尸進(jìn)程,其進(jìn)程號(hào)就會(huì)一直被占用,可能導(dǎo)致系統(tǒng)不能產(chǎn)生新的進(jìn)程。
然后還有我們經(jīng)常會(huì)見到的一種情況,就是父進(jìn)程先于子進(jìn)程結(jié)束,這種情況多見于手動(dòng)kill某個(gè)父進(jìn)程的情況,這種情況就是下面要說到的
孤兒進(jìn)程
父進(jìn)程先于子進(jìn)程退出,那么子進(jìn)程將成為孤兒進(jìn)程。孤兒進(jìn)程將被init進(jìn)程(進(jìn)程號(hào)為1)接管,并由init進(jìn)程對(duì)它完成狀態(tài)收集(wait/waitpid)工作
PID1負(fù)責(zé)清理那些被拋棄的進(jìn)程所留下來的痕跡,有效的回收的系統(tǒng)資源,保證系統(tǒng)長(zhǎng)時(shí)間穩(wěn)定的運(yùn)行
了解了linux的PID1,接著來看下容器中的PID1進(jìn)程
熟悉docker都知道,docker容器并不是一個(gè)完整的linux的操作系統(tǒng),它也沒什么內(nèi)核初始化過程,更沒有像init(1)這樣的初始化過程。在docker容器中被標(biāo)志為PID1的進(jìn)程實(shí)際上就是一個(gè)普通的用戶進(jìn)程,我們還拿nginx官方的鏡像起的容器來看。
我用docker run -d nginx直接啟動(dòng)的
可以看到,就是Dockerfile中指定的CMD那個(gè)進(jìn)程,注意:如果你啟動(dòng)容器的時(shí)候,指定了命令,會(huì)覆蓋CMD,也就是CMD是條默認(rèn)啟動(dòng)的命令參數(shù),如果啟動(dòng)容器時(shí)指定了命令,會(huì)覆蓋,當(dāng)Dockerfile中有多條CMD時(shí),執(zhí)行最后一條
這個(gè)進(jìn)程其實(shí)在宿主機(jī)上有一個(gè)普通的用戶進(jìn)程ID
之所以在容器中PID變成1,是因?yàn)閘inux內(nèi)核提供的PID namespaces功能,如果宿主機(jī)上所有用戶進(jìn)程構(gòu)成了一個(gè)完整的樹形結(jié)構(gòu),那么PID namespaces實(shí)際上就是將這個(gè)CMD或ENTRYPOINT進(jìn)程及其子進(jìn)程作為另外一個(gè)分支,很顯然這部分也是一個(gè)樹形結(jié)構(gòu)
當(dāng)我們?cè)谒拗鳈C(jī)上kill掉這個(gè)進(jìn)程ID,那么整個(gè)容器便會(huì)處于退出狀態(tài)
這也就解釋了上面為什么命令執(zhí)行完之后,容器就退出了
認(rèn)真的小伙伴從上面圖中看到了,我上面說linux中PID1進(jìn)程為所有用戶進(jìn)程的父進(jìn)程,但是在容器里面,通過ps命令看到的進(jìn)程的父進(jìn)程都是“0”,這又是為什么呢?
前面提到,容器中的進(jìn)程樹實(shí)際上是宿主機(jī)進(jìn)程樹的一棵子樹,或者說分支,那么我們?cè)谒拗鳈C(jī)上就可以找到這顆子樹的父進(jìn)程。
我們可以看到,這個(gè)docker容器中PID 0的進(jìn)程應(yīng)該就是這個(gè)containerd-shim
我們結(jié)合docker的結(jié)構(gòu)圖看一下
從架構(gòu)圖中,我們可以看到containerd-shim進(jìn)程下還有一個(gè)runC進(jìn)程,但是我們?cè)谏厦孢^程中,并沒有發(fā)現(xiàn)runC這個(gè)進(jìn)程
runC是OCI標(biāo)準(zhǔn)的一個(gè)參考實(shí)現(xiàn),而OCI Open Container Initiative,是由多家公司共同成立的項(xiàng)目,并由linux基金會(huì)進(jìn)行管理,致力于container runtime的標(biāo)準(zhǔn)的制定和runc的開發(fā)等工作。runc,是對(duì)于OCI標(biāo)準(zhǔn)的一個(gè)參考實(shí)現(xiàn),是一個(gè)可以用于創(chuàng)建和運(yùn)行容器的CLI(command-line interface)工具。
runc直接與容器所依賴的cgroup/linux kernel等進(jìn)行交互,負(fù)責(zé)為容器配置cgroup/namespace等啟動(dòng)容器所需的環(huán)境,創(chuàng)建啟動(dòng)容器的相關(guān)進(jìn)程
事實(shí)上,Docker容器的創(chuàng)建過程是這樣子的 docker-containerd-shim –> runC –> entrypoint,而我們看到的最終狀態(tài)是 docker-containerd-shim –> entrypoint,而runc進(jìn)程創(chuàng)建完容器之后,自己就先退出去了,所以我們上面的過程中一直沒有出現(xiàn)
看到這里你應(yīng)該了解,為什么你啟動(dòng)容器或?qū)懞玫膁ockerfile,總是剛啟動(dòng)就退出,而且沒有任何錯(cuò)誤了吧!