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