容器的优雅退出(3):你好,SIGTERM!

举报
东坡爱吃肘子 发表于 2022/11/10 12:49:41 2022/11/10
【摘要】 我们来尝试一下处理 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 的源码解读。

  • 1.46KB 下载次数:0

    附件下载

  • 3.zip 1.46KB 下载次数:0
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。