容器的优雅退出(3):你好,SIGTERM!
容器的优雅退出(3):你好,SIGTERM!
我们已经知道 1 号进程的 exit()
流程了,这样我们就仿照 Linux 处理线程信息的方法就好,只不过把最后一步通知子进程的 SIGKILL 信号改成 SIGTERM 信号。我们来尝试一下。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
pid_t child_pid = 0;
void signal_handler(int signalno)
{
if (signalno == SIGTERM)
{
printf("received SIGTERM\n");
if (child_pid > 0)
{
kill(child_pid, signo);
sleep(1);
printf("sent SIGTERM\n");
}
exit(0);
}
}
int main(int argc, char *argv[])
{
int i;
int total;
if (argc < 2)
{
total = 1;
}
else
{
total = atoi(argv[1]);
}
signal(SIGTERM, signal_handler);
printf("create %d processes\n", total);
for (i = 0; i < total; i++)
{
pid_t pid = fork();
if (pid == 0)
{
pid_t m_pid, p_pid;
m_pid = getpid();
p_pid = getppid();
printf("Child => PPID: %d PID: %d\n", p_pid, m_pid);
while (1)
{
sleep(10);
}
printf("Child process eixts\n");
exit(EXIT_SUCCESS);
}
else if (pid > 0)
{
child_pid = pid;
printf("Parent created child %d\n", i);
}
else
{
printf("Unable to create child process. %d\n", i);
break;
}
}
printf("Paraent is sleeping\n");
while (1)
{
sleep(10);
}
return EXIT_SUCCESS;
}
我们仿照上一篇文章进行镜像打包,然后运行,继续用 strace 监测 pid ,我这次的 pid 端口是 10921 和 10948。
$ sudo strace -p 10921
strace: Process 10921 attached
restart_syscall(<... resuming interrupted read ...>) = 0
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=10, tv_nsec=0}, 0x7ffc4df5c3e0) = 0
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=10, tv_nsec=0}, {tv_sec=0, tv_nsec=489159741}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=0, si_uid=0} ---
write(1, "received SIGTERM\n", 17) = 17
kill(7, SIGTERM) = 0
write(1, "sent SIGTERM\n", 13) = 13
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=1, tv_nsec=0}, {tv_sec=0, tv_nsec=999545771}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=7, si_uid=0, si_status=0, si_utime=0, si_stime=0} ---
restart_syscall(<... resuming interrupted clock_nanosleep ...>) = 0
exit_group(0) = ?
+++ exited with 0 +++
---
$ sudo strace -p 10948
strace: Process 10948 attached
restart_syscall(<... resuming interrupted read ...>) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=1, si_uid=0} ---
write(1, "received SIGTERM\n", 17) = 17
exit_group(0) = ?
+++ exited with 0 +++
可以看到,主进程已经把收到的 SIGTERM 信号发送给了子进程,子进程也显示的是 +++ exited with 0 +++ ,代表已经是被 SIGTERM 关闭的了。说明我们这次已经成功的将主进程发送的 SIGKILL 信号改成了 SIGTERM 信号了。
当然先别急,如果我们把主进程等待子进程的退出时间改成 11 秒的话,会发生什么呢?
我们把 signal_handler()
中的 sleep()
函数改成11秒:
void signal_handler(int signalno)
{
if (signalno == SIGTERM)
{
printf("received SIGTERM\n");
if (child_pid > 0)
{
kill(child_pid, signo);
sleep(11);
printf("sent SIGTERM\n");
}
exit(0);
}
}
我们再次打包容器镜像并启动,我现在这两个进程的 pid 端口是 11290 和 11316,下面是用 strace 监测的结果:
$ sudo strace -p 11290
strace: Process 11290 attached
restart_syscall(<... resuming interrupted read ...>) = 0
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=10, tv_nsec=0}, 0x7ffc430b5140) = 0
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=10, tv_nsec=0}, {tv_sec=0, tv_nsec=593568102}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=0, si_uid=0} ---
write(1, "received SIGTERM\n", 17) = 17
kill(6, SIGTERM) = 0
write(1, "sent SIGTERM\n", 13) = 13
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=11, tv_nsec=0}, {tv_sec=10, tv_nsec=999950698}) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=6, si_uid=0, si_status=0, si_utime=0, si_stime=0} ---
restart_syscall(<... resuming interrupted clock_nanosleep ...>) = ?
+++ killed by SIGKILL +++
---
$ sudo strace -p 11316
strace: Process 11316 attached
restart_syscall(<... resuming interrupted read ...>) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=1, si_uid=0} ---
write(1, "received SIGTERM\n", 17) = 17
exit_group(0) = ?
+++ exited with 0 +++
怎么回事,子进程是顺利退出了,但是主进程却还没等到 11 秒,就已经收到 1 个 SIGKILL 信号退出了。
其实这就和我们写的主进程等待子进程退出的超时时间一样,如果 docker stop 超过了一定的时间,那么 docker 就会直接向主进程发送 SIGKILL 信号关闭容器。 Docker 的这个超时时间正是 10 秒,而Kubernetes 的超时时间则是 30 秒。
我的例子有点粗糙,我们可以参考社区项目 Tini 的代码,这段代码是 wait_and_forward_signal()
这个函数中一段代码:
/* There is a signal to handle here */
switch (sig.si_signo) {
case SIGCHLD:
/* Special-cased, as we don't forward SIGCHLD. Instead, we'll
* fallthrough to reaping processes.
*/
PRINT_DEBUG("Received SIGCHLD");
break;
default:
PRINT_DEBUG("Passing signal: '%s'", strsignal(sig.si_signo));
/* Forward anything else */
if (kill(kill_process_group ? -child_pid : child_pid, sig.si_signo)) {
if (errno == ESRCH) {
PRINT_WARNING("Child was dead when forwarding signal");
} else {
PRINT_FATAL("Unexpected error when forwarding signal: '%s'", strerror(errno));
return 1;
}
}
这个例子里面让进程忽略了 SIGCHLD 信号,并把其他信号都直接转发给了子进程。
Tini 是一个很小但是很好用的 容器 init 进程项目,Docker 1.13 及以上的版本已经将这个项目包括进去了,我们可以在 docker run 时,加上 --init 参数,就会使用 tini 作为我们容器的 init 进程了。
下一篇文章我会写一下 tini 的源码解读。
- 点赞
- 收藏
- 关注作者
评论(0)