容器的优雅退出(1):是 SIGKILL 吗

举报
东坡爱吃肘子 发表于 2022/11/10 12:25:18 2022/11/10
【摘要】 无论是在 Kubernetes 中删除一个 pod,还是用 Docker 来停止一个容器,最后都会使用 Containerd 这个服务。Containerd 在停止容器时,会向容器的 init 进程发送一个 SIGTERM 信号。在 init 进程退出后,容器内的其他进程也会立刻退出。不过不同的是,init 进程收到的是 SIGTERM 信号,而容器内的其他进程收到的则是 SIGKILL 信号。

容器的优雅退出(1):是 SIGKILL 吗

我们在容器平台上,如果想要停止一个容器,无论是在 Kubernetes 中删除一个 pod,还是用 Docker 来停止一个容器,最后都会使用 Containerd 这个服务。而 Containerd 在停止容器时,就会去向容器的 init 进程发送一个 SIGTERM 信号。在 init 进程退出后,容器内的其他进程也会立刻退出。不过不同的是,init 进程收到的是 SIGTERM 信号,而容器内的其他进程收到的则是 SIGKILL 信号。

SIGKILL 这个信号在 Linux 中属于特权信号,不能被忽略和捕获。这个信号不能被捕获,也就代表用户无法注册自己的 handler,那么应用就会直接退出。

我们可以先动手试一下,是不是像我说的那样:

我写了一个简单例子,我把需要的代码文件都放在了附件中,需要的可以下载试一下。C代码如下:

shutdown.c :

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

void signal_handler(int signalno)
{
    if (signalno == SIGTERM) 
    {
        printf("received 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) 
        {
            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;
}

Dockerfile 文件 :

FROM gongt/glibc:latest
COPY ./c-shutdown /

编译 C 文件:

gcc -o c-shutdown c-shutdown.c

制作镜像:

$ docker build -t c-shutdown:v1 

Sending build context to Docker daemon  22.02kB
Step 1/2 : FROM gongt/glibc:latest
 ---> 3c1190395d6d
Step 2/2 : COPY ./c-shutdown /
 ---> 53f56d3d1e68
Successfully built 53f56d3d1e68
Successfully tagged c-shutdown:v

得到上面的输出就算是制作好我们需要的镜像了。现在我们启动镜像,再使用 docker ps 看一下是否确实启动成功:

$ docker run --name c-shutdown -d -it c-shutdown:v1 /c-shutdown

b798df5c09d9bc640a3ba8283a2d5bcb65ef8e28ce6f409c6717712d3c136fd0

$ docker ps

CONTAINER ID   IMAGE           COMMAND         CREATED         STATUS         PORTS     NAMES
b798df5c09d9   c-shutdown:v1   "/c-shutdown"   5 seconds ago   Up 3 seconds             c-shutdown

我们再通过 ps 命令查看 容器进程的 pid :

$ ps -ef | grep c-shutdown

root        2950    2927  0 17:09 pts/0    00:00:00 /c-shutdown
root        2977    2950  0 17:09 pts/0    00:00:00 /c-shutdown
DDRyan      2985    2277  0 17:09 pts/0    00:00:00 grep --color=auto c-shutdown

从上面的信号,我们可以看到这个容器启用了两个进程,pid 分别是 2950 和 2977。

我们可以使用 strace 工具来监控这两个进程的状态 :(开启两个终端,分别运行,使用 -p 参数指定pid)

$ sudo strace -p 2950

strace: Process 2950 attached
restart_syscall(<... resuming interrupted read ...>) = 0

---

$ sudo strace -p 2977
 
strace: Process 2977 attached
restart_syscall(<... resuming interrupted read ...>) = 0

现在我们就已经监控好主进程和主进程创建的子进程了。现在可以开启一个新的终端来停止这个容器:

$ docker stop c-shutdown
c-shutdown

监控终端的显示:

$ sudo strace -p 2950

strace: Process 2950 attached
restart_syscall(<... resuming interrupted read ...>) = 0
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=100, tv_nsec=0}, 0x7ffff6936640) = 0
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=100, tv_nsec=0}, 0x7ffff6936640) = 0
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=100, tv_nsec=0}, {tv_sec=11, tv_nsec=727531886}) = ? 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
exit_group(0)                           = ?
+++ exited with 0 +++

---

$ sudo strace -p 2977

strace: Process 2977 attached
restart_syscall(<... resuming interrupted read ...>) = 0
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=100, tv_nsec=0}, 0x7ffff6936640) = 0
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=100, tv_nsec=0},  <unfinished ...>) = ?
+++ killed by SIGKILL +++

从上面的显示来看,容器的 init 进程确实收到了 SIGTERM 信号,而另一个进程收的是则是 SIGKILL 信号。

SIGKILL 信号是无法被捕获的,那么容器内的其他进程就会直接退出。这种情况,在实际的生产环境中,是很难接收的。应用在退出时,应该还需要做一些清理工作,包括清理一些远端的链接,文件的 close ,缓存的落盘等一些动作。

所以我们应该如何让容器 graceful shutdown,而不是 hard shutdown 呢?

  • 931B 下载次数:0

    附件下载

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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