背景
我們內部壓力(cpu 80%凸主,內存90%)通過stress (做頁面壓力測試)在容器內部做測試中厕诡,發(fā)現(xiàn)某幾個時候通過
docker stop $containerid
docker cli退出后,短暫時間內docker ps查看到容器依然在運行狀態(tài)削解。但是很快docker ps查看容器或者ps查看容器主進程pid就可以確認容器推出了富弦。我們需要解釋一下Docker stop發(fā)生了什么
Docker主要執(zhí)行流程
Docker Stop主要流程
1.Docker 通過containerd向容器主進程發(fā)送SIGTERM信號后等待一段時間后,如果從containerd收到了容器退出消息那么容器退出成功氛驮。
2腕柜、在上一步中,如果等待超時矫废,那么Docker將使用Docker kill 方式試圖終止容器
Docker Kill主要流程
1.Docker引擎通過containerd使用SIGKILL發(fā)向容器主進程盏缤,等待一段時間后,如果從containerd收到容器退出消息蓖扑,那么容器Kill成功
2.在上一步中如果等待超時唉铜,Docker引擎將跳過Containerd自己親自動手通過kill系統(tǒng)調用向容器主進程發(fā)送SIGKILL信號。如果此時kill系統(tǒng)調用返回主進程不存在律杠,那么Docker kill成功潭流。否則引擎將一直死等到containerd通過引擎,容器退出柜去。
Docker stop中存在的問題
在上文中我們看到Docker stop首先間接向容器主進程發(fā)送sigterm信號試圖通知容器主進程優(yōu)雅退出幻枉。但是容器主進程如果沒有顯示處理sigterm信號的話,那么容器主進程對此過程會不會有任何反應诡蜓,此信號被忽略了 這里和常規(guī)認識不同,在常規(guī)想法中任何進程的默認sigterm處理應該是退出胰挑。但是namespace中pid==1的進程蔓罚,sigterm默認動作是忽略。也即是容器首進程如果不處理sigterm瞻颂,那么此信號默認會被忽略豺谈,這就是很多時候Docker Stop不能立即優(yōu)雅關閉容器的原因——因為容器主進程根本沒有處理SIGTERM
特別指出linux上全局范圍內pid=1的進程,不能被sigterm贡这、sigkill茬末、sigint終止
進程組首進程退出后,子進程收到sighub
在bash shell里可以通過trap命令捕獲發(fā)往shell的信號,如果docker的主進程是shell進程的話丽惭,可以通過trap命令實現(xiàn)SIGTERM信號的捕獲和處理:
term_func(){
echo “receiving SIGTERM”
kill -s SIGTERM $1
}
pid=
trap “ term_func $pid” TERM
Your command & pid = $!
wait
wait 命令的意思是等待所有子進程退出击奶。放在這里是因為,trap命令只能等前臺運行的命令結束后才能處理信號责掏,但是wait命令會在收到信號后立即退出柜砾,所以將命令后臺化以后加wait,可以保證腳本對信號的即時響應换衬。關于shell里通過trap命令處理信號的詳細使用方式見《shell trap信號處理》《Sending and Trapping Signals》
Docker kill為何會阻塞
容器主/子進程處于D狀態(tài)
進程D狀態(tài)表示進程處于不可中斷睡眠狀態(tài)痰驱,一般都是在等待IO資源。當然有些時候如果系統(tǒng)IO出現(xiàn)問題瞳浦,那么將有大量的進程處于D狀態(tài)担映。在這種狀態(tài),信號是無法將進程喚醒叫潦;只有等待進程自己從D狀態(tài)中返回蝇完。而且在常規(guī)內核中,如果某個進程一直處于D狀態(tài)诅挑,那么理論上除了重啟系統(tǒng)那么沒有什么方法或手段將它從D中接回四敞。
從上面解釋Docker kill第二步中可以看到一旦容器中主進程或者子進程處于D狀態(tài),那么Docker將等待拔妥,一直等到所有容器主進程和其子進程都退出后才返回忿危,那么此時Docker kill就卡住了。
問題解釋
當出現(xiàn)問題時刻没龙,宿主機上發(fā)現(xiàn)大量的stress進程(實際是容器的進程)處于D狀態(tài)铺厨,而系統(tǒng)響應變慢。問題可以這樣解釋:
1.Docker kill通過containerd間接向容器主進程發(fā)送SIGKill信號以后硬纤,由于系統(tǒng)響應慢解滓,容器內部子進程(stress)處于D狀態(tài),那么在超時時間內containerd沒有上報容器退出筝家。Docker kill走到了直接發(fā)送Sigkill階段
2.在此階段前洼裤,容器內部主進程退出了,所以系統(tǒng)調用kill 發(fā)送SIGKILL很快就返回進程不存在了溪王。引擎認為自己把容器殺死了腮鞍,Docker kill成功返回了。
3.在一定時間后容器子進程從D狀態(tài)中恢復莹菱,它們退出了移国,containerd上報容器退出,引擎清理資源道伟,此時Docker ps看到容器才是退出狀態(tài)
在docker pidnamespace共享特性下容器對信號的響應
在k8s的pod下常見的場景迹缀,pause容器和其他容器共享pid namespace(pause容器pidnamespace共享給相同pod下其他容器使用)。pause容器退出后,其他容器也會退出(pause容器如果收到SIGTERM并退出了祝懂,那么其他容器也會退出)票摇;直接給其他容器發(fā)送SIGTERM信號,pause容器不會收到SIGTERM嫂易。
總結
- 容器主進程最好需要自己處理SIGTERM信號兄朋,因為這是你優(yōu)雅退出的機會。如果你不處理怜械,那么在Docker stop里你會收到Kill颅和,你未保存的數(shù)據(jù)就會直接丟失掉。
- Docker stop和Docker kill返回并不意味著容器真正退出成功了缕允,必須通過docker ps查看峡扩。
- 對于通過restful與docker 引擎鏈接的客戶端,需要在docker stop和kill restful請求鏈接上加上超時障本。對于docker cli用戶教届,需要有另外的機制監(jiān)控Docker stop或Docker kill命令超時卡死
- 處于D狀態(tài)一致卡死的進程,內核無法殺死驾霜,docker系統(tǒng)也救不了它案训。只有重啟系統(tǒng)才能清除。