当前位置: 首页 > news >正文

docker 容器如何使用tini 作为启动进程并 清理、管理 Docker 容器僵死进程

一般的docker镜像为了节省空间,通常是没有安装systemd或者sysvint这类初始化系统的进程。一旦容器的起始进程不稳定将会产生大量的僵尸进程,影响宿主系统的运行。

缺少init的容器

init系统有以下几个特点:

  • 它是系统的第一个进程,负责产生其他所有用户进程。
  • init 以守护进程方式存在,是所有其他进程的祖先。
  • 它主要负责:

1.启动守护进程

2.回收孤儿进程

3.将操作系统信号转发给子进程

以下dockerfile为例:

FROM nginx

ENTRYPOINT ["nginx", "-c"]
CMD ["/etc/nginx/nginx.conf"]

当docker容器启动时,PID 1即容器启动程序将会是nginx, 只要这个程序停止,容器就会跟住停止。由于nginx 不具备init 上述的功能,PID 1是无法回收异常退出进程,异常退出的进程变成僵尸进程,继续占用系统资源。

当多个容器运行在一个宿主机上的时候,为了避免一个容器消耗完我们整个宿主机进程号资源,docker会配置PID CGROUP来限制每个容器的最大进程数目。也就是说,进程数目在每个容器中也是有限的,是一种很宝贵的资源。(例如:Linux 机器上的进程总数目是有限制,如果进程数据过多,比如你想ssh登录到机器上就不行 )

如何使用 tini 初始化系統

tini 是一套更简单的 init 系统,专门用来执行一个子程序(spawn a single child),并等待子程序结束,即便子程序已经变成僵尸程序也能捕捉到,同时也能转送 Signal 给子程序。如果你使用docker来跑容器,可以非常简便的在docker run的时候用**–init参数,就会自动注入tini程式 (/sbin/docker-init**) 到容器中,并且自动取代ENTRYPOINT设定,让原本的程式直接跑在 tini程序底下。

dockerfile如下:

FROM nginx

RUN export TINI_VERSION=0.9.0 && \
    export TINI_SHA=fa23d1e20732501c3bb8eeeca423c89ac80ed452 && \
    curl -fsSL https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-static -o /bin/tini && \
    echo 'Calculated checksum: '$(sha1sum /bin/tini) && \
    chmod +x /bin/tini && echo "$TINI_SHA  /bin/tini" | sha1sum -c 
    
ENTRYPOINT ["/bin/tini","--","/opt/nginx/docker-entrypoint.sh"]
ENTRYPOINT ["nginx", "-c"] 
CMD ["/etc/nginx/nginx.conf"]

建议为了提高容器运行的可靠性,可以选择在打包镜像时加入tini的安装,并以tini作为启动入口。

Jenkins的官方镜像中使用Tini 的使用

首先,我们先简单聊聊Jenkins。当您运行Docker容器时,Docker会将它与系统的其他部分隔离开来。这种隔离发生在不同的级别(例如网络、文件系统、进程)。

但Tini并不真正关注网络或文件系统,所以让我们把注意力放在Tini的一个重要概念上:进程。

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

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

与其他进程不同的是,PID 1有一个独特的职责,那就是收割“僵尸进程”。

那何为“僵尸进程”呢?

“僵尸进程”是指:

  • 已经退出。
  • 没有被其父进程wait(wait是指syscall父进程用于检索其子进程的退出代码)。
  • 父进程已丢失(也就是说,它们的父进程已经不存在了),这意味着他们永远不会被其父进程处理。

当“僵尸进程”被创建时(也就是说,一旦它的父进程非正常退出了,它也就跟着无法正常退出了),它会继承成为PID 1的子级,最后PID 1会负责关闭它。

换句话说,有人必须在“不负责任”的父进程离开后,对这些“孤儿”进行清理,这是PID 1的作用。

请注意,创建“僵尸进程”通常是不被允许的(也就是说,理想情况下,您应该修复代码,这样就不会创建“僵尸进程”),但是对于像Jenkins这种应用来说,它们是不可避免的:因为Jenkins通常运行的代码不是由Jenkins维护者编写的(也就是您的Jenkins构建脚本),所以他们也无法“修复代码”。

这就是Jenkins使用Tini的原因:在构建了创建“僵尸进程”的脚本后进行清理。


但其实Bash实际上也做同样的事情(收割“僵尸进程”),所以你可能会想:为什么不把Bash当作PID 1呢?

第一个问题是,如果您将Bash作为PID 1运行,那么您发送到Docker容器的所有信号(例如,使用docker stop或docker kill)最终都会发送到Bash,Bash默认不会将它们转发到任何地方(除非您自己编写代码实现)。换句话说,如果你使用Bash来运行Jenkins,那么当你运行docker stop的时候,Jenkins将永远收不到停止信号!

而Tini通过“信号转发”解决了这个问题:如果你向Tini发送信号,那么它也会向你的子进程发送同样的信号(在你的例子中是Jenkins)。

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

请注意,您可以通过在Bash中创建信号处理程序来实际执行转发,并返回适当的退出代码来解决这个问题。另一方面,这需要做更多的工作,而添加Tini只是文档中的几行。


其实还有另一个解决方案可以将Jenkins作为PID 1运行,即在Jenkins中添加另一个线程来负责收割“僵尸进程”。

但这也不理想,原因有二:

首先,如果将Jenkins以PID 1的身份运行,那么很难区分继承给Jenkins的进程(应该被收割)和Jenkins自己产生的进程(不应该被收割,因为还有其他代码已经在等待它们执行)。我相信你可以用代码来解决这个问题,但还是要问一遍:当你可以把Tini放进去的时候,为什么还要写呢?

其次,如果Jenkins以PID 1运行,那么它可能不会接收到您发送的信号!

这是PID 1进程中的微妙之处。与其他进程不同的是,PID 1没有默认的信号处理程序,这意味着如果Jenkins没有明确地为SIGTERM安装信号处理程序,那么该信号在发送时将被丢弃(而默认行为是终止该过程)。

Tini确实安装了显式信号处理程序(顺便说一下,是为了转发信号),所以这些信号不再被丢弃。相反,它们被发送到Jenkins,Jenkins并不像PID 1(Tini )那样运行,因此有默认的信号处理程序(注意:这不是Jenkins使用Tini的原因,Jenkins使用它来获取信号,但在RabbitMQ的镜像中是这个作用)。


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

希望以上内容对你有所帮助!

如果您有兴趣了解更多,以下是一些可供参考的资料:

  • 僵尸进程详解:https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/
  • 更简洁的解释:https://github.com/docker-library/official-images#init

最后,请注意Tini还有更多的选择(比如Phusion的基础镜像)。

Tini的主要特性是:

  • 做PID 1需要做的一切,而不做其他任何事情。像读取环境文件、改变用户、过程监控等事情不在Tini的范围内(还有其他更好的工具);
  • 零配置就能上手(如果运行不正常,Tini >= 0.6也会警告您);
  • 它有丰富的测试。

相关文章:

  • 计算机三级数据库高级查询
  • CSDN文章自动转移到印象笔记?一怒之下的我“揍”出了代码~
  • 图像处理学习笔记-04-频率域滤波04
  • Halcon学习---光学字符识别(OCR)
  • 【电商数仓】数仓搭建之DIM维度层(商品、优惠券、活动、地区、时间维度表)
  • class11:cookie和session
  • 一些常用的刷题网站
  • Python自动化小技巧11——excel文件的文字内容筛选
  • ArrayList的源码分析
  • 不支持TLS的设备如何实现游客登录加密通信方案
  • 【Pandas 数据分析3-2】Pandas 数据读取与输出 - Excel
  • TiDB Dashboard 实例性能分析 - 持续分析页面
  • Spring Boot 集成 Redis 配置 MyBatis 二级缓存
  • 9 二叉树-添加
  • SSM进阶-搭建Dubbo
  • -------------------- 第二讲-------- 第一节------在此给出链表的基本操作
  • 【许晓笛】 EOS 智能合约案例解析(3)
  • 2018以太坊智能合约编程语言solidity的最佳IDEs
  • centos安装java运行环境jdk+tomcat
  • ECMAScript 6 学习之路 ( 四 ) String 字符串扩展
  • ES学习笔记(12)--Symbol
  • Mysql优化
  • nginx(二):进阶配置介绍--rewrite用法,压缩,https虚拟主机等
  • 大型网站性能监测、分析与优化常见问题QA
  • 猴子数据域名防封接口降低小说被封的风险
  • 后端_MYSQL
  • 基于遗传算法的优化问题求解
  • 软件开发学习的5大技巧,你知道吗?
  • 实战|智能家居行业移动应用性能分析
  • 协程
  • 一起来学SpringBoot | 第十篇:使用Spring Cache集成Redis
  • # 透过事物看本质的能力怎么培养?
  • #stm32驱动外设模块总结w5500模块
  • (C++17) std算法之执行策略 execution
  • (Java岗)秋招打卡!一本学历拿下美团、阿里、快手、米哈游offer
  • (分享)一个图片添加水印的小demo的页面,可自定义样式
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (牛客腾讯思维编程题)编码编码分组打印下标题目分析
  • (四)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (转)AS3正则:元子符,元序列,标志,数量表达符
  • .NET Remoting Basic(10)-创建不同宿主的客户端与服务器端
  • .Net Remoting(分离服务程序实现) - Part.3
  • .NET WebClient 类下载部分文件会错误?可能是解压缩的锅
  • .Net(C#)常用转换byte转uint32、byte转float等
  • .NET连接数据库方式
  • /etc/sudoers (root权限管理)
  • :=
  • []我的函数库
  • [2019.3.20]BZOJ4573 [Zjoi2016]大森林
  • [2019.3.5]BZOJ1934 [Shoi2007]Vote 善意的投票
  • [Angular] 笔记 20:NgContent
  • [BZOJ 3531][Sdoi2014]旅行(树链剖分+线段树)
  • [BZOJ] 2006: [NOI2010]超级钢琴
  • [bzoj1006]: [HNOI2008]神奇的国度(最大势算法)
  • [C++]C++类基本语法