Поскольку скрипт запускается как pid 1 по желанию, а установка init: true
в docker-compose.yml
, похоже, ничего не меняет, я взял более глубокий диск в этом топе c. Это заставляет меня выяснять несколько ошибок, которые я сделал:
Ведение журнала
Подход к печати сообщения при обнаружении SIGTERM
был разработан как простой тестовый пример, чтобы увидеть, работает ли это в основном, прежде чем я забочусь об остановке сервера. Но я заметил, что сообщение не появляется по двум причинам:
Буферизация вывода
При запуске долгосрочного процесса в python, таком как HTTP-сервер (или любой while True
l oop для пример), отображается без вывода при запуске контейнера, прикрепленного с docker-compose up
(без флага -d
). Чтобы получать живые журналы, нам нужно запустить python с флагом -u
или установить переменную env PYTHONUNBUFFERED=TRUE
.
Нет конвейера журнала после остановки
Но основная проблема заключалась не в буферизация вывода (это всего лишь примечание, поскольку мне интересно, почему не было вывода журнала из контейнера). При отмене контейнера docker-compose
останавливает отправку журналов на консоль. Это означает, что с логической точки зрения он не может отображать ничего, что происходит ПОСЛЕ CTRL + C нажата .
Чтобы получить эти журналы, нам нужно дождаться, пока docker-compose
не остановит контейнер, и запустить docker-compose logs
. Он напечатает все, включая те, которые сгенерированы после нажатия CTRL + C. Используя docker-compose logs
, я обнаружил, что SIGTERM
передается в контейнер, и мой обработчик событий работает.
Остановка веб-сервера
С этими знаниями я попытался остановить экземпляр веб-сервера. Сначала это не работает, потому что недостаточно просто позвонить по номеру webServer.server_close()
. После выполнения любой работы по очистке требуется явный выход:
def terminate(signal,frame):
print("Start Terminating: %s" % datetime.now())
webServer.server_close()
sys.exit(0)
Когда sys.exit()
не вызывается, процесс продолжает работать, что приводит к ~ 10 секундам ожидания, прежде чем Docker убьет его.
Полный рабочий пример
Вот демонстрационный скрипт, реализующий все, что я узнал:
from http.server import BaseHTTPRequestHandler, HTTPServer
import signal
from datetime import datetime
import sys, os
hostName = "0.0.0.0"
serverPort = 80
class MyServer(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "text/html")
self.end_headers()
self.wfile.write(bytes("Hello from Python Webserver", "utf-8"))
webServer = None
def terminate(signal,frame):
print("Start Terminating: %s" % datetime.now())
webServer.server_close()
sys.exit(0)
if __name__ == "__main__":
signal.signal(signal.SIGTERM, terminate)
webServer = HTTPServer((hostName, serverPort), MyServer)
print("Server started http://%s:%s with pid %i" % ("0.0.0.0", 80, os.getpid()))
webServer.serve_forever()
Работая в контейнере, его можно было очень быстро остановить, не дожидаясь Docker чтобы убить процесс:
$ docker-compose up --build -d
$ time docker-compose down
Stopping python-test_app_1 ... done
Removing python-test_app_1 ... done
Removing network python-test_default
real 0m1,063s
user 0m0,424s
sys 0m0,077s