专栏首页腾讯云TStack专栏惊!Docker竟有这些不为人知的bug

惊!Docker竟有这些不为人知的bug

| 作者简介

| Docker容器生成僵尸进程

现象

公司开发服务器上使用Docker跑了几个容器,这些容器都是长时间运行的。偶然发现服务器上有大量僵尸进程,大约有两三千个。简单跟踪了下,发现这些僵尸进程均是在容器的进程命名空间的。

12

ps aux | grep 'Z' | grep -v grepll /proc/${any_zombie_pid}

在容器里运行的程序是很正常的web server,怎么会这样呢?

Docker 和子进程“僵尸化”问题

初始进程的责任:“收割”“僵尸进程”

Unix 的进程之间是树状结构的关系。每个进程都可以派生出子进程,而除了最顶端的进程之外,也都会有一个父进程。

这个最顶端的进程就是初始进程,其在启动系统时被内核启动,并负责启动系统的其余功能部分。如:SSH 后台程序、Docker 后台程序、Apache/Nginx 和 GUI 桌面环境等等。这些程序又可能会派生出它们自己的子进程。

这一部分并没有什么问题。但问题在于当一个进程终止时,会发生什么?假设上图中的 bash (5) 进程结束了,那么其会转变为「废弃进程」(defunct process),也被称作为“僵尸进程”(zombie process)。

为什么会这样?因为 Unix 这样设计地目的,在于让父进程能够耐心“等待”子进程结束,从而获得其结束状态(exit status)。只有当父进程调用 waitpid() 之后,“僵尸进程”才会真正结束。手册里是这样描述地:

"一个已经终止但并未被“等待”的进程,就成为了一个“僵尸”。内核会记录这些“僵尸进程”的基本信息(PID、终止状态、资源占用信息),以确保其父进程在之后的时间里可以通过“等待”来获取这个子进程的信息。"

通常来说,人们会简单地认为“僵尸进程”就是那些会造成破坏的失控进程。但从 Unix 系统角度来分析,“僵尸进程”有着非常清晰地定义:进程已经终止,但尚未被其父进程“等待”。

绝大多数情况下,这都不会产生什么问题。在一个子进程上调用 waitpid() 以消除其“僵尸”状态,被称为“收割”。多数应用程序都能够正确地“收割”其子进程。在上例中,操作系统会在 bash 进程终止时发送 SIGCHLD 信号以唤醒 sshd 进程,其在接收到信号后就“收割”掉了此子进程。

但还有一种特殊情况——如果父进程终止了,无论是正常的(程序逻辑正常终止),还是用户操作导致的(比如用户杀死了该进程)——子进程会如何处理?它们不再拥有父进程,变成了「孤儿进程」(orphaned)(这是确切的技术术语)。

此时初始进程(PID 1)就会因其被赋予地特殊任务而介入——「领养」(adopt)(同样的,这是确切的技术术语)「孤儿进程」。这就意味着初始进程会成为这些子进程的父进程,而无论其是否由初始进程创建。

以 Nginx 为例,其默认就会作为后台程序运行。工作流程如下:Nginx 创建一个子进程后,自身进程结束,然后该子进程就被初始进程「领养」了。

其中的要点是什么?操作系统内核自动处理了「领养」逻辑,因此内核其实是希望初始进程也自动完成对这些「孤儿进程」的“收割”逻辑

这在 Unix 操作系统中是一个非常重要的机制,大量的软件都是因而设计和实现。几乎所有的服务(daemon)程序都预期初始进程会「领养」和“收割”其守护子进程。

尽管我们是以服务程序做例子,但系统并没有什么机制对此进行规约。任何一个进程在结束时,都会预期初始进程能够清理(「领养」和“收割”)其子进程。这一点,在《操作系统概述》和《Unix 系统高级编程》两书中描述地非常详细。

“僵尸进程”的危害

“僵尸进程”都已经终止了,它们危害在哪里?它们原本占用的内存已经释放了吗?在 ps 中除了多了些条目,还有什么别的吗?

是的,内存确实已经释放,但能够在 ps 中看到,说明它们还仍然占用着一些内核资源。对 Linux waitpid 的文档引用如下:

"在“僵尸进程”在被父进程“等待”以彻底消除之前,其仍然会被记录在内核进程表中。而当该表被写满后,新的进程将无法被创建。"

对 Docker 的影响

这个问题会如何对 Docker 产生怎样的影响?我们可以看到很多人只在他们的容器中跑一个进程,而且也认为只需要跑这么一个进程就足够了。但显而易见地,这些进程无法承担初始进程在前文中所述的任务逻辑。因此,为了能够正确地“收割”被「领养」的进程,我们需要另外的初始进程来完成这些工作。

举一个相对复杂地例子,我们的容器是一个 web 服务器,需要去跑一段基于 bash 的 CGI 脚本,而该脚本又会去调用 grep 程序。假定 web 服务器发现了 CGI 脚本执行超时,也中止了其继续执行。但此时 grep 程序并不会受到影响仍然继续执行,当其执行结束时,就变成了一个“僵尸进程”并由初始进程(即 web 服务器)「收养」。但 web 服务器无法正确地“收割”这个 grep 进程,所以该“僵尸进程”就在系统中常驻了。

这个问题同样也存在于其它场景中。我们能看到人们尝尝为第三方程序创建 Docker 容器——又如 PostgreSQL ——并将其作为容器中的主进程运行。当我们运行别人的代码时,我们如何确保这些程序*并不会*派生出子进程并因而堆积大量的“僵尸进程”?唯独仅有我们运行着自己的代码,同时还对所有的依赖包和依赖包的依赖包做严格地审查,才能杜绝这种问题。因此,通常来说,我们很有必要来执行一个合适的初始化系统(init system)来避免这些问题地发生。

解决方案

1. 重新编译容器镜像,像baseimage-docker一样,往镜像中引入一套轻量的初始化系统my_init,并将这个my_init程序作为容器运行的初始进程。

2. 将原来的CMD ["/path-to-your-app"]修改为CMD ["/bin/bash", "-c", "set -e && /path-to-your-app"] && true,这是一个不完善方案,因为没有干净地终止应用进程,可能会造成文件损坏,有风险。

| 容器的目录被其它的进程使用

现象

在正常停止Docker容器后,删除容器报错:

1234

Error response from daemon: Driver devicemapper failed to remove root filesystem a5144c558eabbe647ee9a25072746935e03bb797f4dcaf44c275e0ea4ada463a: remove /var/lib/docker/devicemapper/mnt/25cb26493fd3c804d96e802a95d6c74d7cae68032bf50fc640f40ffe40cc4188: device or resource busyError response from daemon: Driver devicemapper failed to remove root filesystem bdd60d5104076351611efb4cdb34c50c9d3f2136fdaea74c9752e2df9fd6f40f: remove /var/lib/docker/devicemapper/mnt/d2b5b784495ece1c9365bdea78b95076f035426356e6654c65ee1db87d8c03e7: device or resource busyError response from daemon: Driver devicemapper failed to remove root filesystem 847b5bb74762a7356457cc331d948e5c47335bbd2e0d9d3847361c6f69e9c369: remove /var/lib/docker/devicemapper/mnt/71e7b20dca8fd9e163c3dfe90a3b31577ee202a03cd1bd5620786ebabdc4e52a: device or resource busyError response from daemon: Driver devicemapper failed to remove root filesystem a85e44dfa07c060244163e19a545c76fd25282f2474faa205d462712866aac51: remove /var/lib/docker/devicemapper/mnt/8bcd524cc8bfb1b36506bf100090c52d7fbbf48ea00b87a53d69f32e537737b7: device or resource busy

快速解决方案

12345

# 找到使用容器目录的进程$ find /proc/*/mounts | xargs grep -E "526c823031c2065c6fb3b92f9aaded4477eccceb65f245391a1d8a6acae13d0e"/proc/27837/mounts:shm /var/lib/docker/containers/526c823031c2065c6fb3b92f9aaded4477eccceb65f245391a1d8a6acae13d0e/shm tmpfs rw,nosuid,nodev,noexec,relatime,size=65536k 0 0$ ps aux|grep 27837# 先停掉这些进程后,再就可以成功删除容器了

问题根源

https://github.com/moby/moby/issues/27381

Core of the issue here is that container is either still running or some of its mount points have leaked into other some mount namespace. You docker-pid and host both seem to be sharing same mount namespace. And that means docker daemon is running in host mount namespace. And that probably means that nginx started at some point after container start and it seems to be running in its own mount namespace. And at that time mount points leaked into nginx mount namespace and that’s preventing deletion of container.

原来是老的内核存在bug,Docker进程共享宿主机的mount命名空间,这样容器的挂载点被泄漏给其它进程的命名空间了。

解决方案

升级内核至3.10.0-693.5.2.el7.x86_64以后,另外安装Docker仓库里最新的docker-ce

1234567891011121314151617

sudo yum remove docker \                  docker-client \                  docker-client-latest \                  docker-common \                  docker-latest \                  docker-latest-logrotate \                  docker-logrotate \                  docker-selinux \                  docker-engine-selinux \                  docker-enginesudo yum install -y yum-utils \  device-mapper-persistent-data \  lvm2sudo yum-config-manager \    --add-repo \    https://download.docker.com/linux/centos/docker-ce.reposudo yum install docker-ce

| 参考

1.https://gist.github.com/snakevil/0b47072fcb626b87f4bd4ab80f7d8946

2.https://www.lijiaocn.com/%E9%97%AE%E9%A2%98/2017/07/14/docker-unable-to-rm-filesystem.html

3.https://github.com/moby/moby/issues/27381

4.https://docs.docker.com/install/linux/docker-ce/centos/#install-using-the-repository

但盼君来

拨“云”见日

腾讯云TStack

本文分享自微信公众号 - 腾讯云TStack(gh_035269c8aa5f)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-10-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 手把手教你打造 SDN 网路 (二) (安装准备)

    在安装完 Vagrant 和 Virtualbox 就可以开始进行,如果用的是 Windows 建议可以下载 Cmder 这个 Console Emulator...

    腾讯云TStack
  • 史上最全全全全的Cell V2干货详解在这!

    ? 本文作者 / 鹏飞师兄 专注于OpenStack计算、Python; 热爱大海、雪山。 Cell V2详解 ? Cell V2 第一次出现是在 Ocata...

    腾讯云TStack
  • Ceph Bulestore磁盘空间分配初探

    ? 本文作者 / spikehe(何诚) 爱好acg,小甲师兄的首席大弟子~ 在大佬中夹缝求生的实习boy 最近跟着小甲师兄优化Ceph块存储缓存,涉及IO映...

    腾讯云TStack
  • 一篇文章搞定Python多进程(全)

    前面写了三篇关于python多线程的文章,大概概况了多线程使用中的方法,文章链接如下:

    南山烟雨
  • Android多进程的数据库访问问题

    在Android开发中,我们可能会使用单独的进程来做一些事情,比如推送服务,心跳服务等,这些不需要主应用启动,只需要一个独立的进程即可。这时候我们一般都会采用启...

    飞雪无情
  • 理解操作系统进程--进程描述

    有了上述概念,现在就可以讨论操作系统怎样以一个有序的方式管理应用程序的执行,以达到以下目的:

    goodspeed
  • 深度好文|面试官:进程和线程,我只问这19个问题

    标准定义:进程是一个具有一定独立功能的程序在一个数据集合上依次动态执行的过程。进程是一个正在执行程序的实例,包括程序计数器、寄存器和程序变量的当前值。

    cxuan
  • 深度好文|面试官:进程和线程,我只问这19个问题

    标准定义:进程是一个具有一定独立功能的程序在一个数据集合上依次动态执行的过程。进程是一个正在执行程序的实例,包括程序计数器、寄存器和程序变量的当前值。

    C语言与CPP编程
  • 进程组、会话、控制终端概念,如何创建守护进程?

    守护进程,也就是通常所说的Daemon进程,是Linux中的后台服务进程。周期性的执行某种任务或等待处理某些发生的事件。

    睡魔的谎言
  • 操作系统复习笔记——第三章 进程

    进程可看做是正在执行的程序。进程需要一定的资源(如CPU时间、内存、文件和I/O设备)来完成其任务。这些资源在创建进程或执行进程时被分配。

    种花家的奋斗兔

扫码关注云+社区

领取腾讯云代金券