一、前言
● 容器中部署的時候往往都是直接運行二進制文件或命令,這樣對於容器的作用更加直觀,但是也會出現新的問題,比如子進程的資源回收、釋放、托管等,處理不好,便會成為可怕的僵屍進程
● 本文主要討論一下docker容器中進程之間信號處理以及對進程管理的問題
二、環境准備
組件 | 版本 |
---|---|
OS | Ubuntu 18.04.1 LTS |
docker | 18.06.0-ce |
三、測試腳本
首先准備一個測試腳本,該腳本主要的作用是接收信號量以及獲取信號發送者的進程號:
semaphore.c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
static struct sigaction siga;
static void signal_handler(int sig, siginfo_t *siginfo, void *context) {
pid_t sender_pid = siginfo->si_pid;
if(sig == SIGTERM) {
printf("received sign: [term] , the sender is [%d]\n", (int)sender_pid);
return;
}
return;
}
void main(int argc, char *argv[]) {
printf("process [%d] started...\n", getpid());
siga.sa_sigaction = *signal_handler;
siga.sa_flags |= SA_SIGINFO;
sigaction(SIGTERM, &siga, NULL);
while(1) {
sleep(10);
}
}
測試一下:
首先編譯運行
root@k8s-master:/tmp# gcc semaphore.c
root@k8s-master:/tmp# ./a.out
process [20765] started...
重新打開一個控制台,發送一個SIGTERM信號
root@k8s-master:~# echo $$
20638
root@k8s-master:~# kill -15 20765
查看第一個控制台
root@k8s-master:/tmp# ./a.out
process [20765] started...
received sign: [term] , the sender is [20638]
看起來腳本已經可以正常工作了
它監聽了發送來得SIGTERM信號,並且成功找出了發送者
注:
SIGTERM是殺或的killall命令發送到進程默認的信號,SIGTERM類似於問一個進程終止可好,讓清理文件和關閉。說白了,就是對溫柔的對待,而不是粗暴的霸王硬上弓
四、進程在docker中收到的信號量
進程作為docker容器中1號進程
1號進程是所有進程的父進程,它可以收到從docker引擎發送的信號量,從而溫柔的關閉進程
root@k8s-master:/tmp# docker run --name sem_test --rm -it -v /tmp/a.out:/a.out ubuntu:latest /a.out
process [1] started...
重新打開一個控制台
root@k8s-master:~# docker stop sem_test
sem_test
回到第一個控制台
root@k8s-master:/tmp# docker run --name sem_test --rm -it -v /tmp/a.out:/a.out ubuntu:latest /a.out
process [1] started...
received sign: [term] , the sender is [0]
root@k8s-master:/tmp#
作為1號進程確實正確收到了來自docker引擎的SIGTERM,此時它可以從容的清理掉內存棧、網絡連接等資源
進程不是docker1號進程
root@k8s-master:~# docker exec -it sem_test bash
root@77e2d4e0ed03:/# /a.out
[1] 19
process [19] started...
重新打開一個控制台,查看進程樹
查看進程樹狀態
root@c8d8af54136a:/# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 07:52 pts/0 00:00:00 bash
root 15 1 0 07:52 pts/0 00:00:00 /a.out
root 16 0 3 07:53 pts/1 00:00:00 bash
root 27 16 0 07:53 pts/1 00:00:00 ps -ef
1號進程是一個非常普通的bash,a.out只不過是它的子進程而已
這時的a.out還能正確的接收到SIGTERM嗎?
root@k8s-master:~# docker stop sem_test
sem_test
查看第一個控制台狀態:
root@k8s-master:/tmp# docker run --name sem_test --rm -it -v /tmp/a.out:/a.out ubuntu:latest bash
root@c8d8af54136a:/# /a.out
process [15] started...
root@k8s-master:/tmp#
很遺憾,a.out沒有收到SIGTERM,它被霸王硬上弓了
注:
根據docker官網docker stop的介紹:
The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL.
docker stop會發送SIGTERM讓應用程序回收資源,過了溫柔期之后,會直接kill掉
五、dumb-init
● 從上面的測試來看,docker stop會向容器的1號進程發送SIGTERM
● 但是一個普通的1號進程收到SIGTERM並不會向它的子進程做任何處理
● 所以我們需要一個優秀的父進程來接收來自docker的信號,並且傳遞給它的兒子們
dumb-init可以幫助我們解決1號進程的問題:
https://github.com/Yelp/dumb-init
下載一個最新版:
wget https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_amd64 -O dumb-init
通過dumb-init運行a.out
root@k8s-master:/tmp# docker run --name sem_test --rm -it -v /tmp/a.out:/a.out -v /tmp/dumb-init:/dumb-init ubuntu:latest /dumb-init /a.out
process [8] started...
打開一個新的控制台查看進程樹:
root@k8s-master:/tmp# docker exec -it sem_test bash
root@09d494ac6ae3:/# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 08:08 ? 00:00:00 /dumb-init /a.out
root 8 1 0 08:08 pts/0 00:00:00 /a.out
root 9 0 3 08:09 pts/1 00:00:00 bash
root 20 9 0 08:09 pts/1 00:00:00 ps -ef
此時,1號進程變成了dumb-init,並且a.out是它的子進程
關閉容器:
root@k8s-master:/tmp# docker stop sem_test
sem_test
查看狀態:
root@k8s-master:/tmp# docker run --name sem_test --rm -it -v /tmp/a.out:/a.out -v /tmp/dumb-init:/dumb-init ubuntu:latest /dumb-init /a.out
process [8] started...
received sign: [term] , the sender is [1]
root@k8s-master:/tmp#
a.out成功收到來自1號進程(dumb-init)發送的信號SIGTERM,這下它可以從容的回收自己的資源了
六、小結
● docker引擎會向容器中1號進程發送信號,如果你的1號進程具備處理子進程各種狀態的能力,那完全可以直接啟動(比如nginx會處理它的worker進程);否則就需要使用像dumb-init之類的來充當1號進程
● 關於容器中僵屍進程的測試(像bash、sleep之類的普通進程能否接管孤兒進程),本文並沒有進行測試
至此,本文結束
在下才疏學淺,有撒湯漏水的,請各位不吝賜教...