前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >为什么我在容器中不能 kill 1 号进程?

为什么我在容器中不能 kill 1 号进程?

作者头像
用户1107783
发布2024-02-03 15:25:48
940
发布2024-02-03 15:25:48
举报
前言

使用容器的理想境界是一个容器只启动一个进程,现实中有时是做不到的。比如容器除了主进程外还启动辅助进程,做监控或者logs;再比如程序本身就是多进程的。

init进程

linux OS在打开电源,执行BIOS/boot-loader后,由boot-loader负责加载linux内核。完成内核初始化后,boot-loader需要执行的第一个用户态进程就是init进程。

init进程的基本功能就是创建出其他进程并管理它们。

而容器中也是由init进程直接或间接创建了Namespace中的其他进程。

linux信号

而为什么不能在容器中kill 1号进程呢?进程在收到信号后,就会去做相应的处理。

  • 第一个选择是忽略这个信号,但有两个信号例外:SIGKILL 和 SIGSTOP,进程不能忽略。它们的主要作用是为内核和超级用户提供删除任意进程的特权。
  • 第二个选择是捕获,指让用户进程可以注册自己针对这个信号的 handler。SIGKILL 和 SIGSTOP 也同样例外,不能有用户自己的处理代码,只能执行系统的缺省行为。
  • 最后一个选择是缺省行为(Default),Linux 为每个信号定义了一个缺省行为,对于大部分的信号而言,应用程序不需要注册自己的 handler,使用系统缺省定义行为即可。

SIGTERM(15)

由Linux命令kill缺省发出。如kill 1,通过kill向1号进程发送信号。在没有别的参数时这个信号类型默认为SIGTERM,是可以被捕获的

SIGKILL(9)

Linux 里两个特权信号之一,不能被忽略也不能被捕获。进程一旦收到 SIGKILL就要退出。运行命令 kill -9 1 里的参数“-9”,就是指发送编号为 9 的这个 SIGKILL 信号给 1 号进程。

为什么在容器中不能kill 1号进程?

对于不同的程序,结果是不同的。把c程序作为1号进程就无法在容器中杀死,而go程序作为1号进程却可以。

运行 kill 1 时,希望把 SIGTERM 发送给 1 号进程,就像下图中带箭头虚线。

在 Linux 中,kill 命令调用了 kill() 系统调用(内核的调用接口)而进入到了内核函数 sys_kill()。而内核在决定把信号发送给 1 号进程时会调用 sig_task_ignored() 函数进行判断,它会决定内核在哪些情况下会把发送的这个信号给忽略掉。如果信号被忽略了,那么 init 进程就不能收到指令了。

想要知道 init 进程为什么收到或者收不到信号,就要去看 sig_task_ignored()的实现。

问题和第二个if语句有关,一旦这三个子条件都被满足,那么信号就不会发送给进程。

  • !(force && sig_kernel_only(sig)):如果是同一个Namespace发出的信号,值为0。所以这个条件总是满足。
  • handler == SIG_DFL:判断信号的handler是否为SIG_DFL(default handler)。SIGKILL不允许捕获,handler一直是SIG_DFL,该条件总是满足。SIGTERM可捕获,不一定满足。
  • t->signal->flags & SIGNAL_UNKILLABLE:进程必须是GINAL_UNKILLABLE的,在每个namespace的init进程建立时就会打上这个标签。

可以看出最关键的一点就是 handler == SIG_DFL 。Linux 内核针对每个 Namespace 里的 init 进程,把只有 default handler 的信号都给忽略了。

如果我们自己注册了信号的 handler,那么即使是 init 进程在接收到 SIGTERM 之后也是可以退出的。所以 init 进程是永远不能被 SIGKILL 所杀,但可以被 SIGTERM 杀死。

该怎么证实这一点呢?

  1. 查看 1 号进程状态中 SigCgt Bitmap。在 Go 程序里,很多信号都注册了自己的 handler,包括 SIGTERM(15),也就是 bit 15。而 C 程序里缺省状态下,一个信号 handler 都没有注册;bash 程序注册了两个 handler,bit 2 和 bit 17,也就是 SIGINT 和 SIGCHLD,但是没有注册 SIGTERM。所以C 程序和 bash 程序不能被 SIGTERM 所杀。
代码语言:javascript
复制
### golang init 
# cat /proc/1/status | grep -i 
SigCgt SigCgt: fffffffe7fc1feff 

### C init 
# cat /proc/1/status | grep -i SigCgt 
SigCgt: 0000000000000000

### Bash init 
# cat /proc/1/status | grep -i SigCgt 
SigCgt: 0000000000010002

  1. 给c程序注册SIGTERM handler,捕获SIGTERM
代码语言:javascript
复制
# docker stop sig-proc;docker rm sig-proc 
# docker run --name sig-proc -d registry/sig-proc:v1 /c-init-sig 
# docker exec -it sig-proc bash [root@043f4f717cb5 /]
# ps -ef 
UID PID PPID C STIME TTY TIME CMD
root 1   0   0 09:05 ? 00:00:00 /c-init-sig 
root 6   0  18 09:06 pts/0 00:00:00 bash 
root 19  6   0 09:06 pts/0 00:00:00 ps -ef 
[root@043f4f717cb5 /]# cat /proc/1/status | grep SigCgt 
SigCgt: 0000000000004000 
[root@043f4f717cb5 /]# kill 1 # docker ps CONTAINER ID IMAGE COMMAND CREATED

重点总结

“为什么我在容器中不能 kill 1 号进程?”。解决这个问题需要掌握两个基本概念。

  • Linux 1 号进程。它是第一个用户态的进程。它直接或者间接创建了 Namespace 中的其他进程。
  • Linux 信号。Linux 有 31 个基本信号,进程在处理大部分信号时有三个选择:忽略、捕获和缺省行为。其中两个特权信号 SIGKILL 和 SIGSTOP 不能被忽略或者捕获。

我们尝试了用 bash, C 还有 Go 程序作为容器 init 进程,发现它们对 kill 1 的反应是不同的。因为信号的最终处理都是在 Linux 内核中进行的,因此,我们需要对 Linux 内核代码进行分析。

  1. 容器里 1 号进程对信号处理的两个要点:
  2. 在容器中,1 号进程永远不会响应 SIGKILL 和 SIGSTOP 这两个特权信号;对于其他的信号,如果用户自己注册了 handler,1 号进程可以响应。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-01-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云原生运维圈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • init进程
  • linux信号
    • SIGTERM(15)
      • SIGKILL(9)
      • 为什么在容器中不能kill 1号进程?
      • 重点总结
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档