Команда VK Cloud перевела статью для тех, кто разбирает инциденты в Kubernetes с помощью kubectl debug. Автор рассказывает про незаметный пробел в данных: после завершения debug-сессии API Kubernetes не сохраняет контекст ее завершения — код возврата, длительность сессии и целевой контейнер исчезают при первом же изменении состояния пода. В статье как воспроизвести это тремя командами, почему так устроено на уровне спецификации API, чем это грозит при разборе инцидентов и комплаенсе и что можно сделать уже сегодня.

Сессия, не оставившая следа
Сессия kubectl debug может содержать единственное прямое наблюдение сбойного состояния системы. Но как только сессия завершилась, Kubernetes не сохраняет в своём API контекст её завершения. Это не баг kubectl, так намеренно устроен API Kubernetes для ephemeral-контейнеров (временных контейнеров, которые запускают для отладки, не затрагивая жизненный цикл пода).
Стоит состоянию пода измениться и API Kubernetes больше не отдаёт контекст завершения той debug-сессии. Код возврата с вашей находкой, длительность сессии, контейнер, на который вы нацелились, после следующего обновления пода API Kubernetes ничего из этого не хранит.
Вот как это выглядит и что значит для процесса разбора инцидентов.
Воспроизведите это тремя командами
Чтобы это увидеть, специальный кластер не нужен. Подойдёт любой кластер Kubernetes 1.25+. Три команды подтверждают пробел.
Шаг 1. Разверните стабильный целевой под:
kubectl run debug-target --image=nginx:alpine -n default kubectl wait --for=condition=Ready pod/debug-target -n default
Шаг 2. Подключите debug-сессию, поработайте 10 секунд, выйдите с отличительным кодом:
kubectl debug debug-target -n default \ --image=busybox:1.36 \ --target=nginx \ -it -- sh -c "echo 'finding: connection pool exhausted'; sleep 10; exit 42"
Примечание: --target — это возможность CLI kubectl, которая направляет debug-контейнер в пространство имён процессов целевого контейнера. Имя целевого контейнера API не хранит как поле в объекте пода.
Шаг 3. Сразу после выхода проверьте статус ephemeral-контейнера:
kubectl get pod debug-target -n default \ -o jsonpath='{.status.ephemeralContainerStatuses[*]}' | jq .
Что вы получаете:
{ "containerID": "containerd://...", "image": "busybox:1.36", "name": "debugger-xxxxx", "ready": false, "state": { "terminated": { "exitCode": 42, "finishedAt": "2026-04-17T16:43:56Z" } } }
Код возврата здесь виден, но только до тех пор, пока сохраняется запись State.Terminated. Как только любое другое событие меняет под, перезапускается другой контейнер, подключается вторая debug-сессия, под переразмещается и этот контекст завершения заменяется. Кода возврата предыдущей сессии уже не увидеть. Проверьте логи после завершения сессии:
kubectl logs debug-target -c debugger-xxxxx -n default Error from server (NotFound): container "debugger-xxxxx" not found
Нет lastState, на который можно опереться. Как только текущее состояние меняется, контекст завершения через API Kubernetes уже недоступен.
Почему так происходит: решение в спецификации API
Это не баг и не пропущенная возможность, это явное проектное решение API. Тип EphemeralContainerStatus в API Kubernetes (v1.32) не включает поле lastState:
EphemeralContainerStatus: containerID string image string name string ready boolean state ContainerState # lastState: отсутствует намеренно # restartCount: отсутствует намеренно
Сравните с ContainerStatus для обычного контейнера:
ContainerStatus: containerID string image string lastState ContainerState ← хранит предыдущее завершение name string ready boolean restartCount integer ← считает перезапуски state ContainerState
Ключевое отличие: ContainerStatus.lastState хранит запись о предыдущем завершении — код возврата, время старта, время финиша, причину — и переживает перезапуски. У EphemeralContainerStatus эквивалентного поля нет.
Ephemeral-контейнеры ввели для отладки, не затрагивая гарантии жизненного цикла пода. Их устройство намеренно избегает семантики перезапуска, и это влияет на то, как их состояние отдаётся в API Kubernetes. Спецификация Kubernetes определяет ephemeral-контейнеры как «не перезапускаемые при сбое». Поэтому механизмы, которые отслеживают перезапуски и сохраняют последнее состояние для обычных контейнеров, здесь исключены намеренно. См. документацию Kubernetes по ephemeral-контейнерам и справочник по kubectl debug для upstream-спецификации.

Что теряется на практике
Сигнал для расследования |
Виден после изменения состояния пода? |
Код возврата |
Только пока на месте текущий State.Terminated |
Длительность сессии |
Недоступна |
Целевой контейнер ( |
Не записан как поле API |
Логи debug-контейнера |
Недоступны после завершения |
Эти сигналы живут лишь временно и не сохраняются при переходах состояния пода.
Соглашение о кодах возврата особенно распространено в практике SRE: exit 42 для «connection pool exhausted», exit 1 для «config file missing». Стоит состоянию пода измениться и этих сигналов через API Kubernetes уже не увидеть.
Частичный обходной путь: перенаправьте находки в файл на общем volume перед выходом или держите kubectl logs -f в параллельном терминале, чтобы захватывать stdout в реальном времени. Учтите, что kubectl logs -f сработает, только если сессия активно пишет в stdout, а вы захватываете вывод до выхода — во время живого инцидента это возможно не всегда.
Влияние на реагирование на инциденты
Рассмотрим реалистичную последовательность передачи смены:
Дежурный инженер подключает
kubectl debugк проблемному подуТратит 12 минут на исследование внутри контейнера
Выявляет проблему, выходит с кодом 42
Записывает заметки по инциденту: «нашёл исчерпание пула соединений, exit 42»
Передаёт смену следующему инженеру
Следующий инженер пытается проверить:
# Сколько длилась debug-сессия? kubectl get pod my-pod -o jsonpath='{.status.ephemeralContainerStatuses[*]}' | jq . # Длительность: недоступна # На какой контейнер нацеливались? # --target — флаг CLI kubectl, в API не хранится # Можно посмотреть, что они запускали? kubectl logs my-pod -c debugger-xxxxx # Ошибка: контейнер не найден
Передача смены полностью зависит от заметок первого инженера. А если заметки неполны, во время живого инцидента так и бывает, диагностический контекст через API Kubernetes можно уже не увидеть. Второй инженер начинает с нуля.
В некоторых регулируемых средах у этого есть последствия для комплаенса. Фреймворки, которым нужна отслеживаемость операционных действий, например, требование PCI-DSS 10.3 к журналированию аудита или требования SOC 2 к активности доступа, одним лишь API Kubernetes не выполнить для сессий ephemeral-контейнеров.
API Kubernetes не записывает имя контейнера
--targetи длительность сессии как поля API. Поэтому ответить на вопрос «кто на какой контейнер смотрел и как долго», опираясь на одни лишь стандартные журналы аудита Kubernetes для сессий ephemeral-контейнеров, сейчас невозможно.
Что можно сделать сегодня
Логирование на уровне приложения: договоритесь в команде записывать находки в общий volume или внешнюю систему перед выходом. Просто, но требует дисциплины под давлением инцидента.
Захват в реальном времени через watch API: событийный захват в момент перехода в Terminated сохранит блок State.Terminated до того, как его заменит любая следующая модификация пода. Такой подход сохраняет состояние завершения ephemeral-контейнера в момент его возникновения, пока его не заменили последующие обновления пода. Для этого нужен watch на модификации пода, и расчёт на то, что никакой другой контроллер не обновит под раньше захвата.
Внешние системы наблюдаемости: направляйте находки debug-сессии во внешний журнал аудита или SIEM на уровне приложения перед выходом.
Пример реализации, который демонстрирует этот подход, доступен на github.com/opscart/k8s-causal-memory, с воспроизводимым сценарием в scenarios/05-ephemeral-exit/. Захваченная запись для 10-секундной сессии, вышедшей с кодом 42:
container_name: debugger-1776446626 target_container: nginx exit_code: 42 exit_class: ERROR duration_seconds: 10.0 node_name: opscart-m02
Всё, что API Kubernetes уже не отдаёт после изменения состояния пода, сохранено в момент выхода.
Стоит ли это KEP?
Здесь есть что улучшить в Kubernetes и формализовать это можно через KEP (Kubernetes Enhancement Proposal, формальное предложение по развитию проекта). Один из вариантов ввести для ephemeral-контейнеров минимальную историю завершения, похожую на lastState в ContainerStatus. Поле lastState можно добавить в EphemeralContainerStatus с минимальными ломающими изменениями: ephemeral-контейнеры никогда не перезапускаются. Оно хранило бы только самую последнюю запись о завершении.
Исключить lastState имело смысл, когда ephemeral-контейнеры вводили в alpha (Kubernetes v1.16). Но kubectl debug становится стандартным инструментом разбора инцидентов во всей экосистеме, и отсутствие записи о завершении даёт реальные операционные последствия.
По мере того как ephemeral-контейнеры становятся стандартным механизмом отладки, отсутствие даже минимальной истории завершения поднимает более широкие вопросы о наблюдаемости и аудируемости в самом Kubernetes.
Естественные владельцы такого предложения — SIG Node, которая отвечает за kubelet и жизненный цикл контейнеров, или SIG Instrumentation, которая отвечает за примитивы наблюдаемости.
Воспроизведите сценарий: github.com/opscart/k8s-causal-memory/scenarios/05-ephemeral-exit