当前位置: 首页 > 科技观察

再来说说容器中的No.1进程

时间:2023-03-14 18:09:29 科技观察

如何理解init进程?Linux进程在树中排序。每个进程都可以产生子进程,并且除了最顶层进程之外的每个进程都有一个父进程。一旦我们启动了多个进程,容器中就会出现一个pid为1的进程,也就是我们常说的进程1或者init进程,然后其他的子进程就会由这个进程创建。接下来,让我带您了解一下init进程是如何产生的。一个Linux操作系统,系统开机后执行BIOS/boot-loader,boot-loader会负责加载Linux内核。Linux内核执行文件一般放在/boot目录下,文件名类似于vmlinuz*。在内核完成操作系统的各种初始化之后,这个程序首先需要执行的用户态就是init进程。内核代码启动1号进程时,如果没有外部参数指定程序路径,一般会尝试从几个默认路径执行1号进程的代码。这些路径是Unix中常见的可执行代码路径。系统启动时,首先执行内核态的代码,然后调用内核中1号进程的代码,从内核态切换到用户态。当前主流的Linux发行版,无论是基于RedHat还是基于Debian,都指向/sbin/init作为指向Systemd的符号链接。Systemd是目前最流行的Linuxinit进程。在它之前,还有SysVinit、UpStart等Linuxinit进程。docker中的init后在linux上有了容器的概念,一旦容器建立了自己的PidNamespace(进程命名空间),这个Namespace中的进程号也从1开始标记。因此,容器的init进程也称为进程号1、你只需要记住:1号进程是第一个用户态进程,它直接或间接地在Namespace中创建其他进程。每个Docker容器都是一个PID命名空间,这意味着容器中的进程与主机上的其他进程是隔离的。PID命名空间是一棵树,从PID1开始,通常称为init。注意:当你运行Docker容器时,镜像的ENTRYPOINT是你的根进程,PID为1(如果你没有ENTRYPOINT,那么CMD就是根进程,你可以配置一个shell脚本,或者其他可执行文件programs,容器的根进程到底是什么完全取决于你的配置)。PID1在处理kill信号方面的特殊之处与其他进程不同:PID1通过默认操作忽略任何信号。因此,除非应用程序被编码为侦听SIGTERM信号,或者应用程序未实现处理SIGTERM信号的逻辑,否则应用程序不会停止。例如,默认的Bash和C语言程序不会为SIGTERM信号注册处理程序;PID1永远不会响应SIGKILL和SIGSTOP这两个特权信号;对于其他信号,如果用户注册了处理程序,进程1可以响应。Bash作为PID1怎么样?每个基本图像都有这个作为Bash。Bash正确地获取采用的子进程。Bash可以运行任何东西。所以在你的Dockerfile中,你肯定会使用这个:CMD["/bin/bash","-c","/path-to-your-app"]Bash默认情况下不处理SIGTERM信号,所以这将是下面的问题产生:第一个问题是:如果Bash以PID1运行,那么发送给Docker容器dockerstop的信号最终会发送SIGTERM信号给Bash,但是Bash默认不会处理SIGTERM信号,也不会会将它们转发到任何地方(除非您自己编写代码来完成)。dockerstop命令执行后,容器会有一个关闭时间限制,默认是10秒,超过10秒会用kill强制关闭。换句话说,当向Bash发送一个SIGTERM信号终止时,它会等待十秒钟,然后包含所有进程的整个容器将被内核强行终止。这些进程通过SIGKILL信号异常终止。SIGKILL是特权信号,无法捕获,因此进程无法干净地终止。假设服务正在运行的应用程序正忙于写入文件;如果应用程序在写入过程中异常终止,文件可能会损坏。不干净的终止是不好的。这几乎就像从服务器上拔下电源线一样。第二个问题是:一旦进程退出,Bash也会继续退出。如果程序因错误退出,Bash将以0退出代码退出,进程实际上崩溃了(但0表示“一切正常”;这将导致Docker或k8s上的重启策略无法按预期运行)。因为真正想要的可能是Bash返回与.请注意,我们修改bash以编写一个EXIT处理程序,该处理程序仅向子进程发出信号:然后kill$pids>/dev/null2>/dev/nullfi}trapcleanupEXIT/path-to-your-app不幸的是,这并不能解决问题。向子进程发送信号是不够的:init进程还必须等待子进程终止才能终止自身。如果init进程过早终止,所有子进程都会被内核不干净地终止。显然,需要一个更复杂的解决方案,但像Upstart、Systemd和SysVinit这样的完整初始化系统对于轻量级Docker容器来说是多余的。幸运的是,我们有很多用于容器的init程序。我们建议在这里使用简单的tini。Tini用作PID1。有许多方法可以在容器中启动init系统。这里推荐Tini。它是一个专用于容器的轻量级初始化系统。使用起来也很简单:FROMopenjdk8:8u201-jdk-alpine3.9RUNapkadd--no-cachetiniwget\&&mkdir-p/opt/arthas\&&cd/opt/arthas\&&wgethttps://img.ydisp.cn/news/20220804/t5iw4e03ds5.html["/sbin/tini","--"]请注意,Tini中有一些额外的功能很难用Bash或Java实现(例如Tini可以注册作为“子收割者”,所以它并不真的需要运行PID1来做“僵尸进程”的收割工作),但这些功能对于一些高级应用场景来说非常有用。docker中为什么会有僵尸进程?使用容器的理想状态是一个容器只启动一个进程,但这在实际应用中有时是不可能的。例如,容器中除了主进程外,我们还可能会启动辅助进程来监控或轮转日志;再比如,我们需要把原本运行在虚拟机(VM)上的程序搬到容器中去,这些原来运行在虚拟机上的程序本身就是多进程的。一旦我们启动了多个进程,容器中就会出现一个pid为1的进程,也就是我们常说的进程1或者init进程,然后其他的子进程就会由这个进程创建。比如我们在部署java服务的时候,需要部署一个Arthas(Alsace)作为java程序的诊断工具。总结第一个概念是Linux进程号1。它是第一个用户态进程。它直接或间接地在命名空间中创建其他进程。第二个概念是容器中进程1处理信号的三个要点:PID1没有默认的信号处理程序。如果应用程序不监听SIGTERM信号,或者如果应用程序没有实现处理SIGTERM信号的逻辑,则应用程序不会停止,容器也不会终止。在容器中,1号进程永远不会响应SIGKILL和SIGSTOP这两个特权信号;对于其他信号,如果用户注册了一个处理程序,1号进程就可以响应。第三个概念是tini作为No.1进程,可以将SIGTERM信号传递给子进程,收割僵尸进程。