【问题标题】:Python multiprocessing crashes docker containerPython 多处理使 docker 容器崩溃
【发布时间】:2019-02-12 13:12:16
【问题描述】:

当我在控制台中运行它时,有一个简单的 python 多处理代码就像一个魅力:

# mp.py
import multiprocessing as mp


def do_smth():
    print('something')


if __name__ == '__main__':
    ctx = mp.get_context("spawn")
    p = ctx.Process(target=do_smth, args=tuple())
    p.start()
    p.join()

结果:

> $ python3 mp.py
something

然后我用 Dockerfile 创建了一个简单的 Docker 容器:

FROM python:3.6

ADD . /app
WORKDIR /app

还有 docker-compose.yml:

version: '3.6'

services:
  bug:
    build:
      context: .
    environment:
      - PYTHONUNBUFFERED=1
    command: su -c "python3.6 forever.py"

forever.py 在哪里:

from time import sleep

if __name__ == '__main__':
    i = 0
    while True:
        sleep(1.0)
        i += 1
        print(f'hello {i:3}')

现在我使用 docker compose 运行 forever.py

> $ docker-compose build && docker-compose up 
...
some output
...
Attaching to mpbug_bug_1
bug_1  | hello   1
bug_1  | hello   2
bug_1  | hello   3
bug_1  | hello   4

到目前为止,一切都很好并且可以理解。但是当我尝试在 docker 容器中运行 mp.py 时,它会崩溃而没有任何消息:

> $ docker exec -it mpbug_bug_1 /bin/bash
root@09779ec47f9d:/app# python mp.py 
something
root@09779ec47f9d:/app# % 

代码要点可以在这里找到:https://gist.github.com/ilalex/83649bf21ef50cb74a2df5db01686f18

你能解释一下为什么 docker 容器会崩溃以及如何在不崩溃的情况下做到这一点吗?

提前谢谢你!

【问题讨论】:

  • 看起来不像是崩溃了。只是看起来它退出了。容器在执行完脚本后退出。
  • @Neil 但是forever.py 不能在没有崩溃的情况下退出。还是可以?
  • 如果您是exec-ing 进入运行forever.py 的容器,那么您将停止该进程并启动bash
  • @C.Nivs 当exec-ing 进入容器时,日志显示forever.py 仍在工作并打印 hello-message 直到我运行 mp.py
  • 嗯好吧。还有一个问题,“docker ps”的输出是什么a)在你的exec进入并运行mp.py之前和b)容器退出之后。

标签: python python-3.x docker docker-compose


【解决方案1】:

为了快速修复,不要使用 spawn 启动方法,和/或不要使用 su -c ...,两者都是不必要的 IMO。改为:

p = mp.Process(target=do_smth, args=tuple())

或者您可以使用--init 选项启动容器。

使用spawn start 方法,Python 也会启动一个semaphore tracker process 来防止信号量泄漏,你可以通过在中间暂停mp.py 看到这个过程,它看起来像:

472   463 /usr/local/bin/python3 -c from multiprocessing.semaphore_tracker import main;main(3)

这个进程由mp.py启动,但在mp.py之后退出,因此它不会被mp.py收割,而是应该被init设计收割。

问题是这个容器(命名空间)中没有init,而不是init,PID 1是su -c,因此su采用了死信号跟踪进程。

看来su错误地认为死子进程是命令进程(forever.py),没有检查关系,所以su盲目退出,作为PID 1退出,内核杀死容器中的所有其他进程,包括forever.py

可以通过strace 观察到这种行为:

docker run --security-opt seccomp:unconfined --rm -it ex_bug strace -e trace=process -f su -c 'python3 forever.py'

会输出如下错误信息:

strace: Exit of unknown pid 14 ignored

参考:Docker and the PID 1 zombie reaping problem (phusion.nl)

【讨论】:

    【解决方案2】:

    mp.py 看起来不像 forever.py 的等价物。 mp.py 将运行新的工作进程,它只会打印something 然后它会退出 => 当这个工作进程完成时,主进程中的join() 将立即退出。

    更好的等价于forever.py:工作进程在无限循环中打印hello消息,主进程将在join()-forever-mp.py中等待该工作进程退出:

    import multiprocessing as mp
    from time import sleep
    
    def do_smth():
        i = 0
        while True:
            sleep(1.0)
            i += 1
            print(f'hello {i:3}')
    
    if __name__ == '__main__':
        ctx = mp.get_context("spawn")
        p = ctx.Process(target=do_smth, args=tuple())
        p.start()
        p.join()
    

    更新docker-compose.yml:

    version: '3.6'
    
    services:
      bug:
        build:
          context: .
        environment:
          - PYTHONUNBUFFERED=1
        command: su -c "python3.6 forever-mp.py"
    

    测试:

    $ docker-compose build && docker-compose up 
    ...
    some output
    ...
    Attaching to multiprcs_bug_1_72681117a752
    bug_1_72681117a752 | hello   1
    bug_1_72681117a752 | hello   2
    bug_1_72681117a752 | hello   3
    bug_1_72681117a752 | hello   4
    

    检查容器中的进程:

    $ docker top multiprcs_bug_1_72681117a752
    UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
    root                38235               38217               0                   21:36               ?                   00:00:00            su -c python3.6 forever-mp.py
    root                38297               38235               0                   21:36               ?                   00:00:00            python3.6 forever-mp.py
    root                38300               38297               0                   21:36               ?                   00:00:00            /usr/local/bin/python3.6 -c from multiprocessing.semaphore_tracker import main;main(3)
    root                38301               38297               0                   21:36               ?                   00:00:00            /usr/local/bin/python3.6 -c from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=4, pipe_handle=6) --multiprocessing-fork
    

    【讨论】:

    • 当然它们并不等同。问题是当我们运行 python 多处理时意外停止容器。有关重现的步骤,请参阅要点。
    • 您的 mp.py 不会永远运行 - 它会打印“某事”,并且会以退出代码 0 结束。那么,为什么您期望该容器会永远运行呢?
    • 我希望forever.py 在我运行mp.py 时不会停止运行。它们之间没有逻辑联系。为什么容器停止forever.py?这是我的问题。
    • 当你同时运行mp.py时,你能重现forever.py的停止吗?
    • 不,我无法复制它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-23
    • 1970-01-01
    • 2021-10-08
    • 1970-01-01
    • 2016-11-29
    • 2019-01-25
    • 2017-02-27
    相关资源
    最近更新 更多