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

Mar*_*sen 11 python signals sigkill sigint docker

我正在用 Python 编写一个监控服务来监控另一个服务,虽然监控和调度部分工作正常,但我很难弄清楚如何使用发送SIGINT到 Docker 容器的信号来正确关闭该服务。具体来说,该服务应该从 docker stop 或 Kubernetes stop 信号捕获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)
Run Code Online (Sandbox Code Playgroud)

以及相应的 Dockerfile,同样简约:

FROM python:3.8-slim-buster
COPY test/TestGS.py .
STOPSIGNAL SIGINT
CMD [ "python", "TestGS.py" ]
Run Code Online (Sandbox Code Playgroud)

我选择了 Docker,因为Docker stop 命令被记录为发出 SIGINT 信号,等待一会儿,然后发出 SIGKILL。这应该是一个理想的测试用例。

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

$ docker inspect -f '{{.State.ExitCode}}' 64d39c3b
Run Code Online (Sandbox Code Playgroud)

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

显然,两件事之一正在发生。信号SIGTERM不会传播到容器或 Python 运行时,这可能是真的,因为 exit_graceously 函数显然没有被调用,否则我们会看到信号的打印输出。我知道你必须小心如何从Docker 内部启动代码才能真正获得SIGINT,但是当将停止信号行添加到 Dockerfile 时,SIGINT应该向容器发出一个全局的,至少以我粗浅的理解阅读文档。

或者,我编写的 Python 代码根本没有捕获任何信号。不管怎样,我根本不明白为什么停止代码永远不会被调用。我花了相当多的时间研究网络,但在这一点上,我觉得我在绕圈子,知道如何解决使用信号正确结束在 docker 内运行的 python 脚本的问题吗SIGINT

谢谢

马文

Mar*_*sen 14

解决方案:

应用程序必须在 docker 内以 PID 1 的身份运行才能接收 SIGINT。为此,必须使用 ENTRYPOINT 而不是 CMD。固定的 Dockerfile:

FROM python:3.8-slim-buster
COPY test/TestGS.py .
ENTRYPOINT ["python", "TestGS.py"]
Run Code Online (Sandbox Code Playgroud)

构建图像:

docker build . -t python-signals
Run Code Online (Sandbox Code Playgroud)

运行图像:

docker run -it --rm --name="python-signals" python-signals
Run Code Online (Sandbox Code Playgroud)

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

 docker stop python-signals
Run Code Online (Sandbox Code Playgroud)

然后你就得到了预期的输出:

Received SIGTERM signal
Stop app
Run Code Online (Sandbox Code Playgroud)

Docker 只向 PID 1 发出 SIGTERMS,这对我来说似乎有点奇怪,但幸运的是,这相对容易修复。下面的文章对解决这个问题最有帮助。

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

  • (“ENTRYPOINT”和“CMD”连接在一起形成一个命令,无论该命令是什么,都将在容器内作为 pid 1 运行。对我来说,与原始 Dockerfile 最大的区别似乎是您是否指定了“停止信号`。) (5认同)