Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python inside docker container, gracefully stop

I'm running a very basic example of Python loop inside a Windows docker container, that I would like to gracefully stop.

The script is started this way in my dockerfile:

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

In the docker documentation it's said a SIGTERM signal is sent to the main command, so I'm trying to catch it this way:

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 :)")

In theory the signal should be caught by the handler, the boolean should toggle and the loop should exit and display my very last log line. It doesn't, it's just stopping the whole thing at the moment the signal is emitted (or 2-3 seconds later rather)

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

My last line log is never reached. Does anyone knows what's going on ? Is it a python specific issue, docker specific or Windows specific?

Also I tried to inspect the stopped container with docker logs, the last log isn't here either. Tried to add a sleep after it, same result.

Thanks,

like image 419
Tigzy Avatar asked Dec 19 '18 13:12

Tigzy


People also ask

How do you gracefully stop a docker container?

docker rm -f The final option for stopping a running container is to use the --force or -f flag in conjunction with the docker rm command. Typically, docker rm is used to remove an already stopped container, but the use of the -f flag will cause it to first issue a SIGKILL.

How do you kill a running session in a container?

Just Stopping the Container TL;DR: press ctrl+c then ctrl+d - that means, keep the ctrl key pressed, type a c, and let go of ctrl. Then the same with ctrl and d. If there's a non-shell process running, the combination is ctrl+c to interrupt it. Then you can exit the shell, or the container might exit already.

How do I exit without stopping container?

Press Ctrl-P, followed by Ctrl-Q, to detach from your connection. You'll be dropped back into your shell but the previously attached process will remain alive, keeping your container running.

How do I keep a python docker container running?

If you would like to keep your container running in detached mode, you need to run something in the foreground. An easy way to do this is to tail the /dev/null device as the CMD or ENTRYPOINT command of your Docker image. This command could also run as the last step in a custom script used with CMD or ENTRYPOINT.


2 Answers

this seems to still be pervasive when using docker-compose up in current versions which i've been investigating on Raspbian all morning (and led me here).

However running your example with docker-compose 2.1.1 via docker-compose up with the following configs shows that the last line of your python code is actually called, you just can't see it:

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 :)")

Verify

Use docker-compose logs to inspect logs after Ctrl-C:

$ 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 :)
like image 137
lys Avatar answered Oct 03 '22 05:10

lys


Just catch KeyboardInterrupt and that's it.

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!')
like image 39
grapes Avatar answered Oct 03 '22 07:10

grapes