Диагностика неисправностей Pod и контейнеров Kubernetes

KubernetesBeginner
Практиковаться сейчас

Введение

Такой статус Pod, как Pending или CrashLoopBackOff, является симптомом, а не первопричиной. Эффективная диагностика последовательно опирается на несколько источников сведений: конфигурацию объекта, условия, события, состояние каждого контейнера, журналы и проверку работающего контейнера.

Вы диагностируете две заранее подготовленные неисправности, исправите их, а затем изучите исправный многоконтейнерный Pod. Цель — освоить универсальную методику диагностики, а не запомнить одно конкретное исправление.

Для каждого инцидента придерживайтесь одного порядка: начните с общего обзора, выделите один объект, изучите его текущее состояние и события, проверьте сведения, относящиеся к конкретному контейнеру, а затем исправьте контроллер, которому принадлежит объект. Это предотвращает случайные изменения и помогает объяснить, почему исправление сработало.

Начните с состояния Pod

На этом этапе вы определите, какие рабочие нагрузки исправны, а какие требуют проверки. Начните с обзора всего пространства имён. Параметр -o wide сохраняет привычную таблицу, но добавляет полезные столбцы, например IP-адрес Pod и назначенный узел.

cd /home/labex/project/pod-troubleshooting
kubectl get deployments,pods -n practice-debug -o wide

Исправный Pod должен иметь статус 2/2 Running. У одного Pod Deployment должна быть проблема с загрузкой образа, а рабочий контейнер должен постоянно перезапускаться.

Типичный результат в тестовой среде выглядит следующим образом. Суффиксы Pod, возраст и количество перезапусков могут отличаться:

NAME                                 READY   STATUS             RESTARTS   AGE
pod/broken-image-58758b4d69-c7j7g    0/1     ImagePullBackOff   0          33s
pod/crashy-worker-5557cfb974-xwpst   0/1     Error              2          33s
pod/web-with-sidecar                 2/2     Running            0          33s

Не беспокойтесь, если рабочий контейнер сейчас имеет статус Error, а не CrashLoopBackOff. Он ненадолго запускается, завершается и ожидает, пока Kubernetes снова его запустит, поэтому отображаемое состояние меняется прямо во время наблюдения. Растущее значение RESTARTS — более надёжный признак того, что контейнер постоянно завершается с ошибкой.

Выведите причины ожидания на уровне контейнеров, не просматривая весь YAML Pod:

kubectl get pods -n practice-debug -o custom-columns='POD:.metadata.name,PHASE:.status.phase,READY:.status.containerStatuses[*].ready,WAITING_REASON:.status.containerStatuses[*].state.waiting.reason,RESTARTS:.status.containerStatuses[*].restartCount'

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

Например, WAITING_REASON=ImagePullBackOff указывает на возможную проблему с конфигурацией образа или доступом к реестру. У рабочего контейнера причина ожидания может отсутствовать, если последняя попытка завершилась состоянием Terminated. Поэтому одна таблица — это лишь отправная точка, а не полный диагноз.

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

Диагностика ошибки загрузки образа

На этом этапе вы проследите путь от статуса до событий, связанных с ошибкой загрузки образа. Сохраните имя созданного Pod, чтобы последующие команды обращались к тому же объекту:

IMAGE_POD=$(kubectl get pods -n practice-debug -l app=broken-image -o jsonpath='{.items[0].metadata.name}')
echo "$IMAGE_POD"
kubectl describe pod "$IMAGE_POD" -n practice-debug

Читайте вывод снизу вверх. В разделе Events должно быть указано, что Kubernetes не может загрузить nginx:not-a-real-tag. Напрямую подтвердите настроенный образ:

kubectl get pod "$IMAGE_POD" -n practice-debug -o jsonpath='{.spec.containers[0].image}{"\n"}'
kubectl events -n practice-debug --for "pod/${IMAGE_POD}"

События объясняют, какое действие завершилось ошибкой, а запрос JSONPath подтверждает, какой именно образ был задан. Использование обоих источников помогает не принять временную проблему реестра за постоянно недействительный тег.

В тестовой среде ключевые строки выглядели примерно так:

Image:          nginx:not-a-real-tag
State:          Waiting
  Reason:       ErrImagePull
Failed to pull image "nginx:not-a-real-tag": ... manifest unknown
Error: ImagePullBackOff

ErrImagePull означает, что попытка загрузки завершилась ошибкой. ImagePullBackOff означает, что Kubernetes теперь увеличивает интервал перед повторением той же операции. Сообщение manifest unknown вместе с недействительным тегом связывает симптом с его причиной.

Исправляйте Deployment, а не созданный им Pod. Команда kubectl set image изменяет шаблон Pod, поэтому контроллер Deployment заменит неисправный Pod:

kubectl set image deployment/broken-image web=nginx:1.27-alpine -n practice-debug
kubectl rollout status deployment/broken-image -n practice-debug --timeout=60s

В случае успеха в конце вывода должны появиться строки:

deployment.apps/broken-image image updated
deployment "broken-image" successfully rolled out

Старый Pod является временным объектом, который можно удалить; шаблон Deployment — это постоянное желаемое состояние. После обновления должен появиться Pod с новым именем, а Deployment должен сообщить о наличии одной доступной реплики.

Анализ текущих и предыдущих журналов

На этом этапе вы диагностируете контейнер, который запускается, а затем завершается. Найдите его Pod и изучите сведения о перезапусках:

WORKER_POD=$(kubectl get pods -n practice-debug -l app=crashy-worker -o jsonpath='{.items[0].metadata.name}')
kubectl get pod "$WORKER_POD" -n practice-debug
kubectl describe pod "$WORKER_POD" -n practice-debug

Для перезапускающегося контейнера обычная команда kubectl logs выводит журналы текущей попытки запуска. Параметр --previous запрашивает у среды выполнения контейнеров журналы предыдущей попытки, которая уже завершилась:

kubectl logs "$WORKER_POD" -n practice-debug -c worker
kubectl logs "$WORKER_POD" -n practice-debug -c worker --previous || \
  echo "The previous container log is no longer available; continue with the current log and describe output."

В текущем или предыдущем выводе должно появиться собственное сообщение приложения:

configuration missing

Параметр --previous особенно полезен, когда новая попытка запуска начинается раньше, чем вы успеваете проверить завершившуюся с ошибкой. Однако это историческое свидетельство, а не гарантированно доступное хранилище: локальная среда выполнения уже могла удалить запись о том контейнере. Если вы видите unable to retrieve container logs, это ожидаемый результат, и нужно использовать текущее сообщение журнала вместе с выводом describe. Поля Last State, Exit Code и Restart Count по-прежнему подтверждают, что процесс неоднократно завершался.

Параметр -c worker явно указывает имя контейнера. Исправьте контроллер, заменив завершающуюся с ошибкой команду на постоянно работающий цикл:

kubectl patch deployment crashy-worker -n practice-debug --type='json' -p='[{"op":"replace","path":"/spec/template/spec/containers/0/command","value":["sh","-c","echo worker-ready; while true; do sleep 30; done"]}]'
kubectl rollout status deployment/crashy-worker -n practice-debug --timeout=60s

Длинная команда исправления может выглядеть сложно, поэтому разберите её изнутри наружу: path выбирает поле command первого контейнера, op: replace указывает тип изменения, а value задаёт новый массив команд. Вы меняете только одно поле, а остальная конфигурация Deployment остаётся без изменений.

JSON-патч точно заменяет одно поле в шаблоне Pod Deployment. В рабочей среде внесите эквивалентное изменение и в исходный манифест.

Дождитесь завершения развёртывания, поскольку команда сразу меняет желаемое состояние, но замена Pod и его переход в состояние готовности происходят асинхронно. Успешный ответ API ещё не доказывает, что рабочая нагрузка исправна.

Обращение к контейнерам в многоконтейнерном Pod

На этом этапе вы изучите Pod, контейнеры которого выполняют разные функции. Сначала выведите их имена:

kubectl get pod web-with-sidecar -n practice-debug -o jsonpath='{range .spec.containers[*]}{.name}{"\n"}{end}'

Конструкция JSONPath range перебирает список контейнеров. Получите журналы каждого контейнера отдельно:

kubectl logs web-with-sidecar -n practice-debug -c reporter --tail=5
kubectl logs web-with-sidecar -n practice-debug -c web --tail=5

Параметр --tail=5 ограничивает вывод последними пятью строками. Контейнер reporter обычно выводит повторяющиеся сообщения reporter-alive. NGINX может показать уведомления о запуске или пока не содержать журналов запросов. Оба результата нормальны, поскольку никто ещё не отправлял ему HTTP-запрос.

Выполните команду внутри контейнера web:

kubectl exec web-with-sidecar -n practice-debug -c web -- nginx -v

В тестовой среде образ вернул:

nginx version: nginx/1.27.5

Точная версия исправления может измениться после обновления образа; главное, что команда успешно выполнилась внутри контейнера web.

Первый -- завершает параметры kubectl. Всё, что следует после него, является командой, выполняемой в выбранном контейнере.

Это различие важно: -c web — параметр, обрабатываемый kubectl, а nginx -v запускается внутри контейнера. Если не указать -c для многоконтейнерного Pod, kubectl может выбрать контейнер по умолчанию и показать сведения не о том процессе.

Контейнеры одного Pod используют общее сетевое пространство имён, поэтому reporter на базе BusyBox также может обратиться к NGINX через адрес обратной связи. В wget -qO- параметр -q скрывает сообщения о ходе выполнения, а -O- выводит тело ответа в стандартный вывод. Конвейер передаёт это тело команде head, которая оставляет только первые строки:

kubectl exec web-with-sidecar -n practice-debug -c reporter -- wget -qO- http://127.0.0.1 | head

Ответ должен начинаться с <!DOCTYPE html>. Это универсальный диагностический приём для следующих заданий: запустить небольшой HTTP-клиент внутри подготовленного Pod и проверить поведение приложения, не открывая новый внешний порт.

Подтверждение восстановления с разных сторон

На этом этапе вы убедитесь, что исправление контроллеров создало исправные Pod взамен неисправных. Одновременно проверьте доступность контроллеров и готовность Pod:

kubectl get deployments,pods -n practice-debug
kubectl get pods -n practice-debug -o custom-columns='POD:.metadata.name,READY:.status.containerStatuses[*].ready,RESTARTS:.status.containerStatuses[*].restartCount,OWNER:.metadata.ownerReferences[0].name'

Наконец, проверьте, описывают ли последние предупреждающие события всё ещё активные неисправности.

Параметр --field-selector type=Warning просит API-сервер вернуть только события типа Warning. Затем --sort-by=.metadata.creationTimestamp сортирует результаты от старых к новым, помогая отличить исторические ошибки от текущего состояния.

kubectl get events -n practice-debug --field-selector type=Warning --sort-by=.metadata.creationTimestamp

Старые предупреждения могут сохраняться и после восстановления. Для определения текущего состояния используйте условия Pod и доступность контроллеров, а события рассматривайте как объяснение того, как система пришла к этому состоянию.

После двух исправлений вывод текущего состояния должен выглядеть примерно так:

NAME                            READY   UP-TO-DATE   AVAILABLE
deployment.apps/broken-image    1/1     1            1
deployment.apps/crashy-worker   1/1     1            1

NAME                                 READY   STATUS    RESTARTS
pod/broken-image-...                 1/1     Running   0
pod/crashy-worker-...                1/1     Running   0
pod/web-with-sidecar                 2/2     Running   0

Вы всё ещё можете видеть старые предупреждения Failed, BackOff или даже раннее FailedCreatePodSandBox, появившееся при запуске кластера. Сопоставьте имена объектов и временные отметки: предупреждения относятся к заменённым Pod, тогда как новые Pod находятся в состоянии Running, а оба Deployment доступны. События — это история, а не индикатор состояния системы в реальном времени.

Итоговый вывод должен объединять текущие сведения: оба Deployment доступны, все предназначенные контейнеры готовы, а среди активных Pod нет объектов в состоянии ошибки загрузки образа или зацикленного падения. Диагностика завершается проверкой результата, а не выполнением команды исправления.

Итоги

Вы диагностировали ошибки загрузки образов и запуска, используя условия, события, состояние контейнеров, текущие и предыдущие журналы. Вы исправляли шаблоны контроллеров, а не созданные ими Pod, и научились обращаться к отдельным контейнерам с помощью logs и exec. В следующем задании вам предстоит применить этот подход, основанный на фактах, без пошаговых подсказок.