Получить доступ

Чем SIGTERM отличается от SIGKILL и как правильно завершать сервис?

MiddleОперационные системы
Ещё спрашивают так:
  • Что такое graceful shutdown?
  • Что происходит с приложением при остановке контейнера?
  • Что такое сигналы в Unix?
Коротко

SIGTERM — вежливая просьба завершиться: процесс может её перехватить и закончить работу. SIGKILL — убийство ядром, перехватить нельзя.

  • Graceful shutdown: перестать принимать новые запросы, доделать текущие, закрыть соединения
  • Docker и Kubernetes шлют SIGTERM, через срок (10 с / 30 с) — SIGKILL
  • Процесс с PID 1 в контейнере без обработчика SIGTERM не завершится сам

А ещё Kubernetes: снятие пода из балансировки и SIGTERM идут параллельно — нужен preStop

Так подсказку видит пользователь sotto на живом собеседовании — за пару секунд, пока вы думаете вслух.

Подробно

Сигнал — асинхронное уведомление процессу от ядра или другого процесса. Процесс может перехватить большинство сигналов своим обработчиком, игнорировать или оставить действие по умолчанию.

Частые сигналы:

  • SIGTERM (15) — запрос на завершение, по умолчанию его шлёт kill;
  • SIGINT (2) — Ctrl+C в терминале;
  • SIGKILL (9) — немедленное завершение, перехватить нельзя; ресурсы освобождает ядро, но приложение ничего не успевает;
  • SIGHUP — потеря терминала; многие демоны перечитывают по нему конфиг;
  • SIGSEGV — недопустимый доступ к памяти;
  • SIGCHLD — дочерний процесс завершился.

Graceful shutdown — корректная остановка без потерь:

  1. перестать принимать новые соединения и сообщить балансировщику, что экземпляр уходит (readiness = false);
  2. дождаться завершения запросов в работе, с таймаутом;
  3. закоммитить смещения в Kafka, вернуть сообщения в очередь, дописать буферы логов;
  4. закрыть пулы соединений к базам;
  5. выйти.

В Spring Boot это server.shutdown=graceful, в Go — http.Server.Shutdown, в Python — обработчик signal.SIGTERM.

Контейнеры. docker stop шлёт SIGTERM главному процессу и через 10 секунд SIGKILL; Kubernetes — через terminationGracePeriodSeconds (30 с). Ловушки:

  • PID 1. Ядро не применяет действие по умолчанию к PID 1, поэтому процесс без обработчика игнорирует SIGTERM и умирает только от SIGKILL через таймаут. А если запустить через sh -c, сигнал получит оболочка, а не приложение. Решение — exec-форма ENTRYPOINT ["java", ...], exec в скрипте или минимальный init (tini, --init);
  • гонка с балансировкой. Kubernetes одновременно шлёт SIGTERM и убирает под из Endpoints, а прокси узнают об этом с задержкой. Если сразу перестать принимать соединения, часть запросов получит ошибку. Добавляют preStop с паузой в несколько секунд.

Пример

import signal, threading

stopping = threading.Event()
signal.signal(signal.SIGTERM, lambda *_: stopping.set())

while not stopping.is_set():
    handle_next_task(timeout=1)     # берём задачи, пока не попросили остановиться
finish_in_flight()                  # доделываем начатое и выходим

Что хотят услышать

  • Перехватываемый SIGTERM против неперехватываемого SIGKILL.
  • Шаги graceful shutdown.
  • Поведение Docker/Kubernetes: SIGTERM, грейс-период, SIGKILL.
  • PID 1 и exec-форма запуска, preStop — плюс.

Частые ошибки

  • Использовать kill -9 как обычный способ остановки.
  • Запускать приложение через shell-обёртку, которая не передаёт сигнал.

Связанные вопросы

Закрытая бета

Подсказки на собеседовании — в реальном времени

sotto слышит вопрос интервьюера и показывает короткий ответ и план, как в блоке «Коротко». Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем