容器的优雅退出(1):是 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 呢?
- 点赞
- 收藏
- 关注作者
评论(0)