Python 如何在 Docker 中接收 SIGINT 以停止服务?

Python how to recieve SIGINT in Docker to stop service?

我正在 Python 中编写一个监控服务来监控另一个服务,虽然监控和调度部分工作正常,但我很难弄清楚如何使用SIGINT 信号发送到 Docker 容器。具体来说,该服务应该从 docker 停止或 Kubernetes 停止信号中捕获 SIGINT,但到目前为止还没有。我已将问题减少到一个最小的测试用例,它很容易在 Docker:

中复制
import signal
import sys
import time

class MainApp:

    def __init__(self):
        self.shutdown = False
        signal.signal(signal.SIGINT, self.exit_gracefully)
        signal.signal(signal.SIGTERM, self.exit_gracefully)

    def exit_gracefully(self, signum, frame):
        print('Received:', signum)
        self.shutdown = True

    def start(self):
        print("Start app")

    def run(self):
        print("Running app")
        time.sleep(1)

    def stop(self):
        print("Stop app")

if __name__ == '__main__':

    app = MainApp()

    app.start()

    # This boolean flag should flip to false when a SIGINT or SIGTERM comes in...
    while not app.shutdown:
        app.run()

    else: # However, this code gets never executed ...
        app.stop()
        sys.exit(0)

和相应的Docker文件,再次简约:

FROM python:3.8-slim-buster
COPY test/TestGS.py .
STOPSIGNAL SIGINT
CMD [ "python", "TestGS.py" ]

我选择 Docker 因为 Docker stop command 被记录为发出一个 SIGINT 信号,稍等片刻,然后发出一个 SIGKILL。这应该是一个理想的测试用例。

但是,当启动带有交互式 shell 的 docker 容器,并从第二个 shell 停止容器时,stop() 代码永远不会执行。验证问题,一个简单的:

$ docker inspect -f '{{.State.ExitCode}}' 64d39c3b

显示退出代码 137 而不是退出代码 0。

显然,正在发生以下两件事之一。 SIGTERM 信号没有传播到容器或 Python 运行时,这可能是真的,因为显然没有调用 exit_gracefully 函数,否则我们会看到信号的打印输出。我知道你必须小心如何从 Docker to actually get a SIGINT 中开始你的代码,但是当将停止信号线添加到 Docker 文件时,全局 SIGINT 应该被发布到容器,至少以我阅读文档的拙劣理解。

或者,我编写的 Python 代码根本没有捕捉到任何信号。无论哪种方式,我根本无法弄清楚为什么永远不会调用停止代码。我花了相当多的时间研究网络,但在这一点上,我觉得我是 运行 圈子,知道如何解决正确结束 python 脚本的问题 运行在 docker 中使用 SIGINT 信号?

谢谢

马文

解决方案:

应用程序必须 运行 作为 docker 内的 PID 1 才能接收 SIGINT。为此,必须使用 ENTRYPOINT 而不是 CMD。固定的 Docker 文件:

FROM python:3.8-slim-buster
COPY test/TestGS.py .
ENTRYPOINT ["python", "TestGS.py"]

构建图像:

docker build . -t python-signals

运行 图片:

docker run -it --rm --name="python-signals" python-signals

然后从第二个终端停止容器:

 docker stop python-signals

然后你得到预期的输出:

Received SIGTERM signal
Stop app

我觉得 Docker 只向 PID 1 发送 SIGTERMS 有点奇怪,但谢天谢地,这相对容易修复。下面的文章对解决这个问题很有帮助。

https://itnext.io/containers-terminating-with-grace-d19e0ce34290