在Docker容器中运行Python,优雅地停止。

10

我正在一个Windows docker容器内运行Python循环的非常基础的示例,我希望能够实现优雅停止

在我的dockerfile中,脚本是这样启动的:

CMD [ "python.exe" , "./test.py"]

在Docker文档中,它说会向主要命令发送SIGTERM信号,因此我尝试以如下方式捕获它:

import signal
import time
import logging, sys

class GracefulKiller:
  kill_now = False
  def __init__(self):
    signal.signal(signal.SIGINT, self.exit_gracefully)
    signal.signal(signal.SIGTERM, self.exit_gracefully)

  def exit_gracefully(self,signum, frame):
    self.kill_now = True

if __name__ == '__main__':
  logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
  killer = GracefulKiller()
  while True:
    time.sleep(1)
    logging.info("doing something in a loop ...")
    if killer.kill_now:
      break

  logging.info("End of the program. I was killed gracefully :)")
在理论上,信号应该被处理程序捕获,布尔值应该切换并且循环应该退出并显示我的最后一个日志行。但事实并非如此,它只是在信号被发出时(或者稍后2-3秒)就停止了整个过程。
C:\Users\Administrator\Documents\Projects\test>docker-compose up
Recreating test_1 ... done
Attaching to test_1
test_1  | INFO:root:doing something in a loop ...
test_1  | INFO:root:doing something in a loop ...
test_1  | INFO:root:doing something in a loop ...
test_1  | INFO:root:doing something in a loop ...
test_1  | INFO:root:doing something in a loop ...
test_1  | INFO:root:doing something in a loop ...
test_1  | INFO:root:doing something in a loop ...
test_1  | INFO:root:doing something in a loop ...
test_1  | INFO:root:doing something in a loop ...
test_1  | INFO:root:doing something in a loop ...
test_1  | INFO:root:doing something in a loop ...
test_1  | INFO:root:doing something in a loop ...
test_1  | INFO:root:doing something in a loop ...
test_1  | INFO:root:doing something in a loop ...
Gracefully stopping... (press Ctrl+C again to force)
Stopping test_1   ... done
我的最后一行日志从未到达。有人知道发生了什么吗?这是Python特定问题,Docker特定还是Windows特定?

我还尝试使用docker logs检查停止的容器,最后一条日志也不在这里。尝试添加延迟后,结果相同。

谢谢,


你曾经使用 docker-compose 让这个工作了吗? - Gino Mempin
2个回答

5

在我一整个早上调查树莓派系统上使用docker-compose up时,这个问题似乎仍然普遍存在。(这也是我来到这里的原因。)

然而,使用docker-compose 2.1.1和下面的配置参数,在docker-compose up中运行你的示例,可以显示你 Python 代码的最后一行,只不过你看不到它:

docker-compose.yaml

services:
  grace_test:
    container_name: grace_test
    build: .

Dockerfile

FROM python:3.8-slim-buster

# setup WORKDIR
ADD . /grace_test
WORKDIR /grace_test
CMD ["python", "test.py"]

test.py

import signal
import time
import logging, sys

class GracefulKiller:
  kill_now = False
  def __init__(self):
    signal.signal(signal.SIGINT, self.exit_gracefully)
    signal.signal(signal.SIGTERM, self.exit_gracefully)

  def exit_gracefully(self,signum, frame):
    self.kill_now = True

if __name__ == '__main__':
  logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
  killer = GracefulKiller()
  while True:
    time.sleep(1)
    logging.info("doing something in a loop ...")
    if killer.kill_now:
      break

  logging.info("End of the program. I was killed gracefully :)")

验证

Ctrl-C之后,使用 docker-compose logs 检查日志:

$ docker-compose logs
grace_test  | INFO:root:doing something in a loop ...
grace_test  | INFO:root:doing something in a loop ...
grace_test  | INFO:root:doing something in a loop ...
grace_test  | INFO:root:doing something in a loop ...
grace_test  | INFO:root:End of the program. I was killed gracefully :)

1
只需捕获 KeyboardInterrupt 即可。
if __name__ == '__main__':
  logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
  try:
    while True:
      time.sleep(1)
      logging.info("doing something in a loop ...")
  except KeyboardInterrupt as ex:
    print('goodbye!')

1
@tgogos 在哪里?我没有在标题或正文中找到 OP 需要操作系统信号的信息。从问题的标题和意图来看,他需要一种停止容器的方法,无论如何。 - grapes
1
我不需要信号,只是docker似乎在使用它来优雅地停止容器:https://www.ctl.io/developers/blog/post/gracefully-stopping-docker-containers/ - Tigzy
6
@Tigzy,当我使用docker run ...时,你的代码似乎可以正常工作,但当我使用docker-compose up时会失败。 GitHub上有一些问题提到了这种不稳定的行为(#3347#3317)。 - tgogos
@tgogos,你说的“工作”,是指你能看到结束日志吗?我在这里尝试使用docker run,但仍然没有任何输出。你在使用Windows系统吗? - Tigzy
2
让我们在聊天中继续这个讨论 - grapes
显示剩余10条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接