Устранение неисправностей: Почему мой Pod в Kubernetes завис в состоянии Pending или CrashLoopBackOff?

Pod'ы Kubernetes, зависшие в состояниях `Pending` или `CrashLoopBackOff`, могут остановить развертывание. Это подробное руководство объясняет эти распространенные состояния, предлагая практические пошаговые методы устранения неисправностей. Научитесь диагностировать такие проблемы, как нехватка ресурсов, ошибки извлечения образов, сбои приложений и неправильные настройки проверок с помощью команд `kubectl`. Получите действенные советы и лучшие практики для быстрого решения проблем с Pod'ами и поддержания надежной среды Kubernetes, гарантируя, что ваши приложения всегда работают.

Устранение неисправностей: Почему мой Pod в Kubernetes завис в состоянии Pending или CrashLoopBackOff?

Pending и CrashLoopBackOff выглядят похоже, когда вы ждете развертывания, но означают совершенно разные вещи. Pending обычно означает, что Kubernetes не смог разместить или подготовить pod. CrashLoopBackOff означает, что контейнер запустился, затем завершился, и Kubernetes задерживает следующий перезапуск.

Эта разница важна. Зависший pod часто является проблемой планировщика, образа или хранилища. Падающий pod обычно является проблемой приложения, команды, проверки, разрешений или памяти. Начните с этого разделения, и путь устранения неисправностей станет намного короче.

Понимание состояний Pod: Pending vs. CrashLoopBackOff

Прежде чем приступить к устранению неисправностей, важно понять, что означают эти два состояния.

Статус Pod: Pending

Pod в состоянии Pending означает, что Kubernetes принял объект Pod, но он еще не полностью перешел в состояние запущенного контейнера. Иногда он еще не был запланирован на узел. Иногда узел назначен, но извлечение образа, подключение тома или настройка песочницы не завершены.

Статус Pod: CrashLoopBackOff

Pod в состоянии CrashLoopBackOff означает, что контейнер внутри Pod многократно запускается, падает и перезапускается. Kubernetes реализует экспоненциальную задержку между перезапусками, чтобы предотвратить перегрузку узла. Это состояние почти всегда указывает на проблему с приложением, работающим внутри самого контейнера, или его непосредственным окружением.

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

Устранение неисправностей Pod'ов в состоянии Pending

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

1. Недостаточные ресурсы на узлах

Одна из самых частых причин зависания Pod в состоянии Pending — отсутствие на каком-либо узле кластера достаточного количества доступных ресурсов (CPU, память) для удовлетворения requests Pod'а. Планировщик не может найти подходящий узел.

Шаги диагностики:

  1. Опишите Pod: Команда kubectl describe pod — ваш лучший друг здесь. Она часто показывает события, объясняющие, почему Pod не может быть запланирован.

    kubectl describe pod <pod-name> -n <namespace>
    

    Ищите события типа "FailedScheduling" и сообщения типа "0/3 nodes are available: 3 Insufficient cpu" или "memory".

  2. Проверьте ресурсы узлов: Посмотрите текущее использование ресурсов и емкость ваших узлов.

    kubectl get nodes
    kubectl top nodes # (требуется metrics-server)
    

Решение:

  • Увеличьте емкость кластера: Добавьте больше узлов в ваш кластер Kubernetes.
  • Отрегулируйте запросы ресурсов Pod: Уменьшите requests для CPU и памяти в манифесте вашего Pod, если они установлены слишком высоко.
    resources:
      requests:
        memory: "128Mi"
        cpu: "250m"
    
  • Вытесните другие Pod'ы: Вручную вытесните Pod'ы с более низким приоритетом с узлов, чтобы освободить ресурсы (используйте с осторожностью).

2. Ошибки извлечения образа

Если Kubernetes может запланировать Pod на узел, но узел не может извлечь образ контейнера, Pod останется в состоянии Pending.

Распространенные причины:

  • Неправильное имя/тег образа: Опечатки в имени образа или использование несуществующего тега.
  • Отсутствие аутентификации для частного реестра: Отсутствие или неправильные ImagePullSecrets для частных реестров.
  • Сетевые проблемы: Узел не может связаться с реестром образов.

Шаги диагностики:

  1. Опишите Pod: Опять же, kubectl describe pod является ключевым. Ищите события типа "Failed" или "ErrImagePull" или "ImagePullBackOff".

    kubectl describe pod <pod-name> -n <namespace>
    

    Пример вывода события: Failed to pull image "my-private-registry/my-app:v1.0": rpc error: code = Unknown desc = Error response from daemon: pull access denied for my-private-registry/my-app, repository does not exist or may require 'docker login'

  2. Проверьте ImagePullSecrets: Убедитесь, что imagePullSecrets правильно настроены в вашем Pod или ServiceAccount.

    kubectl get secret <your-image-pull-secret> -o yaml -n <namespace>
    

Решение:

  • Исправьте имя/тег образа: Перепроверьте имя образа и тег в манифесте развертывания.
  • Настройте ImagePullSecrets: Убедитесь, что вы создали секрет docker-registry и связали его с вашим Pod или ServiceAccount.
    kubectl create secret docker-registry my-registry-secret \n      --docker-server=your-registry.com \n      --docker-username=your-username \n      --docker-password=your-password \n      --docker-email=your-email -n <namespace>
    
    Затем добавьте его в спецификацию Pod:
    spec:
      imagePullSecrets:
      - name: my-registry-secret
      containers:
      ...
    
  • Сетевое подключение: Проверьте сетевое подключение от узла к реестру образов.

Если вы используете частный реестр, проверьте также ServiceAccount. Многие команды прикрепляют imagePullSecrets к ServiceAccount по умолчанию в пространстве имен вместо каждого Deployment:

kubectl get serviceaccount default -n <namespace> -o yaml

Если секрет существует, но извлечение все равно не удается, убедитесь, что имя хоста реестра в секрете точно соответствует имени хоста в ссылке на образ. registry.example.com/app:v1 и https://registry.example.com/app:v1 — это не одно и то же.

3. Проблемы, связанные с томами

Если ваш Pod требует PersistentVolumeClaim (PVC), а соответствующий PersistentVolume (PV) не может быть предоставлен или привязан, Pod останется в состоянии Pending.

Шаги диагностики:

  1. Опишите Pod: Ищите события, связанные с томами.

    kubectl describe pod <pod-name> -n <namespace>
    

    События могут показывать FailedAttachVolume, FailedMount или подобные сообщения.

  2. Проверьте статус PVC и PV: Проверьте статус PVC и PV.

    kubectl get pvc <pvc-name> -n <namespace>
    kubectl get pv
    

    Ищите PVC, застрявшие в состоянии Pending, или PV, которые не привязаны.

Решение:

  • Убедитесь в наличии StorageClass: Убедитесь, что StorageClass определен и доступен, особенно при использовании динамического предоставления.
  • Проверьте доступность PV: При использовании статического предоставления убедитесь, что PV существует и соответствует критериям PVC.
  • Проверьте режимы доступа: Убедитесь, что режимы доступа (например, ReadWriteOnce, ReadWriteMany) совместимы.

Также проверьте, запланирован ли pod в зоне, где том может быть подключен. В облачных кластерах диск, созданный в одной зоне доступности, может не подключиться к узлу в другой. В событии обычно упоминается сродство тома к узлу или ошибка подключения. В этом случае исправлением могут быть ограничения планирования, другой StorageClass или пересоздание тома в правильной зоне.

4. Ограничения (Taints), терпимости (Tolerations) и селекторы узлов

Pod может оставаться в состоянии Pending, даже если в кластере много CPU и памяти. Планировщик также должен соблюдать правила размещения.

Распространенные примеры:

  • Pod имеет nodeSelector, который не соответствует ни одному узлу.
  • Pod требует сродства к узлу, которое слишком строгое.
  • Единственные подходящие узлы имеют ограничения (taints), и у pod нет соответствующей терпимости (toleration).
  • В пространстве имен есть квота, которая блокирует запрошенные ресурсы.

Сначала проверьте события планирования:

kubectl describe pod <pod-name> -n <namespace>

Затем сравните правила размещения pod с метками узлов:

kubectl get pod <pod-name> -n <namespace> -o yaml
kubectl get nodes --show-labels
kubectl describe node <node-name>

Если в событии сказано, что ограничение не было терпимо, либо запланируйте pod в другом месте, либо добавьте терпимость, только если эта рабочая нагрузка действительно должна быть на этих узлах. Не терпите слепо каждое ограничение. Ограничения часто защищают специальные узлы, узлы GPU, узлы инфраструктуры или узлы, находящиеся под давлением.

Устранение неисправностей Pod'ов в состоянии CrashLoopBackOff

Состояние CrashLoopBackOff указывает на проблему на уровне приложения. Контейнер успешно запустился, но затем завершился с ошибкой, что побудило Kubernetes многократно перезапускать его.

1. Ошибки приложения

Наиболее распространенной причиной является то, что само приложение не может запуститься или сталкивается с фатальной ошибкой вскоре после запуска.

Распространенные причины:

  • Отсутствующие зависимости/конфигурация: Приложение не может найти критические файлы конфигурации, переменные окружения или внешние службы, от которых оно зависит.
  • Неправильная команда/аргументы: command или args, указанные в спецификации контейнера, неверны или приводят к немедленному завершению.
  • Ошибки логики приложения: Баги в коде приложения, которые вызывают сбой при запуске.

Шаги диагностики:

  1. Просмотрите логи Pod: Это самый важный шаг. Логи часто показывают точное сообщение об ошибке, вызвавшей сбой приложения.

    kubectl logs <pod-name> -n <namespace>
    

    Если Pod многократно падает, логи могут показывать вывод последней неудачной попытки. Чтобы увидеть логи из предыдущего экземпляра падающего контейнера, используйте флаг -p (previous):

    kubectl logs <pod-name> -p -n <namespace>
    
  2. Опишите Pod: Ищите Restart Count в разделе Containers, который показывает, сколько раз контейнер падал. Также проверьте Last State на наличие Exit Code.

    kubectl describe pod <pod-name> -n <namespace>
    

    Код выхода 0 обычно означает корректное завершение, но любой ненулевой код выхода указывает на ошибку. Распространенные ненулевые коды выхода включают 1 (общая ошибка), 137 (SIGKILL, часто OOMKilled), 139 (SIGSEGV, ошибка сегментации).

Решение:

  • Просмотрите логи приложения: Основываясь на логах, отладьте код вашего приложения или конфигурацию. Убедитесь, что все необходимые переменные окружения, ConfigMap и Secrets правильно смонтированы/внедрены.
  • Протестируйте локально: Попробуйте запустить образ контейнера локально с теми же переменными окружения и командами, чтобы воспроизвести и отладить проблему.

Если в pod несколько контейнеров, всегда указывайте имя контейнера:

kubectl logs <pod-name> -c <container-name> -n <namespace>
kubectl logs <pod-name> -c <container-name> -p -n <namespace>

Без -c вы можете читать логи sidecar-контейнера, в то время как основное приложение — то, которое падает.

2. Сбои проверок Liveness и Readiness

Kubernetes использует проверки Liveness и Readiness для определения работоспособности и доступности вашего приложения. Если проверка liveness постоянно терпит неудачу, Kubernetes перезапустит контейнер, что приведет к CrashLoopBackOff.

Шаги диагностики:

  1. Опишите Pod: Проверьте определения проверок Liveness и Readiness и их Last State в разделе Containers.

    kubectl describe pod <pod-name> -n <namespace>
    

    Ищите сообщения, указывающие на сбои проверок, например "Liveness probe failed: HTTP probe failed with statuscode: 500".

  2. Просмотрите логи приложения: Иногда логи приложения предоставляют контекст, почему конечная точка проверки не работает.

Решение:

  • Отрегулируйте конфигурацию проверки: Исправьте path, port, command, initialDelaySeconds, periodSeconds или failureThreshold проверки.
  • Убедитесь в работоспособности конечной точки проверки: Проверьте, что конечная точка приложения, на которую нацелена проверка, действительно работоспособна и отвечает должным образом. Возможно, приложение запускается слишком долго, что требует большего значения initialDelaySeconds.

Для медленно запускающихся приложений рассмотрите startupProbe. Он дает приложению больше времени для инициализации, прежде чем проверка liveness начнет его оценивать. Это чище, чем устанавливать огромное initialDelaySeconds для liveness при каждом перезапуске.

3. Превышение лимитов ресурсов

Если контейнер постоянно пытается использовать больше памяти, чем его memory.limit, или его CPU throttled из-за превышения cpu.limit, ядро может завершить процесс, часто с событием OOMKilled (Out Of Memory Killed).

Шаги диагностики:

  1. Опишите Pod: Ищите OOMKilled в разделе Last State или Events. Код выхода 137 часто указывает на событие OOMKilled.

    kubectl describe pod <pod-name> -n <namespace>
    
  2. Проверьте kubectl top: Если установлен metrics-server, используйте kubectl top pod, чтобы увидеть фактическое использование ресурсов вашими Pod'ами.

    kubectl top pod <pod-name> -n <namespace>
    

Решение:

  • Увеличьте лимиты ресурсов: Если вашему приложению действительно нужно больше ресурсов, увеличьте limits для memory и/или cpu в манифесте вашего Pod. Это может потребовать большей емкости на ваших узлах.
    resources:
      requests:
        memory: "256Mi"
        cpu: "500m"
      limits:
        memory: "512Mi" # Увеличьте это
        cpu: "1000m"   # Увеличьте это
    
  • Оптимизируйте приложение: Профилируйте ваше приложение, чтобы выявить и уменьшить потребление ресурсов.

4. Проблемы с разрешениями

Контейнеры могут падать, если им не хватает необходимых разрешений для доступа к файлам, каталогам или сетевым ресурсам, которые им требуются.

Шаги диагностики:

  1. Просмотрите логи: Логи приложения могут показывать ошибки отказа в доступе (EACCES).
  2. Опишите Pod: Проверьте используемый ServiceAccount и любые смонтированные настройки securityContext.

Решение:

  • Отрегулируйте securityContext: Установите runAsUser, fsGroup или allowPrivilegeEscalation по мере необходимости.
  • Разрешения ServiceAccount: Убедитесь, что ServiceAccount, связанный с Pod, имеет необходимые Roles и ClusterRoles, привязанные через RoleBindings и ClusterRoleBindings.
  • Разрешения на тома: Убедитесь, что смонтированные тома (например, emptyDir, hostPath, ConfigMap, Secret) имеют правильные разрешения для пользователя контейнера.

Быстрое дерево решений

Когда кто-то говорит "pod сломан", выполните их по порядку:

kubectl get pod <pod-name> -n <namespace> -o wide
kubectl describe pod <pod-name> -n <namespace>
kubectl logs <pod-name> -n <namespace> --all-containers=true --tail=100
kubectl logs <pod-name> -n <namespace> --all-containers=true -p --tail=100

Затем ветвление:

  • Если нет узла в kubectl get pod -o wide, сосредоточьтесь на планировании: запросы, ограничения, сродство, квота и доступность узлов.
  • Если узел есть, но в событии упоминается извлечение образа, сосредоточьтесь на имени образа, теге, аутентификации реестра и сетевом доступе от узла к реестру.
  • Если в событии упоминается монтирование или подключение, сосредоточьтесь на PVC, PV, StorageClass, режимах доступа и размещении по зонам.
  • Если pod запускается, а затем перезапускается, сосредоточьтесь на логах, коде выхода, проверках, команде/аргументах, конфигурации, секретах и лимитах памяти.

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

Чтение кодов выхода без лишней реакции

Коды выхода — это подсказки, а не полные объяснения.

  • 1 обычно означает, что приложение вернуло общую ошибку. Логи важнее, чем число.
  • 2 может указывать на ошибки использования командной строки во многих программах.
  • 126 часто означает, что команда существует, но не может быть выполнена.
  • 127 часто означает, что команда не найдена.
  • 137 обычно появляется, когда процесс получает SIGKILL; в Kubernetes это часто, но не всегда, связано с OOMKilled.
  • 143 означает, что процесс получил SIGTERM, что может произойти при нормальном завершении.

Если код выхода 137, проверьте Last State pod и события, прежде чем предполагать утечку памяти. Дренаж узла, вытеснение или ручное завершение также могут завершить контейнер.

Общие шаги диагностики и инструменты

Вот краткий контрольный список команд для запуска при возникновении проблем с Pod'ами:

  • Получите быстрый обзор: Проверьте статус ваших Pod'ов.
    kubectl get pods -n <namespace>
    kubectl get pods -n <namespace> -o wide
    
  • Детальная информация о Pod: Самая важная команда для понимания событий, состояний и условий Pod.
    kubectl describe pod <pod-name> -n <namespace>
    
  • Логи контейнера: Посмотрите, что сообщает ваше приложение.
    kubectl logs <pod-name> -n <namespace>
    kubectl logs <pod-name> -p -n <namespace> # Предыдущий экземпляр
    kubectl logs <pod-name> -f -n <namespace> # Следовать за логами
    
  • События кластера: Иногда проблема не в конкретном Pod, а в событии всего кластера (например, давление на узел).
    kubectl get events -n <namespace>
    
  • Интерактивная отладка: Если ваш контейнер запускается, но быстро падает, вы можете попытаться выполнить exec в него на короткое время или в отдельный отладочный контейнер, если он настроен.
    kubectl exec -it <pod-name> -n <namespace> -- bash
    
    (Примечание: Это работает, только если контейнер остается живым достаточно долго, чтобы подключиться.)

Лучшие практики для предотвращения проблем с Pod'ами

Профилактика всегда лучше лечения. Следование этим лучшим практикам может значительно сократить инциденты Pending и CrashLoopBackOff:

  • Устанавливайте реалистичные запросы и лимиты ресурсов: Начните с разумных requests и limits, затем настройте их на основе профилирования и мониторинга приложения.
  • Используйте конкретные теги образов: Избегайте тегов latest в production. Используйте неизменяемые теги (например, v1.2.3, commit-sha) для воспроизводимости.
  • Реализуйте надежные проверки: Настройте проверки liveness и readiness, которые точно отражают работоспособность вашего приложения. Учитывайте время запуска с помощью initialDelaySeconds.
  • Централизованное логирование и мониторинг: Используйте такие инструменты, как Prometheus, Grafana, ELK stack или облачные сервисы логирования для сбора и анализа логов и метрик Pod'ов.
  • Контроль версий для манифестов: Храните манифесты Kubernetes в системе контроля версий (например, Git), чтобы отслеживать изменения и облегчать откаты.
  • Тщательное тестирование: Тестируйте образы контейнеров и развертывания Kubernetes в средах разработки и staging перед развертыванием в production.
  • Корректное завершение: Убедитесь, что ваши приложения обрабатывают сигналы SIGTERM для корректного завершения, позволяя им освобождать ресурсы перед завершением.

Что обычно исправляет быстрее всего

Для Pending kubectl describe pod обычно является самым быстрым путем, потому что события планировщика и kubelet объясняют, что Kubernetes не смог сделать. Для CrashLoopBackOff предыдущие логи обычно являются самым быстрым путем, потому что текущий контейнер может быть слишком новым, чтобы показать сбой, вызвавший цикл.

После того как вы исправили непосредственную проблему, найдите шаг профилактики: правильно подобранные запросы, лучшие теги образов, проверка запуска, отсутствующая проверка секрета в CI или более понятная инструкция. Лучший инцидент с pod — это тот, который в следующий раз будет легче распознать.