容器中的一号进程

简介: 容器中的一号进程

如何理解 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 是目前最流行的 Linux init 进程,在它之前还有 SysVinit、UpStart 等 Linux init 进程。


docker中的init


在 Linux 上有了容器的概念之后,一旦容器建立了自己的 Pid Namespace(进程命名空间),这个 Namespace 里的进程号也是从 1 开始标记的。所以,容器的 init 进程也被称为 1 号进程。你只需要记住:1 号进程是第一个用户态的进程,由它直接或者间接创建了 Namespace 中的其他进程。


每个Docker容器都是一个PID命名空间,这意味着容器中的进程与主机上的其他进程是隔离的。PID命名空间是一棵树,从PID 1开始,通常称为init。


注意:当你运行一个Docker容器时,镜像的ENTRYPOINT就是你的根进程,即PID 1(如果你没有ENTRYPOINT,那么CMD就会作为根进程,你可能配置了一个shell脚本,或其他的可执行程序,容器的根进程具体是什么,完全取决于你的配置)。


PID 1在处理kill信号的特别之处


与其他进程不同的是:


  • PID 1它会忽略具有默认操作的任何信号。因此除非经过编码,否则应用没有监听 SIGTERM 信号,或者应用中没有实现处理 SIGTERM 信号的逻辑,应用就不会停止。比如默认的Bash与C语言的程序,是没有注册SIGTERM 信号的handler;
  • PID 1永远不会响应 SIGKILL 和 SIGSTOP 这两个特权信号;
  • 对于其他的信号,如果用户自己注册了 handler,1 号进程可以响应。


把Bash当作PID 1呢?


每个基础镜像都有这个是Bash 。Bash 正确地收割了采用的子进程。Bash 可以运行任何东西。所以在你的Dockerfile中,你肯定会用这个:


CMD ["/bin/bash", "-c", "/path-to-your-app"]


Bash默认不会处理SIGTERM信号,因此这将会导致如下的问题:第一个问题是:如果将Bash作为PID 1运行,那么发送到Docker容器docker stop的信号,最终都是将 SIGTERM信号发送到Bash,但是Bash默认不会处理SIGTERM信号,也不会将它们转发到任何地方(除非您自己编写代码实现)。docker stop命令执行后,容器会有一个关闭的时限,默认为10秒,超过十秒则用kill强制关闭。换句话说,给 Bash发送SIGTERM信号终止时,会等待十秒钟,然后被内核强制终止包含所有进程的整个容器。这些进程通过 SIGKILL 信号不正常地终止。SIGKILL是特权信号,无法被捕获,因此进程无法干净地终止。假设服务正在运行的应用程序正忙于写入文件;如果应用程序在写入过程中不干净地终止,文件可能会损坏。不干净的终止是不好的。这几乎就像从服务器上拔下电源插头一样。


第二个问题是:一旦进程退出,Bash也会继续退出。如果程序出了bug退出了,Bash会退出,退出代码为0,而进程实际上崩溃了(但0表示“一切正常”;这将导致Docker或者k8s上重启策略不符合预期)。因为真正想要的可能是Bash返回与的进程相同的退出代码。


请注意,我们对bash进行修改,编写一个 EXIT 处理程序,它只是向子进程发送信号:


#!/bin/bash
function cleanup()
{
    local pids=`jobs -p`
    if [[ "$pids" != "" ]]; then
        kill $pids >/dev/null 2>/dev/null
    fi
}
trap cleanup EXIT
/path-to-your-app


不幸的是,这并不能解决问题。向子进程发送信号是不够的:init 进程还必须等待子进程终止,然后才能终止自己。如果 init 进程过早终止,那么所有子进程都会被内核不干净地终止。


很明显,需要一个更复杂的解决方案,但是像 Upstart、Systemd 和 SysV init 这样的完整 init 系统对于轻量级 Docker 容器来说太过分了。幸运的是,我们有很多在容器中使用的init程序。我们这里推荐使用简单的tini。


tini当作PID 1


我们在容器中启动一个init 系统有很多种,这里推荐使用 tini,它是专用于容器的轻量级 init 系统,使用方法也很简单:


FROM openjdk8:8u201-jdk-alpine3.9
RUN apk add --no-cache tini wget \
    && mkdir -p /opt/arthas \
    && cd /opt/arthas \
    && wget https://arthas.aliyun.com/arthas-boot.jar
ENTRYPOINT ["/sbin/tini", "--"]


请注意,Tini中还有一些额外的功能,在Bash或Java中很难实现(例如,Tini可以注册为“子收割者”,因此它实际上不需要作为PID 1运行来完成“僵尸进程”收割工作),但是这些功能对于一些高级应用场景来说非常有用。


为什么docker中会有僵尸进程?


使用容器的理想境界是一个容器只启动一个进程,但这在现实应用中有时是做不到的。

比如说,在一个容器中除了主进程之外,我们可能还会启动辅助进程,做监控或者 rotate logs;再比如说,我们需要把原来运行在虚拟机(VM)的程序移到容器里,这些原来跑在虚拟机上的程序本身就是多进程的。


一旦我们启动了多个进程,那么容器里就会出现一个 pid 1,也就是我们常说的 1 号进程或者 init 进程,然后由这个进程创建出其他的子进程。比如我们在部署java服务的时候,我们需要部署一个Arthas(阿尔萨斯),来做为java程序的诊断工具。


总结


第一个概念是 Linux 1 号进程。它是第一个用户态的进程。它直接或者间接创建了 Namespace 中的其他进程。第二个概念是容器里 1 号进程对信号处理的三个要点:


  • PID 1没有默认的信号处理程序。如果应用没有监听 SIGTERM 信号,或者应用中没有实现处理 SIGTERM 信号的逻辑,应用就不会停止,容器也不会终止。
  • 在容器中,1 号进程永远不会响应 SIGKILL 和 SIGSTOP 这两个特权信号;
  • 对于其他的信号,如果用户自己注册了 handler,1 号进程可以响应。


第三个概念是tini作为1号进程可以给子进程传递SIGTERM信号和收割僵尸进程。

相关文章
|
Kubernetes Ubuntu Cloud Native
深入剖析Kubernetes学习笔记-05 | 白话容器基础(一):从进程说开去
深入剖析Kubernetes学习笔记-05 | 白话容器基础(一):从进程说开去
278 0
|
11月前
|
PHP Docker 容器
如何在宿主主机运行容器中的php守护进程
在Docker容器中同时运行多个程序(如Nginx+PHP+Ftp)时,需用`docker exec`命令启动额外服务。首先通过`php -v`查看PHP版本,再用`which php-fpm7.4`确认PHP安装路径,通常返回`/usr/sbin/php-fpm7.4`。最后直接运行该路径启动PHP-FPM服务,确保其正常工作。
201 14
|
Linux Docker 容器
在Docker守护进程停机期间保持容器运行(即重启Docker时,正在运行的容器不会停止)
在Docker守护进程停机期间保持容器运行(即重启Docker时,正在运行的容器不会停止)
1077 0
|
Kubernetes Shell 测试技术
在Docker中,可以在一个容器中同时运行多个应用进程吗?
在Docker中,可以在一个容器中同时运行多个应用进程吗?
|
NoSQL Shell Linux
跨cpu架构部署容器技术点:怎么将容器启动时的1号进程挂载到systemctl
--privileged=true:是Docker中的一个参数,用于授予容器的特权权限。当一个容器被设置为特权容器时,它将拥有与主机操作系统相同的权限,可以执行一些高级操作,如访问主机设备、加载内核模块等。
268 0
|
监控 Java 数据安全/隐私保护
在Docker容器中,有时候无法监控到正在运行的进程
在Docker容器中,有时候无法监控到正在运行的进程
508 2
|
Arthas Java Unix
为什么在容器中 1 号进程挂不上 arthas?
作为技术人员还是需要了解底层,这样在排查问题、架构设计上才会有更多自由度,更能够抓住问题、解决问题。
为什么在容器中 1 号进程挂不上 arthas?
|
存储 Kubernetes Java
【探索 Kubernetes|容器基础进阶篇 系列 3】容器进程的文件系统
【探索 Kubernetes|容器基础进阶篇 系列 3】容器进程的文件系统
677 0
|
Kubernetes Cloud Native 安全
【探索 Kubernetes|容器基础进阶篇 系列1】容器的本质是进程
大家好,我是秋意零。 😈 CSDN作者主页 • 😎 博客主页 👿 简介 • 👻 普通本科生在读 • 在校期间参与众多计算机相关比赛,如:🌟 “省赛”、“国赛”,斩获多项奖项荣誉证书 • 🔥 各个平台,秋意零/秋意临 账号创作者 • 🔥 云社区 创建者 点赞、收藏+关注下次不迷路! 欢迎加入云社区
422 0
|
弹性计算 运维 Kubernetes
《云原生架构容器&微服务优秀案例集》——03 零售/电商——厨芯科技 加速业务容器化进程,成功实现增效降本
《云原生架构容器&微服务优秀案例集》——03 零售/电商——厨芯科技 加速业务容器化进程,成功实现增效降本
306 0