Как Kubernetes управляет жизненным циклом подов
Работая DevOps-инженером, я не раз сталкивался с необходимостью тонко управлять поведением подов в Kubernetes. Эти минимальные единицы развёртывания — на первый взгляд, простые объекты — на самом деле являются ключевым элементом всей архитектуры. Они создаются, масштабируются, перезапускаются и удаляются в ответ на изменения состояния кластера и заданные политики.
Однако особенно важно понимать, что завершение работы пода — это очень нетривиальный процесс. Это не просто «удаление контейнера», а целая процедура, включающая в себя механизмы graceful shutdown, взаимодействие с контроллерами, корректную работу с сервисами и многое другое.
В этой статье я подробно расскажу, как устроен процесс завершения работы пода в Kubernetes, что происходит «под капотом», какие подводные камни могут возникнуть и как обеспечить корректное поведение приложений при завершении их работы.
Содержание
7. Распространённые ошибки и проблемы при завершении подов в Kubernetes
8. Как правильно тестировать graceful shutdown в Kubernetes?
9. Использование readiness/liveness probes вместе с graceful shutdown
Что такое жизненный цикл пода в Kubernetes?
Жизненный цикл пода представляет собой последовательность состояний, через которые проходит под с момента создания до окончательного завершения:
pending — под создан, но хотя бы один из контейнеров ещё не запущен (например, ожидает скачивания образа);
running — под запущен, хотя бы один из контейнеров работает;
succeeded — все контейнеры пода завершились успешно (код выхода 0);
failed — один или несколько контейнеров пода завершились с ошибкой;
unknown — статус пода не может быть определён (обычно из-за проблем с соединением);
terminating — специальное состояние, указывающее, что под запущен на удаление.
Почему важно корректное завершение работы подов?
Неправильное завершение работы подов может приводить к различным проблемам, включая:
Потерю пользовательских запросов, если приложение завершилось до завершения обработки активных соединений.
Повреждение данных в случае некорректного закрытия соединений с БД.
Долгое ожидание Kubernetes, если процесс завершения не настроен, что замедляет обновления и развёртывания.
Преждевременное отключение сервисов в микросервисной архитектуре, что может нарушить работу всей системы.
Чтобы избежать этих проблем, Kubernetes предоставляет механизмы для graceful shutdown (плавного завершения работы). Они включают в себя TerminationGracePeriod
, хуки preStop
, корректную обработку сигналов SIGTERM
и возможность тонкой настройки логики завершения приложения.
Основные этапы завершения пода
Процесс завершения пода в Kubernetes состоит из таких ключевых этапов:
Инициирование удаления пода. Kubernetes получает команду на удаление (
kubectl delete pod
) или под завершает работу из-за автоматического пересоздания (например, в случае обновления развёртывания).Отправка SIGTERM контейнерам. Kubernetes отправляет сигнал
SIGTERM
всем контейнерам пода, давая им возможность корректно завершиться.Выполнение хука preStop (если настроен). Если в конфигурации контейнера задан хук
preStop
, он выполняется перед остановкой контейнера.Ожидание в течение terminationGracePeriodSeconds. Kubernetes ожидает заданное время (по умолчанию 30 секунд), чтобы дать процессам возможность завершиться.
Принудительное завершение контейнера (SIGKILL). Если контейнер не завершился за отведённое время, Kubernetes отправляет
SIGKILL
, уничтожая процесс принудительно.Удаление пода из API Kubernetes. После завершения всех контейнеров под удаляется из etcd и больше не виден в
kubectl get pods
.
1. Общий процесс завершения пода
Удаление пода в Kubernetes это не мгновенное событие, а последовательный процесс, состоящий из нескольких этапов. Kubernetes предоставляет механизмы для плавного завершения работы контейнеров, что помогает избежать потери данных, прерывания сетевых соединений и других сбоев.
События, происходящие при удалении пода (kubectl delete pod)
Удаление пода может быть вызвано различными причинами:
Администратор вручную удаляет под с помощью
kubectl delete pod <pod-name>
.Под удаляется при обновлении (rolling update) в Deployment или StatefulSet.
Kubernetes пересоздаёт под при сбоях или изменении конфигурации (например, изменился
nodeSelector
и поду нужно быть пересозданным на другом узле).Нода, на которой запущен под, выходит из строя или становится NotReady.
Механизмы PodDisruptionBudget или Eviction API инициируют пересоздание подов.
Когда Kubernetes получает команду на удаление пода, он инициирует процесс graceful termination, который проходит через несколько этапов.
Переход из состояния Running в Terminating
Изменение статуса пода на Terminating. Как только Kubernetes получает команду на удаление пода, он меняет его статус на Terminating. Под остаётся видимым в
kubectl get pods
, но его STATUS указывает Terminating.Удаление пода из списка эндпоинтов сервисов. Если под является частью сервиса (Service), то Kubernetes удаляет его из списка Endpoints, чтобы больше не направлять на него трафик. Это предотвращает поступление новых запросов на под, но уже активные соединения могут продолжать работать.
Отправка SIGTERM контейнерам. Kubernetes отправляет сигнал
SIGTERM
всем контейнерам внутри пода, давая им возможность корректно завершиться. Контейнер должен обработать этот сигнал и выполнить необходимые операции (закрыть соединения с БД, очистить кеш, сохранить данные и т. д.). Если контейнер не перехватываетSIGTERM
, то процесс завершается по умолчанию, что может привести к потере данных.Выполнение preStop-хуков (если настроены). Если контейнер настроен с хуком
preStop
, то Kubernetes выполняет его перед завершением работы контейнера.preStop
позволяет запустить кастомную команду (например, отправить сигнал другому сервису о завершении работы).Ожидание terminationGracePeriodSeconds. Kubernetes даёт контейнерам время на завершение, используя параметр
terminationGracePeriodSeconds
(по умолчанию 30 секунд). Если контейнер завершился раньше этого времени, то процесс продолжается дальше.Отправка SIGKILL и принудительное завершение контейнеров. Если контейнер не завершился за время
terminationGracePeriodSeconds
, то Kubernetes отправляетSIGKILL (kill -9)
, принудительно уничтожая процесс. Это предотвращает зависание подов, но может привести к некорректному завершению работы приложения.Удаление пода из API Kubernetes. После завершения всех контейнеров под удаляется из etcd (хранилища состояний Kubernetes) и больше не отображается в
kubectl get pods
. Под больше не существует в системе, и Kubernetes может пересоздать его, если это предусмотрено манифестом (например, в Deployment).
Как Kubernetes определяет, что под завершён?
Kubernetes проверяет несколько условий перед окончательным удалением пода:
Завершены ли все контейнеры? Если хотя бы один контейнер всё ещё работает, под не будет удалён до истечения
terminationGracePeriodSeconds
.Были ли контейнеры принудительно завершены? Если процесс не завершился сам, то Kubernetes принудительно убивает его через
SIGKILL
.Удалён ли под из API? После завершения контейнеров, под удаляется из API Kubernetes (etcd). Команда
kubectl get pods
больше его не показывает.Больше нет ссылок на под в сервисах и конечных точках? Kubernetes удаляет под из связанных сервисов (Service, Ingress), чтобы он больше не принимал трафик.
2. TerminationGracePeriod в Kubernetes
Когда под удаляется (например, через kubectl delete pod
), Kubernetes выполняет следующие шаги:
Отправляет SIGTERM всем контейнерам в поде.
Выполняет хуки preStop (если настроены).
Ожидает завершения terminationGracePeriodSeconds — в течение этого времени контейнеры должны завершить работу самостоятельно.
Если контейнеры не завершились вовремя, Kubernetes отправляет
SIGKILL
, принудительно уничтожая процессы.Под удаляется из API Kubernetes (etcd).
Таким образом, terminationGracePeriodSeconds
играет ключевую роль в graceful shutdown, позволяя приложениям корректно закрывать соединения, сохранять данные и освобождать ресурсы.
Поведение по умолчанию (30 секунд) и возможность настройки
По умолчанию terminationGracePeriodSeconds
установлено в 30 секунд. Это означает, что у контейнеров есть 30 секунд на обработку SIGTERM
перед их принудительным завершением.
Можно задать своё значение в манифесте пода:
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
terminationGracePeriodSeconds: 10
containers:
- name: my-container
image: nginx
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "echo 'Shutting down...' && sleep 5"]
В этом примере:
Мы указываем
terminationGracePeriodSeconds: 10
, что даёт поду 10 секунд на завершение.Хук
preStop
выполняет командуecho 'Shutting down...' && sleep 5
, которая запускается перед выключением контейнера.Если контейнер не завершится в течение 10 секунд, Kubernetes отправит
SIGKILL
.
Если terminationGracePeriodSeconds
уменьшить, например, до 5 секунд, то контейнеру может быть мало времени на завершение.
Последствия:
Если приложение не успевает корректно завершить работу, оно может быть принудительно убито.
Данные могут потеряться (если процесс записи не завершился).
Соединения с клиентами могут быть разорваны без завершения запросов.
Это может быть полезно для сервисов, которые можно быстро пересоздать без последствий (например, stateless API-серверов).
Если terminationGracePeriodSeconds
увеличить, например, до 120 секунд, контейнер получает достаточно времени на корректное завершение.
Последствия:
Позволяет приложениям завершать длительные задачи (например, выгружать данные в базу или закрывать соединения).
Может замедлить пересоздание подов при развёртывании (rolling update), так как Kubernetes будет дольше ждать завершения старых подов.
Важно для stateful-приложений (например, баз данных, брокеров сообщений, стриминговых сервисов).
Допустим, у нас есть два пода, один с terminationGracePeriodSeconds: 5
, другой с terminationGracePeriodSeconds: 60
.
apiVersion: v1
kind: Pod
metadata:
name: fast-shutdown
spec:
terminationGracePeriodSeconds: 5
containers:
- name: app
image: busybox
command: ["/bin/sh", "-c", "trap 'echo Terminating...' SIGTERM && sleep 1000"]
---
apiVersion: v1
kind: Pod
metadata:
name: slow-shutdown
spec:
terminationGracePeriodSeconds: 60
containers:
- name: app
image: busybox
command: ["/bin/sh", "-c", "trap 'echo Terminating...' SIGTERM && sleep 1000"]
Что произойдёт при kubectl delete pod fast-shutdown slow-shutdown
?
Под
fast-shutdown
получитSIGTERM
, но через 5 секунд Kubernetes отправитSIGKILL
, так как процесс ещё работает (sleep 1000
).Под
slow-shutdown
получитSIGTERM
, но Kubernetes подождёт 60 секунд передSIGKILL
.
В логах можно будет увидеть:
fast-shutdown Terminating...
fast-shutdown Killed
slow-shutdown Terminating...
... (60 секунд ожидания) ...
slow-shutdown Killed
3. Хуки preStop в Kubernetes
preStop
— это специальный lifecycle-хук в Kubernetes, который выполняется перед остановкой контейнера в момент завершения пода. Когда Kubernetes удаляет под, он выполняет хук preStop
перед отправкой контейнеру сигнала SIGTERM
. Это даёт возможность выполнить важные операции перед завершением работы:
Вручную отключить под от балансировщика или сервисов.
Завершить активные соединения (например, WebSocket, HTTP, gRPC, TCP).
Сохранить важные данные или состояние.
Освободить ресурсы (очистить кеш, закрыть файлы).
Отправить уведомление другому сервису о завершении работы.
Kubernetes поддерживает два типа preStop-хуков:
exec (запуск команды внутри контейнера). Позволяет выполнить shell-команду внутри контейнера перед завершением. Используется для отключения сервисов, сохранения данных, логирования.
httpGet (отправка HTTP-запроса). Отправляет HTTP-запрос на заданную конечную точку перед остановкой контейнера. Может использоваться для информирования внешних сервисов о завершении работы.
Примеры:
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "echo 'Shutting down...' && sleep 5"]
Выполняет команду echo 'Shutting down...'
и ждёт 5 секунд перед завершением. Может быть полезно для журналирования или очистки ресурсов.
lifecycle:
preStop:
httpGet:
path: "/shutdown"
port: 8080
Перед завершением контейнера Kubernetes отправляет HTTP-запрос на /shutdown
порта 8080. Это может быть полезно, если приложение должно выполнить логику завершения (например, выгрузить данные в БД).
Хук preStop
выполняется до отправки SIGTERM
контейнеру:
Под переходит в статус Terminating.
Kubernetes выполняет хук
preStop
:Если это
exec
, команда выполняется внутри контейнера.Если это
httpGet
, запрос отправляется на эндпоинт контейнера.Только после завершения
preStop
, Kubernetes отправляет контейнеруSIGTERM
.
Kubernetes ждёт
terminationGracePeriodSeconds
, чтобы контейнер завершился.Если контейнер не завершился, Kubernetes отправляет
SIGKILL
.
Примечание: помните, что если preStop
зависнет или выполнится слишком долго, это задержит процесс завершения!
Если preStop выполняется 10 секунд, а terminationGracePeriodSeconds
установлен в 30 секунд, то Kubernetes будет ждать 10 + 30 = 40 секунд перед SIGKILL
.
Пример с 10-секундным preStop
и 30-секундным terminationGracePeriod
:
spec:
terminationGracePeriodSeconds: 30
containers:
- name: my-app
image: my-image
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
Если команда в preStop
выполняется дольше, чем terminationGracePeriodSeconds
, под будет «висеть» до окончания выполнения preStop
.
Пример:
spec:
terminationGracePeriodSeconds: 20
containers:
- name: my-app
image: my-image
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 30"]
В этом случае Kubernetes будет 30 секунд ждать выполнения preStop
, а затем принудительно убьёт контейнер с помощью SIGKILL
, игнорируя terminationGracePeriodSeconds
.
Решение: убедиться, что preStop
выполняется быстрее, чем terminationGracePeriodSeconds
.
Примеры использования хуков preStop в YAML-манифесте
Graceful shutdown веб-приложения
Отключаем под от балансировщика (Nginx, HAProxy, Envoy) перед завершением:
apiVersion: v1
kind: Pod
metadata:
name: web-app
spec:
terminationGracePeriodSeconds: 20
containers:
- name: web
image: nginx
lifecycle:
preStop:
exec:
command: ["/usr/bin/curl", "-X", "POST", "http://127.0.0.1:8080/deregister"]
Перед своим выключением под сообщает балансировщику, что уходит из ротации.
Завершение активных соединений в базе данных
Перед остановкой пода отправляем SQL-запрос на завершение соединений:
apiVersion: v1
kind: Pod
metadata:
name: db
spec:
terminationGracePeriodSeconds: 60
containers:
- name: postgres
image: postgres
lifecycle:
preStop:
exec:
command:
- "/bin/sh"
- "-c"
- "psql -U postgres -c 'SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'active';'"
Это помогает избежать зависших соединений после завершения пода.
Очистка временных файлов перед остановкой контейнера
Перед завершением пода очищаем временные файлы внутри контейнера:
apiVersion: v1
kind: Pod
metadata:
name: worker
spec:
terminationGracePeriodSeconds: 15
containers:
- name: worker
image: my-worker
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "rm -rf /tmp/cache"]
Это освобождает ресурсы перед выключением.
Хук
preStop
выполняется доSIGTERM
, позволяя контейнерам корректно завершиться.Есть два типа хуков:
exec
— выполняет команду в контейнере.httpGet
— отправляет HTTP-запрос.
preStop
удлиняет время завершения пода, так как он выполняется доterminationGracePeriodSeconds
.Если хук
preStop
зависнет, он может задержать удаление пода.Использование
preStop
полезно для отключения подов от сервисов, очистки ресурсов и graceful shutdown.
4. Механизм graceful shutdown в Kubernetes
В Kubernetes graceful shutdown (плавное завершение работы) — это процесс корректного завершения пода, позволяющий контейнерам завершить активные операции перед их остановкой. Kubernetes предоставляет встроенный механизм управления завершением, включающий:
Отправку сигнала
SIGTERM
процессам внутри контейнера.Выполнение
preStop
-хуков, если они настроены.Ожидание завершения в течение
terminationGracePeriodSeconds
.Принудительное завершение контейнера с помощью
SIGKILL
(если процесс не завершился вовремя).
Последовательность действий при завершении пода
Когда Kubernetes получает команду kubectl delete pod <pod-name>
, он выполняет следующие шаги:
1. Отправка SIGTERM
контейнерам:
Kubernetes отправляет сигнал
SIGTERM
всем процессам внутри контейнеров пода.Если приложение обрабатывает
SIGTERM
, то оно начинает процесс graceful shutdown.Если контейнер не обрабатывает
SIGTERM
, то процесс завершается немедленно (или остаётся висеть, пока не получитSIGKILL
).
2. Выполнение хука preStop
(если настроен):
Если контейнер имеет
preStop
-хук, он выполняется до обработкиSIGTERM
.Это позволяет, например, уведомить сервис о том, что под выключается, или закрыть соединения.
3. Ожидание окончания terminationGracePeriodSeconds
:
Kubernetes ждёт указанное в
terminationGracePeriodSeconds
время (по умолчанию 30 секунд).За это время контейнер должен завершиться корректно.
4. Принудительное завершение контейнера (SIGKILL
):
Если контейнер всё ещё работает после
terminationGracePeriodSeconds
, Kubernetes отправляетSIGKILL
.SIGKILL
мгновенно завершает процесс без возможности обработки.
Чтобы контейнер завершался корректно и без потери данных, важно:
Обрабатывать сигнал
SIGTERM
. Процесс внутри контейнера должен перехватыватьSIGTERM
и выполнять логику graceful shutdown.Закрывать активные соединения. Например, для HTTP-серверов: остановка принятия новых соединений и корректное завершение текущих запросов.
Освобождать ресурсы. Закрытие файловых дескрипторов, соединений с базой данных, очистка кеша.
Учитывать
terminationGracePeriodSeconds
. Подобрать оптимальное значение, чтобы процесс завершался доSIGKILL
.
Примеры кода на Go и Python для обработки SIGTERM
Обработка SIGTERM
в Go:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
fmt.Println("App is running...")
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGTERM, syscall.SIGINT)
<-stop
fmt.Println("Received SIGTERM, shutting down gracefully...")
time.Sleep(5 * time.Second)
fmt.Println("Cleanup complete, exiting.")
}
Что делает код?
Запускает сервер, который ждёт
SIGTERM
.При получении
SIGTERM
выполняет очистку (например,time.Sleep(5 * time.Second)
имитирует завершение работы).Завершается корректно.
Обработка SIGTERM
в Python:
import signal
import time
def graceful_shutdown(signum, frame):
print("Received SIGTERM, shutting down gracefully...")
time.sleep(5
print("Cleanup complete, exiting.")
exit(0)
signal.signal(signal.SIGTERM, graceful_shutdown)
print("App is running...")
while True:
time.sleep(1)
Что делает код?
Запускает бесконечный цикл (как работающий сервер).
При получении
SIGTERM
выполняетgraceful_shutdown()
, где имитируется очистка ресурсов перед выходом.
Тестирование graceful shutdown в Kubernetes
Запускаем под с обработкой
SIGTERM
:
apiVersion: v1
kind: Pod
metadata:
name: graceful-app
spec:
terminationGracePeriodSeconds: 10
containers:
- name: app
image: my-app
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "echo 'Shutting down...' && sleep 5"]
Удаляем под и наблюдаем процесс завершения:
kubectl delete pod graceful-app --grace-period=10
Проверяем логи:
kubectl logs graceful-app --previous
Ожидаемый вывод:
Shutting down...
Received SIGTERM, shutting down gracefully...
Cleanup complete, exiting.
Kubernetes управляет завершением пода, сначала отправляя
SIGTERM
, затем ожидаяterminationGracePeriodSeconds
, и в крайнем случае отправляяSIGKILL
.Для graceful shutdown приложение должно:
Обрабатывать
SIGTERM
.Завершать активные соединения и очищать ресурсы.
Использовать
preStop
-хуки для дополнительных действий.
terminationGracePeriodSeconds
должен быть достаточно большим, чтобы приложение успело корректно завершиться.
5. Обработка SIGTERM в контейнерах
SIGTERM (signal 15)
— это стандартный сигнал в Unix/Linux, предназначенный для корректного завершения процесса. В отличие от SIGKILL
, он даёт процессу возможность выполнить очистку перед завершением.
Когда процесс получает SIGTERM
, он может:
Игнорировать его (если не установлен обработчик).
Обработать его (завершить активные задачи, сохранить данные и выйти).
Завершиться по умолчанию, если нет обработчика.
Kubernetes отправляет SIGTERM
контейнерам перед их завершением, давая им возможность graceful shutdown.
Как контейнерное приложение должно реагировать на SIGTERM
?
Перехватывать SIGTERM. Контейнер должен обрабатывать сигнал, а не игнорировать его.
Останавливать активные соединения. Завершать запросы, закрывать файловые дескрипторы.
Освобождать ресурсы. Чистить кеш, завершать фоновые задачи.
Выходить с кодом 0, сигнализируя, что завершение прошло успешно.
Если контейнер не обрабатывает SIGTERM
, он будет принудительно убит SIGKILL
после terminationGracePeriodSeconds
.
Различия между SIGTERM
и SIGKILL
:
Сигнал | Как работает | Можно обработать? | Когда используется в Kubernetes |
| Позволяет процессу завершиться корректно | Да | При удалении пода ( |
| Немедленно убивает процесс, без шанса на обработку | Нет | Если |
Пример:
Kubernetes отправляет
SIGTERM
и ждёт 30 секунд (terminationGracePeriodSeconds
).Если контейнер не завершился, Kubernetes отправляет
SIGKILL
.
Почему SIGKILL
— это плохо?
Процесс уничтожается мгновенно, без возможности сохранения данных.
Открытые соединения рвутся, транзакции в базах данных могут остаться в некорректном состоянии.
Логи могут не записаться.
Почему важно правильно обрабатывать SIGTERM
?
Стабильность. Исключает неожиданные сбои.
Предотвращение потерь данных. База данных или файловая система не пострадают.
Graceful shutdown. Сервис корректно отключается от других систем.
Более предсказуемая работа в Kubernetes. Процесс завершения управляемый, без хаотичного
SIGKILL
.
Пример проблем из-за неправильной обработки SIGTERM
:
API-сервер: получает
SIGKILL
, запросы пользователей теряются.Брокер сообщений (Kafka, RabbitMQ): не успевает обработать сообщения, возможна их потеря.
База данных: открытые транзакции остаются незавершёнными, возможны повреждения данных.
Демонстрация с trap в shell-скриптах
В Bash можно обработать SIGTERM
с помощью trap
:
#!/bin/sh
cleanup() {
echo "Received SIGTERM, shutting down gracefully..."
sleep 5
echo "Cleanup complete, exiting."
exit 0
}
trap cleanup TERM
echo "App is running..."
while true; do sleep 1; done
Как работает этот скрипт?
При запуске: бесконечный цикл (эмуляция сервиса).
При SIGTERM: выполняет
cleanup()
:журналирует завершение;
ожидает 5 секунд (имитация завершения работы);
выходит с кодом 0.
Запуск контейнера и тестирование SIGTERM
Запускаем контейнер:
docker run --rm -it container
Узнаём PID процесса внутри контейнера:
docker exec <container-id> ps aux
Отправляем
SIGTERM
:docker kill --signal=SIGTERM <container-id>
В логах контейнера увидим:
Received SIGTERM, shutting down gracefully...
Cleanup complete, exiting.
Если бы не было trap, контейнер бы завершился сразу, без логов и очистки.
6. Debugging и диагностика завершения подов в Kubernetes
Корректное завершение подов в Kubernetes — важный аспект, который может влиять на стабильность сервисов. Если под не завершается ожидаемо, зависает в статусе Terminating или получает SIGKILL
, необходимо провести диагностику. Kubernetes предоставляет несколько инструментов для отладки процесса завершения подов.
Как проверить статус завершения пода (kubectl describe pod)
Основной командой для диагностики подов является: kubectl describe pod <pod-name>
Что искать в выводе kubectl describe pod
?
Статус пода. Если под в статусе Terminating слишком долго, значит он не успел завершиться в
terminationGracePeriodSeconds
.События (Events). События помогут понять, когда был отправлен
SIGTERM
, выполнялсяpreStop
, и был лиSIGKILL
. Пример вывода:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Killing 30s kubelet Stopping container app
Normal PreStopHook 30s kubelet Running preStop hook
Warning ForceKillPod 0s kubelet Pod was forcefully killed after timeout
Поле TerminationGracePeriodSeconds. В describe pod можно увидеть, сколько времени Kubernetes дал поду на завершение. Если это значение слишком маленькое, контейнер мог не успеть завершиться до
SIGKILL
.
Использование kubectl logs для отладки graceful shutdown
Если под не завершился корректно, полезно проверить его логи: kubectl logs <pod-name>
Если под уже удалён, то можно посмотреть логи последнего завершения: kubectl logs <pod-name> --previous
Что искать в логах?
Обрабатывает ли приложение SIGTERM?
Received SIGTERM, shutting down gracefully... Cleanup complete, exiting.
Выполняется ли preStop?
Running preStop hook…
Есть ли ошибки при завершении?
Error: connection refused
Процесс получил SIGKILL? Если в логах нет сообщений о graceful shutdown, значит процесс завершился
SIGKILL
.
Как увидеть задержки в завершении
Для мониторинга статуса завершения можно использовать: kubectl get pod <pod-name> --watch
Что можно заметить?
Если под остаётся в Terminating дольше
terminationGracePeriodSeconds
, то это указывает на проблему с завершением.Если под быстро исчезает после Terminating, значит он завершился корректно.
Пример вывода:
NAME READY STATUS RESTARTS AGE
my-app 1/1 Running 0 5m
my-app 1/1 Terminating 0 5m
my-app 0/1 Terminating 0 5m
Если Terminating висит долго, значит процесс не завершился.
Анализ событий с kubectl get events
Если под долго висит в Terminating, можно проверить системные события:
kubectl get events --sort-by=.metadata.creationTimestamp
Пример вывода и что он значит:
LAST SEEN TYPE REASON OBJECT MESSAGE
10s Normal Killing pod/my-app Stopping container app
10s Normal PreStopHook pod/my-app Running preStop hook
5s Warning ForceKillPod pod/my-app Pod was forcefully killed after timeout
Ключевые моменты:
PreStopHook
означает, что выполнялсяpreStop
.Killing
означает, что Kubernetes отправилSIGTERM
.ForceKillPod
говорит о том, что контейнер не завершился вовремя и Kubernetes отправилSIGKILL
.
Практический сценарий диагностики завершения пода
Под слишком долго висит в Terminating
kubectl get pod app --watch
Если статус Terminating остаётся дольше terminationGracePeriodSeconds
, проверяем подробности: kubectl describe pod app
В Events может быть видно:
Normal PreStopHook 30s kubelet Running preStop hook
Warning ForceKillPod 0s kubelet Pod was forcefully killed after timeout
Что делать?
Проверить, не завис ли
preStop
.Увеличить
terminationGracePeriodSeconds
, если приложение требует больше времени.
Контейнер получает SIGKILL (принудительно убивается)
Если контейнер не завершился вовремя, Kubernetes отправляет SIGKILL
. Это можно проверить в логах: kubectl logs app --previous
Если в логах нет сообщений о graceful shutdown, но под исчез, значит он получил SIGKILL
.
Что делать?
Убедиться, что приложение обрабатывает
SIGTERM
.Увеличить
terminationGracePeriodSeconds
.Проверить
kubectl describe pod на ForceKillPod
.
7. Распространённые ошибки и проблемы при завершении подов в Kubernetes
Хотя Kubernetes предоставляет механизмы graceful shutdown, неправильная настройка или отсутствие обработки SIGTERM
могут привести к проблемам. Рассмотрим наиболее частые ошибки и способы их устранения.
Приложение не обрабатывает SIGTERM и завершается принудительно
Если приложение внутри контейнера не обрабатывает сигнал SIGTERM
, то Kubernetes будет вынужден принудительно завершить процесс через SIGKILL (kill -9)
после истечения terminationGracePeriodSeconds
.
Как выявить проблему?
Проверить логи перед завершением:
kubectl logs <pod-name> --previous
Если нет сообщений о завершении работы (Received SIGTERM, shutting down gracefully…
), значит процесс не обрабатывает SIGTERM
.
Проверить события Kubernetes:
kubectl get events --sort-by=.metadata.creationTimestamp
Если в событиях есть ForceKillPod
, это значит, что Kubernetes отправил SIGKILL
из-за таймаута.
Решение: добавить обработку SIGTERM
в код приложения.
Пример обработки SIGTERM
в Python:
import signal
import time
def shutdown_handler(signum, frame):
print("Received SIGTERM, shutting down gracefully...")
time.sleep(5)
print("Cleanup complete, exiting.")
exit(0)
signal.signal(signal.SIGTERM, shutdown_handler)
print("Application running...")
while True:
time.sleep(1)
Пример для Go:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGTERM, syscall.SIGINT)
<-stop
fmt.Println("Received SIGTERM, shutting down...")
time.Sleep(5 * time.Second)
fmt.Println("Cleanup complete, exiting.")
}
Теперь приложение завершится корректно.
Хук preStop hook выполняется слишком долго или зависает
Если preStop
выполняется дольше, чем указано в terminationGracePeriodSeconds
, то Kubernetes задержит завершение пода, пока не выполнится preStop
или не истечёт тайм-аут.
Как выявить проблему?
Проверить состояние пода:
kubectl get pod <pod-name> --watch
Если под остаётся в Terminating дольше ожидаемого времени,
preStop
мог зависнуть.Посмотреть события (
kubectl describe pod
):
Events:
- Normal PreStopHook 30s kubelet Running preStop hook
- Warning ForceKillPod 0s kubelet Pod was forcefully killed after timeout
Решение: ограничить время выполнения preStop
и избегать зависания.
Проблемный preStop
(зависает):
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 100"]
Если terminationGracePeriodSeconds = 30
, но preStop
выполняется 100 секунд, то Kubernetes не будет ждать больше 30 секунд и отправит SIGKILL
.
Правильный preStop (тайм-аут 5 секунд):
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "timeout 5s some_command"]
Дополнительно:
Проверять
preStop
команды локально в контейнере перед деплоем.Использовать таймауты в скриптах (
timeout, set -e, set -o pipefail
).
Неправильная настройка terminationGracePeriodSeconds
Если terminationGracePeriodSeconds
слишком маленькое, то контейнер не успевает завершиться до SIGKILL
.
Как выявить проблему?
Проверить конфигурацию пода:
kubectl describe pod <pod-name>
Найти значение
terminationGracePeriodSeconds
.Проверить события Kubernetes:
kubectl get events --sort-by=.metadata.creationTimestamp
Если видно
ForceKillPod
, значитterminationGracePeriodSeconds
недостаточно.
Решение: настроить terminationGracePeriodSeconds
в зависимости от типа приложения.
Пример:
spec:
terminationGracePeriodSeconds: 60
Меньшее значение (5-10 сек) для stateless-приложений (Nginx, API-серверы).
Большее значение (60+ сек) для stateful-приложений (базы данных, брокеры сообщений).
Проблемы с завершением при использовании initContainers
initContainers
выполняются перед основным контейнером, но если они зависли или не завершились корректно, то Kubernetes может затянуть процесс завершения пода.
Как выявить проблему?
Проверить состояние пода:
kubectl get pod <pod-name> -o wide
Если
initContainer
не завершился, основной контейнер не стартует.Посмотреть логи initContainer:
kubectl logs <pod-name> -c <init-container-name>
Выяснить, почему
initContainer
не завершился.
Решение: использовать корректные таймауты и рестарты в initContainer
.
Проблемный initContainer
:
initContainers:
- name: init-db
image: postgres
command: ["sh", "-c", "until pg_isready; do sleep 5; done"]
Если база недоступна, initContainer
может зависнуть навсегда.
Правильный initContainer
(ограниченный таймаут):
initContainers:
- name: init-db
image: postgres
command: ["sh", "-c", "timeout 60s until pg_isready; do sleep 5; done"]
Ограничиваем выполнение 60 секундами, после чего контейнер завершится с ошибкой.
8. Как правильно тестировать graceful shutdown в Kubernetes?
Перед тем как запускать сервис в эксплуатацию, важно протестировать его поведение при завершении. Вот несколько способов, как это сделать.
Проверка обработки SIGTERM внутри контейнера. Запускаем под и отправляем в него SIGTERM
:
kubectl run test-pod --image=your-app-image -- sleep 1000
kubectl exec test-pod -- pkill -SIGTERM -f your-app
kubectl logs test-pod --previous
Если в логах нет сообщений о graceful shutdown, значит, приложение не обрабатывает SIGTERM
.
Тестирование удаления пода:
kubectl delete pod <pod-name>
kubectl logs <pod-name> --previous
Если в логах отсутствуют сообщения о корректном завершении, значит, процесс завершается слишком быстро или его убивает SIGKILL
.
Проверка задержек завершения. Запускаем под и удаляем его, наблюдая за процессом: kubectl get pods --watch
Если под остаётся в Terminating дольше, чем terminationGracePeriodSeconds
, проблема может быть в зависающем preStop
или неправильной обработке SIGTERM
.
Анализ событий Kubernetes:
kubectl get events --sort-by=.metadata.creationTimestamp
Если видим ForceKillPod
, значит, контейнеру не хватило времени на завершение.
Как установить terminationGracePeriodSeconds
?
spec:
terminationGracePeriodSeconds: 60
Если приложение обрабатывает долгие операции (например, запись в базу), то лучше увеличить время завершения.
9. Использование readiness/liveness probes вместе с graceful shutdown
readinessProbe
и livenessProbe
помогают Kubernetes понимать, когда под готов к работе и когда его нужно перезапустить.
Если под в процессе завершения, то readinessProbe
помогает исключить его из балансировки. Это предотвращает потерю трафика. Пример readinessProbe
, которая проверяет /health
:
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 3
periodSeconds: 5
Когда под удаляется, Kubernetes больше не направляет в него запросы.
Если процесс в контейнере завис и не отвечает, то livenessProbe
поможет автоматически его перезапустить. Пример:
livenessProbe:
exec:
command: ["/bin/sh", "-c", "pgrep app || exit 1"]
initialDelaySeconds: 10
periodSeconds: 10
Если команда не найдёт процесс my-app, то Kubernetes перезапустит контейнер.
Помните, что preStop
выполняется до SIGTERM
. Важно, чтобы readinessProbe
уже перестала возвращать 200 OK, иначе под будет получать новые запросы, пока завершает работу.
Правильная последовательность завершения с readinessProbe:
kubectl delete pod
readinessProbe
становится Unhealthy (под больше не получает трафик).Выполняется
preStop
(если есть).Kubernetes отправляет
SIGTERM
.Ждёт
terminationGracePeriodSeconds
.Отправляет
SIGKILL
(если процесс завис).
10. Советы по журналированию процессов завершения пода
Журналирование помогает понять, почему под завершился и какие шаги были выполнены перед SIGKILL
.
Приложение должно явно фиксировать момент получения SIGTERM
.
Пример для Python:
import logging
import signal
logging.basicConfig(level=logging.INFO)
def shutdown_handler(signum, frame):
logging.info("Received SIGTERM, starting shutdown...")
logging.info("Cleanup complete, exiting.")
exit(0)
signal.signal(signal.SIGTERM, shutdown_handler)
Теперь в логах будет видно, когда процесс начал завершаться.
Если используется preStop
, то важно логировать его выполнение.
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "echo 'Running preStop hook...' >> /var/log/prestop.log"]
Это позволяет проверить, выполнялся ли preStop
.
Можно собирать события завершения с помощью kubectl get events
и записывать их в систему мониторинга. Пример сбора событий:
kubectl get events --field-selector reason=Killing --sort-by=.metadata.creationTimestamp
Заключение
Мы детально разобрали, как Kubernetes управляет жизненным циклом подов и какие механизмы он предоставляет для корректного завершения контейнеров.
Ключевые моменты:
Процесс завершения пода состоит из нескольких этапов: отправки SIGTERM
, выполнения хука preStop
, ожидания terminationGracePeriodSeconds
, а затем (если необходимо) принудительного завершения SIGKILL
.
terminationGracePeriodSeconds
— это критически важный параметр, который определяет, сколько времени у контейнера есть на корректное завершение. Настройка его значения зависит от типа приложения (API, база данных, брокеры сообщений и т. д.).
Хук preStop
позволяет выполнить дополнительные действия перед остановкой контейнера: закрыть соединения, очистить ресурсы, уведомить другие сервисы.
Правильная обработка SIGTERM
в приложении необходима, иначе контейнер получит SIGKILL
и завершится принудительно, что может привести к потере данных или проблемам в работе системы.
Диагностика graceful shutdown требует использования команд kubectl describe pod, kubectl logs --previous, kubectl get events
. Эти инструменты помогают понять, почему под зависает в Terminating или почему Kubernetes отправляет SIGKILL
.
Лучшие практики:
Настроить
readinessProbe
, чтобы исключать под из балансировки перед завершением.Логировать обработку
SIGTERM
в коде приложения.Тестировать завершение (
kubectl delete pod + kubectl logs --previous
).Использовать
PodDisruptionBudget
для управления плановыми завершениями подов.
Kubernetes предлагает гибкие и мощные механизмы управления жизненным циклом подов, но их необходимо правильно настраивать. От того, насколько хорошо приложение обрабатывает завершение работы, зависит его стабильность и надёжность.
Главное правило: тестируйте поведение приложения при завершении пода. Проверяйте, как оно реагирует на SIGTERM
, как долго выполняется shutdown, журналируйте процесс и анализируйте.