Как стать автором
Обновить
91.22
Холдинг Т1
Многопрофильный ИТ-холдинг

Как Kubernetes управляет жизненным циклом подов

Уровень сложностиСредний
Время на прочтение25 мин
Количество просмотров2.2K

Работая DevOps-инженером, я не раз сталкивался с необходимостью тонко управлять поведением подов в Kubernetes. Эти минимальные единицы развёртывания — на первый взгляд, простые объекты — на самом деле являются ключевым элементом всей архитектуры. Они создаются, масштабируются, перезапускаются и удаляются в ответ на изменения состояния кластера и заданные политики.

Однако особенно важно понимать, что завершение работы пода — это очень нетривиальный процесс. Это не просто «удаление контейнера», а целая процедура, включающая в себя механизмы graceful shutdown, взаимодействие с контроллерами, корректную работу с сервисами и многое другое.

В этой статье я подробно расскажу, как устроен процесс завершения работы пода в Kubernetes, что происходит «под капотом», какие подводные камни могут возникнуть и как обеспечить корректное поведение приложений при завершении их работы.

Содержание

Что такое жизненный цикл пода в Kubernetes?

Жизненный цикл пода представляет собой последовательность состояний, через которые проходит под с момента создания до окончательного завершения:

  1. pending — под создан, но хотя бы один из контейнеров ещё не запущен (например, ожидает скачивания образа); 

  2. running — под запущен, хотя бы один из контейнеров работает;

  3. succeeded — все контейнеры пода завершились успешно (код выхода 0); 

  4. failed — один или несколько контейнеров пода завершились с ошибкой; 

  5. unknown — статус пода не может быть определён (обычно из-за проблем с соединением);

  6. terminating — специальное состояние, указывающее, что под запущен на удаление. 

Почему важно корректное завершение работы подов?

Неправильное завершение работы подов может приводить к различным проблемам, включая:

  • Потерю пользовательских запросов, если приложение завершилось до завершения обработки активных соединений. 

  • Повреждение данных в случае некорректного закрытия соединений с БД. 

  • Долгое ожидание Kubernetes, если процесс завершения не настроен, что замедляет обновления и развёртывания. 

  • Преждевременное отключение сервисов в микросервисной архитектуре, что может нарушить работу всей системы. 

Чтобы избежать этих проблем, Kubernetes предоставляет механизмы для graceful shutdown (плавного завершения работы). Они включают в себя TerminationGracePeriod, хуки preStop, корректную обработку сигналов SIGTERM и возможность тонкой настройки логики завершения приложения.

Основные этапы завершения пода

Процесс завершения пода в Kubernetes состоит из таких ключевых этапов:

  1. Инициирование удаления пода. Kubernetes получает команду на удаление (kubectl delete pod) или под завершает работу из-за автоматического пересоздания (например, в случае обновления развёртывания). 

  2. Отправка SIGTERM контейнерам. Kubernetes отправляет сигнал SIGTERM всем контейнерам пода, давая им возможность корректно завершиться. 

  3. Выполнение хука preStop (если настроен). Если в конфигурации контейнера задан хук preStop, он выполняется перед остановкой контейнера. 

  4. Ожидание в течение terminationGracePeriodSeconds. Kubernetes ожидает заданное время (по умолчанию 30 секунд), чтобы дать процессам возможность завершиться. 

  5. Принудительное завершение контейнера (SIGKILL). Если контейнер не завершился за отведённое время, Kubernetes отправляет SIGKILL, уничтожая процесс принудительно. 

  6. Удаление пода из 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

  1. Изменение статуса пода на Terminating. Как только Kubernetes получает команду на удаление пода, он меняет его статус на Terminating. Под остаётся видимым в kubectl get pods, но его STATUS указывает Terminating. 

  2. Удаление пода из списка эндпоинтов сервисов. Если под является частью сервиса (Service), то Kubernetes удаляет его из списка Endpoints, чтобы больше не направлять на него трафик. Это предотвращает поступление новых запросов на под, но уже активные соединения могут продолжать работать. 

  3. Отправка SIGTERM контейнерам. Kubernetes отправляет сигнал SIGTERM всем контейнерам внутри пода, давая им возможность корректно завершиться. Контейнер должен обработать этот сигнал и выполнить необходимые операции (закрыть соединения с БД, очистить кеш, сохранить данные и т. д.). Если контейнер не перехватывает SIGTERM, то процесс завершается по умолчанию, что может привести к потере данных. 

  4. Выполнение preStop-хуков (если настроены). Если контейнер настроен с хуком preStop, то Kubernetes выполняет его перед завершением работы контейнера. preStop позволяет запустить кастомную команду (например, отправить сигнал другому сервису о завершении работы). 

  5. Ожидание terminationGracePeriodSeconds. Kubernetes даёт контейнерам время на завершение, используя параметр terminationGracePeriodSeconds (по умолчанию 30 секунд). Если контейнер завершился раньше этого времени, то процесс продолжается дальше. 

  6. Отправка SIGKILL и принудительное завершение контейнеров. Если контейнер не завершился за время terminationGracePeriodSeconds, то Kubernetes отправляет SIGKILL (kill -9), принудительно уничтожая процесс. Это предотвращает зависание подов, но может привести к некорректному завершению работы приложения. 

  7. Удаление пода из 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 выполняет следующие шаги:

  1. Отправляет SIGTERM всем контейнерам в поде. 

  2. Выполняет хуки preStop (если настроены). 

  3. Ожидает завершения terminationGracePeriodSeconds — в течение этого времени контейнеры должны завершить работу самостоятельно. 

  4. Если контейнеры не завершились вовремя, Kubernetes отправляет SIGKILL, принудительно уничтожая процессы. 

  5. Под удаляется из 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-хуков:

  1. exec (запуск команды внутри контейнера). Позволяет выполнить shell-команду внутри контейнера перед завершением. Используется для отключения сервисов, сохранения данных, логирования. 

  2. 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 контейнеру:

  1. Под переходит в статус Terminating. 

  2. Kubernetes выполняет хук preStop

    • Если это exec, команда выполняется внутри контейнера. 

    • Если это httpGet, запрос отправляется на эндпоинт контейнера. 

    • Только после завершения preStop, Kubernetes отправляет контейнеру SIGTERM

  3. Kubernetes ждёт terminationGracePeriodSeconds, чтобы контейнер завершился. 

  4. Если контейнер не завершился, 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 предоставляет встроенный механизм управления завершением, включающий:

  1. Отправку сигнала SIGTERM процессам внутри контейнера. 

  2. Выполнение preStop-хуков, если они настроены. 

  3. Ожидание завершения в течение terminationGracePeriodSeconds

  4. Принудительное завершение контейнера с помощью 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 мгновенно завершает процесс без возможности обработки. 

Чтобы контейнер завершался корректно и без потери данных, важно:

  1. Обрабатывать сигнал SIGTERM. Процесс внутри контейнера должен перехватывать SIGTERM и выполнять логику graceful shutdown. 

  2. Закрывать активные соединения. Например, для HTTP-серверов: остановка принятия новых соединений и корректное завершение текущих запросов. 

  3. Освобождать ресурсы. Закрытие файловых дескрипторов, соединений с базой данных, очистка кеша. 

  4. Учитывать 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, он может:

  1. Игнорировать его (если не установлен обработчик). 

  2. Обработать его (завершить активные задачи, сохранить данные и выйти). 

  3. Завершиться по умолчанию, если нет обработчика. 

Kubernetes отправляет SIGTERM контейнерам перед их завершением, давая им возможность graceful shutdown.

Как контейнерное приложение должно реагировать на SIGTERM?

  1. Перехватывать SIGTERM. Контейнер должен обрабатывать сигнал, а не игнорировать его. 

  2. Останавливать активные соединения. Завершать запросы, закрывать файловые дескрипторы. 

  3. Освобождать ресурсы. Чистить кеш, завершать фоновые задачи. 

  4. Выходить с кодом 0, сигнализируя, что завершение прошло успешно. 

Если контейнер не обрабатывает SIGTERM, он будет принудительно убит SIGKILL после terminationGracePeriodSeconds.

Различия между SIGTERM и SIGKILL:

Сигнал

Как работает

Можно обработать?

Когда используется в Kubernetes

SIGTERM (15)

Позволяет процессу завершиться корректно

Да

При удалении пода (kubectl delete pod)

SIGKILL (9)

Немедленно убивает процесс, без шанса на обработку

Нет

Если terminationGracePeriodSeconds истёк

Пример:

  1. Kubernetes отправляет SIGTERM и ждёт 30 секунд (terminationGracePeriodSeconds). 

  2. Если контейнер не завершился, Kubernetes отправляет SIGKILL

Почему SIGKILL — это плохо?

  • Процесс уничтожается мгновенно, без возможности сохранения данных. 

  • Открытые соединения рвутся, транзакции в базах данных могут остаться в некорректном состоянии. 

  • Логи могут не записаться. 

Почему важно правильно обрабатывать SIGTERM?

  • Стабильность. Исключает неожиданные сбои. 

  • Предотвращение потерь данных. База данных или файловая система не пострадают. 

  • Graceful shutdown. Сервис корректно отключается от других систем. 

  • Более предсказуемая работа в Kubernetes. Процесс завершения управляемый, без хаотичного SIGKILL

Пример проблем из-за неправильной обработки SIGTERM:

  1. API-сервер: получает SIGKILL, запросы пользователей теряются. 

  2. Брокер сообщений (Kafka, RabbitMQ): не успевает обработать сообщения, возможна их потеря. 

  3. База данных: открытые транзакции остаются незавершёнными, возможны повреждения данных. 

Демонстрация с 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

  1. Запускаем контейнер: docker run --rm -it container

  2. Узнаём PID процесса внутри контейнера: docker exec <container-id> ps aux

  3. Отправляем SIGTERMdocker kill --signal=SIGTERM <container-id>

  4. В логах контейнера увидим: 

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:

  1. kubectl delete pod

  2. readinessProbe становится Unhealthy (под больше не получает трафик). 

  3. Выполняется preStop (если есть). 

  4. Kubernetes отправляет SIGTERM

  5. Ждёт terminationGracePeriodSeconds

  6. Отправляет 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, журналируйте процесс и анализируйте.

Теги:
Хабы:
+4
Комментарии2

Публикации

Информация

Сайт
t1.ru
Дата регистрации
Дата основания
Численность
свыше 10 000 человек
Местоположение
Россия
Представитель
ИТ-холдинг Т1