容器关闭时的信号
容器里面有过个进程的时候,init进程收到的是 SIGTERM 信号,其他进程收到的是 SIGKILL 信号。
主要原因是系统内核在 pid Namespace 下 init 进程的子进程时是发送 SIGKILL 信号,所以这情况下子进程无法捕获终止信号。解决方法是使用像 tini 这样的init进程来管理,tini 能够将 SIGTERM 信号转发给子进程,这样子进程就能接收到 SIGTERM 信号了。
但是 tini 默认情况下是无法转发 SIGTERM 给子子进程的,kill_process_group 默认缺省值,当kill_process_group 没有配置或者为0的时候,tini 就不会将 SIGTERM 转发给子子进程,而是收到 SIGKILL。
容器化程度很高,容器里面只有一个进程,就是应用进程,这样就能避免这个问题,不需要专业的 init 进程。
使用 tini 或其他的特殊的 init 进程管理容器内的进程
为什么其他进程收到的SIGKILL
在用户态的 init 进程调用 exit() 退出后,会调用内核态的 do_exit(),do_exit() 调用 exit_notify() 去通知子进程退出。对于容器来说,容器内的所有进程都是在同一个 pid namespace 下的,所以 exit_notify() 调用的是 zap_pid_ns_processes() 来关闭同一命名空间下的进程。zap_pid_ns_processes() 的源码里面给其他进程发送的是 SIGKILL 信号。对于特权信号,进程无法做到优雅退出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/*
* The last thread in the cgroup-init thread group is terminating.
* Find remaining pid_ts in the namespace, signal and wait for them
* to exit.
*
* Note: This signals each threads in the namespace - even those that
* belong to the same thread group, To avoid this, we would have
* to walk the entire tasklist looking a processes in this
* namespace, but that could be unnecessarily expensive if the
* pid namespace has just a few processes. Or we need to
* maintain a tasklist for each pid namespace.
*
*/
rcu_read_lock();
read_lock(&tasklist_lock);
nr = 2;
idr_for_each_entry_continue(&pid_ns->idr, pid, nr) {
task = pid_task(pid, PIDTYPE_PID);
if (task && !__fatal_signal_pending(task))
group_send_sig_info(SIGKILL, SEND_SIG_PRIV, task, PIDTYPE_MAX);
}
|
tini 是怎么做到向其他进程转发 SIGTERM 信号的
如果容器中使用 tini 作为 init 进程,tini 的 sigtimedwait() 函数来检查自己收到的信号,然后调用 kill() 把信号传递给子进程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
int wait_and_forward_signal(sigset_t const* const parent_sigset_ptr, pid_t const child_pid) {
siginfo_t sig;
if (sigtimedwait(parent_sigset_ptr, &sig, &ts) == -1) {
switch (errno) {
…
}
} else {
/* 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;
}
}
break;
}
}
return 0;
}
|
容器要做到graceful down,需要给所有的进程来处理自己的退出,而不是强制退出。所以需要接收到的是 SIGTERM 信号,而不是 SIGKILL 信号。或者做到极致的容器化:容器内只有一个进程。
init 进程的关键特点
1、能够将 SIGTERM 信号转发给关键的子进程
2、能够处理zombie进程
3、能够接收外界的 SIGTERM 信号而退出,也可以通过注册 SIGTERM handler,也可以像 tini 一样转发 SIGTERM 该子进程,然后收到 SIGCHILD 后自己退出。