自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

聊聊容器與pod中的僵尸進(jìn)程

運維 系統(tǒng)運維
在類UNIX系統(tǒng)中,僵尸進(jìn)程是指完成執(zhí)行(通過 exit 系統(tǒng)調(diào)用,或運行時發(fā)生致命錯誤或收到終止信號所致),但在操作系統(tǒng)的進(jìn)程表中仍然存在其進(jìn)程控制塊,處于"終止?fàn)顟B(tài)"的進(jìn)程。

[[423832]]

三種狀態(tài)的進(jìn)程模型

按進(jìn)程在執(zhí)行過程中的不同情況至少要定義三種狀態(tài):

  • 運行(running)態(tài):進(jìn)程占有處理器正在運行的狀態(tài)。進(jìn)程已獲得CPU,其程序正在執(zhí)行。在單處理機系統(tǒng)中,只有一個進(jìn)程處于執(zhí)行狀態(tài);在多處理機系統(tǒng)中,則有多個進(jìn)程處于執(zhí)行狀態(tài)。
  • 就緒(ready)態(tài):進(jìn)程具備運行條件,等待系統(tǒng)分配處理器以便運行的狀態(tài)。當(dāng)進(jìn)程已分配到除CPU以外的所有必要資源后,只要再獲得CPU,便可立即執(zhí)行,進(jìn)程這時的狀態(tài)稱為就緒狀態(tài)。在一個系統(tǒng)中處于就緒狀態(tài)的進(jìn)程可能有多個,通常將它們排成一個隊列,稱為就緒隊列。
  • 等待(wait)態(tài):又稱阻塞態(tài)或睡眠態(tài),指進(jìn)程不具備運行條件,正在等待某個時間完成的狀態(tài)。也稱為等待或睡眠狀態(tài),一個進(jìn)程正在等待某一事件發(fā)生(例如請求I/O而等待I/O完成等)而暫時停止運行,這時即使把處理機分配給進(jìn)程也無法運行,故稱該進(jìn)程處于阻塞狀態(tài)。

引起進(jìn)程狀態(tài)轉(zhuǎn)換的具體原因如下:

  • 運行態(tài)→等待態(tài):等待使用資源;如等待外設(shè)傳輸;等待人工干預(yù)。
  • 等待態(tài)→就緒態(tài):資源得到滿足;如外設(shè)傳輸結(jié)束;人工干預(yù)完成。
  • 運行態(tài)→就緒態(tài):運行時間片到;出現(xiàn)有更高優(yōu)先權(quán)進(jìn)程。
  • 就緒態(tài)—→運行態(tài):CPU 空閑時選擇一個就緒進(jìn)程。

五種狀態(tài)的進(jìn)程模型

五態(tài)模型在三態(tài)模型的基礎(chǔ)上增加了新建態(tài)(new)和終止態(tài)(exit)。

新建態(tài):對應(yīng)于進(jìn)程被創(chuàng)建時的狀態(tài),尚未進(jìn)入就緒隊列。創(chuàng)建一個進(jìn)程需要通過兩個步驟:1.為新進(jìn)程分配所需要資源和建立必要的管理信息。2.設(shè)置該進(jìn)程為就緒態(tài),并等待被調(diào)度執(zhí)行。

終止態(tài):指進(jìn)程完成任務(wù)到達(dá)正常結(jié)束點,或出現(xiàn)無法克服的錯誤而異常終止,或被操作系統(tǒng)及有終止權(quán)的進(jìn)程所終止時所處的狀態(tài)。處于終止態(tài)的進(jìn)程不再被調(diào)度執(zhí)行,下一步將被系統(tǒng)撤銷,最終從系統(tǒng)中消失。終止一個進(jìn)程需要兩個步驟:1.先等待操作系統(tǒng)或相關(guān)的進(jìn)程進(jìn)行善后處理(如抽取信息)。2.然后回收占用的資源并被系統(tǒng)刪除。

引起進(jìn)程狀態(tài)轉(zhuǎn)換的具體原因如下:

  • NULL→新建態(tài):執(zhí)行一個程序,創(chuàng)建一個子進(jìn)程。
  • 新建態(tài)→就緒態(tài):當(dāng)操作系統(tǒng)完成了進(jìn)程創(chuàng)建的必要操作,并且當(dāng)前系統(tǒng)的性能和虛擬內(nèi)存的容量均允許。
  • 運行態(tài)→終止態(tài):當(dāng)一個進(jìn)程到達(dá)了自然結(jié)束點,或是出現(xiàn)了無法克服的錯誤,或是被操作系統(tǒng)所終結(jié),或是被其他有終止權(quán)的進(jìn)程所終結(jié)。
  • 運行態(tài)→就緒態(tài):運行時間片到;出現(xiàn)有更高優(yōu)先權(quán)進(jìn)程。
  • 運行態(tài)→等待態(tài):等待使用資源;如等待外設(shè)傳輸;等待人工干預(yù)。
  • 就緒態(tài)→終止態(tài):未在狀態(tài)轉(zhuǎn)換圖中顯示,但某些操作系統(tǒng)允許父進(jìn)程終結(jié)子進(jìn)程。
  • 等待態(tài)→終止態(tài):未在狀態(tài)轉(zhuǎn)換圖中顯示,但某些操作系統(tǒng)允許父進(jìn)程終結(jié)子進(jìn)程。
  • 終止態(tài)→NULL:完成善后操作。

linux的進(jìn)程狀態(tài)

無論進(jìn)程還是線程,在 Linux 內(nèi)核里其實都是用 task_struct{}這個結(jié)構(gòu)來表示的。它其實就是任務(wù)(task),也就是 Linux 里基本的調(diào)度單位。

Linux進(jìn)程狀態(tài)有:

  • TASK_RUNNING : 就緒態(tài)或者運行態(tài),進(jìn)程就緒可以運行,但是不一定正在占有CPU,對應(yīng)進(jìn)程狀態(tài)的R。
  • TASK_INTERRUPTIBLE:睡眠態(tài),但是進(jìn)程處于淺度睡眠,可以響應(yīng)信號,一般是進(jìn)程主動sleep進(jìn)入的狀態(tài),對應(yīng)進(jìn)程狀態(tài)S。
  • TASK_UNINTERRUPTIBLE:睡眠態(tài),深度睡眠,不響應(yīng)信號,典型場景是進(jìn)程獲取信號量阻塞,對應(yīng)進(jìn)程狀態(tài)D。
  • TASK_ZOMBIE:僵尸態(tài),進(jìn)程已退出或者結(jié)束,但是父進(jìn)程還不知道,沒有回收時的狀態(tài),結(jié)束之前的一個狀態(tài),對應(yīng)進(jìn)程狀態(tài)Z。
  • EXIT_DEAD:結(jié)束態(tài),進(jìn)程已經(jīng)結(jié)束,也就是進(jìn)程結(jié)束退出那一瞬間的狀態(tài),對應(yīng)進(jìn)程狀態(tài)X。
  • TASK_STOPED:停止,調(diào)試狀態(tài),對應(yīng)進(jìn)程狀態(tài)T。

孤兒進(jìn)程

一個父進(jìn)程退出,而它的一個或多個子進(jìn)程還在運行,那么那些子進(jìn)程將成為孤兒進(jìn)程。孤兒進(jìn)程將被init進(jìn)程(進(jìn)程號為1,也有可能是容器中的init)所收養(yǎng),并由init進(jìn)程對它們完成狀態(tài)收集工作。孤兒進(jìn)程是沒有父進(jìn)程的進(jìn)程,孤兒進(jìn)程這個重任就落到了init進(jìn)程身上,init進(jìn)程就好像是一個民政局,專門負(fù)責(zé)處理孤兒進(jìn)程的善后工作。每當(dāng)出現(xiàn)一個孤兒進(jìn)程的時候,內(nèi)核就把孤 兒進(jìn)程的父進(jìn)程設(shè)置為init,而init進(jìn)程會循環(huán)地wait()它的已經(jīng)退出的子進(jìn)程。這樣,當(dāng)一個孤兒進(jìn)程凄涼地結(jié)束了其生命周期的時候,init進(jìn)程就會代表黨和政府出面處理它的一切善后工作。因此孤兒進(jìn)程并不會有什么危害。

僵尸進(jìn)程

在類UNIX系統(tǒng)中,僵尸進(jìn)程是指完成執(zhí)行(通過 exit 系統(tǒng)調(diào)用,或運行時發(fā)生致命錯誤或收到終止信號所致),但在操作系統(tǒng)的進(jìn)程表中仍然存在其進(jìn)程控制塊,處于"終止?fàn)顟B(tài)"的進(jìn)程。我們從上面的概念中得知,僵尸進(jìn)程仍然存在在進(jìn)程表中。進(jìn)程會占用系統(tǒng)系統(tǒng)資源,僵尸進(jìn)程過多會導(dǎo)致資源泄露,最主要的資源就是PID。我們來看一下linux系統(tǒng)中的PID。這個最大值可以我們在 /proc/sys/kernel/pid_max 這個參數(shù)中看到。

  1. [root@k8s-dev]# cat /proc/sys/kernel/pid_max 
  2. 32768 

Linux 內(nèi)核在進(jìn)行初始化時,會根據(jù)CPU 的數(shù)目對 pid_max 進(jìn)行設(shè)置。

  • 如果CPU 的數(shù)目小于等于32,那么 pid_max 會被設(shè)置為32768;
  • 如果CPU 的數(shù)目大于32,那么 pid_max = 1024 * (CPU 數(shù)目)。

所以如果超過這個最大值,那么系統(tǒng)就無法創(chuàng)建出新的進(jìn)程了,比如你想 SSH 登錄到這臺機器上就不行了。清理僵尸進(jìn)程:了解了僵尸進(jìn)程的危害,我們來看看怎么清理僵尸進(jìn)程:收割僵尸進(jìn)程的方法是通過kill命令手工向其父進(jìn)程發(fā)送SIGCHLD信號。如果其父進(jìn)程仍然拒絕收割僵尸進(jìn)程,則終止父進(jìn)程,使得init進(jìn)程收養(yǎng)僵尸進(jìn)程。init進(jìn)程周期執(zhí)行wait系統(tǒng)調(diào)用收割其收養(yǎng)的所有僵尸進(jìn)程。為避免產(chǎn)生僵尸進(jìn)程,實際應(yīng)用中一般采取的方式是:

將父進(jìn)程中對SIGCHLD信號的處理函數(shù)設(shè)為SIG_IGN(忽略信號);

  • fork兩次并殺死一級子進(jìn)程,令二級子進(jìn)程成為孤兒進(jìn)程而被init所“收養(yǎng)”、清理。
  • docker容器中的進(jìn)程

容器中的PID

在Docker中,進(jìn)程管理的基礎(chǔ)就是Linux內(nèi)核中的PID名空間技術(shù)。在不同PID名空間中,進(jìn)程ID是獨立的;即在兩個不同名空間下的進(jìn)程可以有相同的PID。

Linux內(nèi)核為所有的PID名空間維護(hù)了一個樹狀結(jié)構(gòu):最頂層的是系統(tǒng)初始化時創(chuàng)建的root namespace(根名空間),再創(chuàng)建的新PID namespace就稱之為child namespace(子名空間),而原先的PID名空間就是新創(chuàng)建的PID名空間的parent namespace(父名空間)。通過這種方式,系統(tǒng)中的PID名空間會形成一個層級體系。父節(jié)點可以看到子節(jié)點中的進(jìn)程,并可以通過信號等方式對子節(jié)點中的進(jìn)程產(chǎn)生影響。反過來,子節(jié)點不能看到父節(jié)點名空間中的任何內(nèi)容,也不可能通過kill或ptrace影響父節(jié)點或其他名空間中的進(jìn)程。

在Docker中,每個Container都是Docker Daemon的子進(jìn)程,每個Container進(jìn)程缺省都具有不同的PID名空間。通過名空間技術(shù),Docker實現(xiàn)容器間的進(jìn)程隔離。另外Docker Daemon也會利用PID名空間的樹狀結(jié)構(gòu),實現(xiàn)了對容器中的進(jìn)程交互、監(jiān)控和回收。注:Docker還利用了其他名空間(UTS,IPC,USER)等實現(xiàn)了各種系統(tǒng)資源的隔離,由于這些內(nèi)容和進(jìn)程管理關(guān)聯(lián)不多,本文不會涉及。

容器退出

當(dāng)創(chuàng)建一個Docker容器的時候,就會新建一個PID名空間。容器啟動進(jìn)程在該名空間內(nèi)PID為1。當(dāng)PID1進(jìn)程結(jié)束之后,Docker會銷毀對應(yīng)的PID名空間,并向容器內(nèi)所有其它的子進(jìn)程發(fā)送SIGKILL。

當(dāng)執(zhí)行docker stop命令時,docker會首先向容器的PID1進(jìn)程發(fā)送一個SIGTERM信號,用于容器內(nèi)程序的退出。如果容器在收到SIGTERM后沒有結(jié)束, 那么Docker Daemon會在等待一段時間(默認(rèn)是10s)后,再向容器發(fā)送SIGKILL信號,將容器殺死變?yōu)橥顺鰻顟B(tài)。這種方式給Docker應(yīng)用提供了一個優(yōu)雅的退出(graceful stop)機制,允許應(yīng)用在收到stop命令時清理和釋放使用中的資源。

docker kill可以向容器內(nèi)PID1進(jìn)程發(fā)送任何信號,缺省是發(fā)送SIGKILL信號來強制退出應(yīng)用。

容器中的僵尸進(jìn)程

產(chǎn)生原因

容器化后,由于單容器單進(jìn)程,已經(jīng)沒有傳統(tǒng)意義上的 init 進(jìn)程了。應(yīng)用進(jìn)程直接占用了 pid 1 的進(jìn)程號。從而導(dǎo)致以下兩個問題。

常見的使用是 docker run my-container script 。給 docker run進(jìn)程發(fā)送SIGTERM 信號會殺掉 docker run 進(jìn)程,但是容器還在后臺運行。

2.當(dāng)進(jìn)程退出時,它會變成僵尸進(jìn)程,直到它的父進(jìn)程調(diào)用 wait()( 或其變種 ) 的系統(tǒng)調(diào)用。process table 里面會把它的標(biāo)記為 defunct 狀態(tài)。一般情況下,父進(jìn)程應(yīng)該立即調(diào)用 wait(), 以防僵尸進(jìn)程時間過長。

如果父進(jìn)程在子進(jìn)程之前退出,子進(jìn)程會變成孤兒進(jìn)程, 它的父進(jìn)程會變成 PID 1。因此,init進(jìn)程就要對這些進(jìn)程負(fù)責(zé),并在適當(dāng)?shù)臅r候調(diào)用 wait() 方法。通常情況下,大部分應(yīng)用進(jìn)程不會處理偶然依附在自己進(jìn)程上的隨機子進(jìn)程,所以在容器中,會出現(xiàn)許多僵尸進(jìn)程。

解決方案

解決這個的辦法就是pid為1的跑一個支持信號轉(zhuǎn)發(fā)且支持回收孤兒僵尸進(jìn)程的進(jìn)程就行了,為此有人開發(fā)出了tini項目,感興趣可以github上搜下下,現(xiàn)在tini已經(jīng)內(nèi)置在docker里了。使用tini可以在docker run的時候添加選項–init即可,底層我猜測是復(fù)制docker-init到容器的/dev/init路徑里然后啟動entrypoint cmd,大家可以在run的時候測試下上面的步驟會發(fā)現(xiàn)根本不會有僵尸進(jìn)程遺留。這里不多說,如果是想默認(rèn)使用tini可以把tini構(gòu)建到鏡像里(例如k8s目前不支持docker run 的--init,所以需要把tini做到鏡像里),參照jenkins官方鏡像dockerfile和tini的github地址文檔 https://github.com/krallin/tini

k8s pod中的僵尸進(jìn)程

k8s 可以將多個容器編排到一個 pod 里面,共享同一個 Linux Namespace。這項技術(shù)的本質(zhì)是使用 k8s 提供一個 pause 鏡像,也就是說先啟動一個 pause 容器,相當(dāng)于實例化出 Namespace,然后其他容器加入這個 Namespace 從而實現(xiàn) Namespace 的共享。我們來介紹一下 pause。

pause 是 k8s 在 1.16 版本引入的技術(shù),要使用 pause,我們只需要在 pod 創(chuàng)建的 yaml 中指定 shareProcessNamespace 參數(shù)為 true,如下:

  1. apiVersion: v1 
  2. kind: Pod 
  3. metadata: 
  4.   name: nginx 
  5. spec: 
  6.   shareProcessNamespace: true 
  7.   containers: 
  8.   - name: nginx 
  9.     image: nginx 
  10.   - name: shell 
  11.     image: busybox 
  12.     securityContext: 
  13.       capabilities: 
  14.         add
  15.         - SYS_PTRACE 
  16.     stdin: true 
  17.     tty: true 

attach到pod中,ps查看進(jìn)程列表:

  1. / # kubectl attach POD -c CONTAINER  
  2. / # ps ax 
  3. PID   USER     TIME  COMMAND 
  4.     1 root      0:00 /pause 
  5.     8 root      0:00 nginx: master process nginx -g daemon off
  6.    14 101       0:00 nginx: worker process 
  7.    15 root      0:00 sh 
  8.    21 root      0:00 ps ax 

我們可以看到 pod 中的 1 號進(jìn)程變成了 /pause,其他容器的 entrypoint 進(jìn)程都變成了 1 號進(jìn)程的子進(jìn)程。這個時候開始逐漸逼近事情的本質(zhì)了:/pause 進(jìn)程是如何處理 將孤兒進(jìn)程的父進(jìn)程置為 1 號進(jìn)程進(jìn)而避免僵尸進(jìn)程的呢?

pause 鏡像的源碼如下:pause.c

  1. #include <signal.h> 
  2. #include <stdio.h> 
  3. #include <stdlib.h> 
  4. #include <string.h> 
  5. #include <sys/types.h> 
  6. #include <sys/wait.h> 
  7. #include <unistd.h> 
  8.  
  9. static void sigdown(int signo) { 
  10.   psignal(signo, "Shutting down, got signal"); 
  11.   exit(0); 
  12. // 關(guān)注1 
  13. static void sigreap(int signo) { 
  14.   while (waitpid(-1, NULL, WNOHANG) > 0) 
  15.     ; 
  16.  
  17. int main(int argc, char **argv) { 
  18.   int i; 
  19.   for (i = 1; i < argc; ++i) { 
  20.     if (!strcasecmp(argv[i], "-v")) { 
  21.       printf("pause.c %s\n", VERSION_STRING(VERSION)); 
  22.       return 0; 
  23.     } 
  24.   } 
  25.  
  26.   if (getpid() != 1) 
  27.     /* Not an error because pause sees use outside of infra containers. */ 
  28.     fprintf(stderr, "Warning: pause should be the first process\n"); 
  29.  
  30.   if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0) 
  31.     return 1; 
  32.   if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0) 
  33.     return 2; 
  34.   // 關(guān)注2 
  35.   if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap, 
  36.                                              .sa_flags = SA_NOCLDSTOP}, 
  37.                 NULL) < 0) 
  38.     return 3; 
  39.  
  40.   for (;;) 
  41.     pause(); // 編者注:該系統(tǒng)調(diào)用的作用是wait for signal 
  42.   fprintf(stderr, "Error: infinite loop terminated\n"); 
  43.   return 42; 

重點關(guān)注一下void sigreap(int signo){...}和if (sigaction(SIGCHLD,...) ,這個不就是我們上面說的 除了這種方式外,還可以通過異步的方式來進(jìn)行回收,這種方式的基礎(chǔ)是子進(jìn)程結(jié)束之后會向父進(jìn)程發(fā)送 SIGCHLD 信號,基于此父進(jìn)程注冊一個 SIGCHLD 信號的處理函數(shù)來進(jìn)行子進(jìn)程的資源回收就可以了。

SIGCHLD 信號的處理函數(shù)核心就是這一行 while (waitpid(-1, NULL, WNOHANG) > 0) ,其中各參數(shù)示意如下:

  • -1:meaning wait for any child process.
  • NULL:?
  • WNOHANG :return immediately if no child has exited.

結(jié)論

得出pause 容器的兩個最重要的特性:

  • 在 pod 中作為容器共享namespace的基礎(chǔ)
  • 作為 pod 內(nèi)的所有容器的父容器,扮演 init 進(jìn)程(即systemd)的作用。

最后

通過這篇文章的學(xué)習(xí),大家能解決容器中出現(xiàn)僵尸進(jìn)程的問題。

本文轉(zhuǎn)載自微信公眾號「運維開發(fā)故事」

 

責(zé)任編輯:姜華 來源: 運維開發(fā)故事
相關(guān)推薦

2022-01-19 08:01:13

Linuxdocker容器

2024-08-26 08:39:26

PHP孤兒進(jìn)程僵尸進(jìn)程

2019-11-20 09:15:53

KubernetesPod

2024-02-05 18:23:23

父進(jìn)程應(yīng)用程序程序

2017-12-15 09:40:47

Linux僵尸進(jìn)程

2022-02-07 11:55:00

linux進(jìn)程線程

2021-10-25 12:23:06

Linux僵尸進(jìn)程

2021-11-22 08:00:00

Kubernetes容器集群

2020-10-10 11:02:09

Linux 系統(tǒng) 數(shù)據(jù)

2021-11-08 10:30:30

Linux僵尸命令

2020-10-22 13:49:37

Docker容器僵死進(jìn)程

2021-11-06 10:17:38

Linux僵尸進(jìn)程

2023-11-03 08:22:09

Android系統(tǒng)算法

2022-10-12 09:01:52

Linux內(nèi)核線程

2024-05-09 09:55:08

2020-07-14 07:27:48

容器IoCSpring

2022-03-04 08:45:11

Docker開源Linux

2025-03-20 09:54:47

2021-03-28 08:32:58

Java

2024-05-15 09:11:51

委托事件C#
點贊
收藏

51CTO技術(shù)棧公眾號