写点什么

dumb-init:一个 Docker 容器初始化系统

2016 年 1 月 08 日

容器化环境中,往往直接运行应用程序,而缺少初始化系统(如 systemd、sysvinit 等)。这可能需要应用程序来处理系统信号,接管子进程,进而导致容器无法停止、产生僵尸进程等问题。

Yelp 开发的 dumb-init ,旨在模拟初始化系统功能,避免上述问题的发生。

问题的根源

对于开发人员来说,希望在容器中运行的进程和普通进程行为一致,这样才能大大降低容器化迁移的成本,而无须让开发人员关注容器初始化和退出的流程。

归功于 Linux 的名字空间(namespace),从容器中看,由容器创建的第一个进程 pid 为 1。而对于 Linux 来说,pid 为 1 的进程,有着特殊的使命:

  1. 传递信号,确保子进程完全退出
  2. 等待子进程退出

子进程的优雅退出

对于第一点,如果 pid 为 1 的进程,无法向其子进程传递信号,可能导致容器发送 SIGTERM 信号之后,父进程等待子进程退出。此时,如果父进程不能将信号传递到子进程,则整个容器就将无法正常退出,除非向父进程发送 SIGKILL 信号,使其强行退出。

考虑如下进程树:

  • bash(PID 1)
    • app(PID2)

bash 进程在接受到 SIGTERM 信号的时候,不会向 app 进程传递这个信号,这会导致 app 进程仍然不会退出。对于传统 Linux 系统(bash 进程 PID 不为 1),在 bash 进程退出之后,app 进程的父进程会被 init 进程(PID 为 1)接管,成为其父进程。但是在容器环境中,这样的行为会使 app 进程失去父进程,因此 bash 进程不会退出。

举个例子:

复制代码
docker run ubuntu /bin/bash -c '(sleep 1000 &) && sleep 2000'

该命令会启动的容器,内部进程结构为:

复制代码
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 17960 2816 ? Ss 13:05 0:00 /bin/bash -c (sleep 1000 &) && sleep 2000
root 7 0.0 0.0 4348 748 ? S 13:05 0:00 sleep 1000
root 8 0.0 0.0 4348 644 ? S 13:05 0:00 sleep 2000

此时,如果对这个容器发送 SIGTERM 信号,该容器将不会退出:

复制代码
docker kill -s SIGTERM 8ef469d46b52

注意,直接使用 docker kill 命令,会向容器发送 SIGKILL 信号强制杀死进程。docker stop 命令会先发送 SIGTERM 信号,等待超时时间之后,发送 SIGKILL 信号。因此,此时通过这两个命令都能够结束容器,但都不能“优雅的”结束进程。

僵尸子进程

另一个问题是等待子进程退出。前面提到过,init 进程另一个任务,是需要接管子进程,确保其能正常退出。但是一般应用程序,不会考虑实现接管进程功能。当应用程序进程在容器中运行时,其子进程创建的子进程,就有可能成为僵尸进程。

这里来模拟这个过程,首先启动一个容器,执行 sleep 命令:

复制代码
docker run ubuntu /bin/bash -c 'sleep 1000'

此时,容器中只有一个 sleep 进程,其 PID 为 1。这时,我们进入这个容器,再启动一个 bash 进程和一个 sleep 进程,模拟应用程序派生出来的子进程。

首先进入容器,

复制代码
docker exec -it 4ecdaafb501f /bin/bash

然后创建进程:

bash -c 'sleep 1000'这时,容器中有一个 PID 为 1 的 sleep 进程,一个 bash 进程,一个父进程为 bash 进程的 sleep 进程。

复制代码
root@4ecdaafb501f:/# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 13:30 ? 00:00:00 sleep 1000
root 6 0 0 13:30 ? 00:00:00 /bin/bash
root 21 6 0 13:31 ? 00:00:00 sleep 1000

再新开一个回话进入容器,然后对 PID 为 6 的 bash 进程发送 SIGKILL 信号,将其杀死,该操作模拟应用程序的子进程结束场景。此时,bash 进程的子进程 sleep 进程,由于失去了父进程,将会由 PID 为 1 的 sleep 进程进行托管。但是,由于 sleep 命令不是标准的 init 系统,没有实现子进程托管的功能。此时的 PID 为 21 的进程,虽然已经结束,但是其没有被父进程回收(通过 waitpid 系统调用),进入僵尸进程状态。

复制代码
root@4ecdaafb501f:/# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 4348 668 ? Ss 13:30 0:00 sleep 1000
root 21 0.0 0.0 0 0 ? Z 13:31 0:00 [sleep] <defunct></defunct>

dumb-init 来了

dumb-init 解决了上述两个问题:向子进程代理发送信号和接管子进程。

默认情况下,dumb-init 会向子进程的进程组发送其收到的信号。原因也很简单,前面已经提到过,像 bash 这样的应用,自己接收到信号之后,不会向子进程发送信号。当然,dumb-init 也可以通过设置环境变量DUMB_INIT_SETSID=0来控制只向它的直接子进程发送信号。

另外 dumb-init 也会接管失去父进程的进程,确保其能正常退出。

dumb-init 使用

要在容器中使用 dumb-init,可以直接安装 deb 包,或者从源码构建。容器启动时,使用 dumb-init 作为初始进程,确保所有子进程都由 dumb-init 进程创建:

复制代码
docker run my_container dumb-init python -c 'while True: pass'

除了在容器中使用之外,dumb-init 也可以直接在 shell 脚本中使用。使用 dumb-init 作为 shell 的父进程,可以解决 shell 创建的子进程优雅退出问题。这种场景使用方式类似于 supervisord 或者 daemontools ,直接将脚本的 shebang 改成#!/usr/bin/dumb-init /bin/sh即可。


感谢魏星对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群InfoQ 好读者(已满),InfoQ 读者交流群(#2)InfoQ 好读者)。

2016 年 1 月 08 日 18:0024409

评论

发布
暂无评论
发现更多内容

LDAP身份认证管理最佳实践

龙归科技

服务器 ldap 客户端

半个多月时间4面阿里,已经成功拿下offer,分享一下个人面经

Java架构之路

Java 程序员 架构 面试 编程语言

十四五,鹏城应作先锋看,山河同襄智能体

脑极体

SRS流媒体服务器源码分析--RTMP消息play

赖猫

流媒体 SRS 音视频开发 流媒体开发

不吹不黑聊中台

小谢同学

云计算 中台 企业架构

Linux/Centos Epoll 原理解析

赖猫

Linux 高并发 epoll

拼多多五面面经(Java岗),全面涵盖Java基础到高并发级别

Java架构之路

Java 程序员 架构 面试 编程语言

翻译:《实用的Python编程》04_02_Inheritance

codists

Python 继承 inheritance

JAVA已经呈饱和趋势了吗?

cdhqyj

Java 程序员 工作 IT

Linux内核 设备树操作常用API

赖猫

Linux Linux内核

深入理解Linux内核 RCU 机制

赖猫

Linux linux编程 Linux内核

容器 & 服务:K8s 与 Docker 应用集群 (四)

程序员架构进阶

Docker Kubernetes 容器技术 28天写作 3月日更

Alluxio 助力 Kubernetes,加速云端深度学习

阿里巴巴云原生

人工智能 大数据 容器 云原生 k8s

Go语言学习笔记:抓取XKCD中文站的漫画

worry

go

寻找被遗忘的勇气(八)

Changing Lin

3月日更

芯翌科技领跑NIST-FRVT戴口罩人脸识别评测,助力后疫情时代科技创新

朋湖网

2021最新分享:阿里内部总监手码的“Redis学习手册”风靡全网

比伯

Java 编程 程序员 架构 面试

接口测试--apipost接口断言详解

测试人生路

接口

山东部署公安新平台!智慧警务情指行一体化系统解决方案

源中瑞-龙先生

女神营业!云通信产品运营带你玩转号码隐私保护:网约车、外卖等O2O行业的最佳实践

阿里云Edge Plus

云通信 通信云

话说cas

木子的昼夜

2021金三银四必备:“基础-中级-高级”Java程序员面试复习路线

比伯

Java 编程 程序员 架构 面试

OCE等你加入

Obsuite

云计算 私有云 滴滴夜莺 Obsuite

WebRTC 音视频同步原理与实现

阿里云视频云

阿里云 音视频 WebRTC 流媒体

Kafka 架构中 ZooKeeper 以怎样的形式存在?

码农架构

Java 消息中间件

全靠这份阿里大佬的“Java进阶面试手册”收获蚂蚁offer

周老师

Java 编程 程序员 架构 面试

新人报道

shun123456789

收藏!这些 IDE 使用技巧,你都知道吗

阿里巴巴云原生

Java ide 云原生 API 调度

分布式事务与解决方案

一个大红包

28天挑战 3月日更

音视频之opengl渲染图片

赖猫

音视频 音视频开发

职场求生攻略答疑篇之 5 —— 我,程序员,非常焦虑

臧萌

职场 成长

Hummer 轻量级跨端技术框架详解及实战

Hummer 轻量级跨端技术框架详解及实战

dumb-init:一个Docker容器初始化系统-InfoQ