livenessProbe рестартит контейнер
Как отличить упавшее приложение от криво настроенной probe и почему логи нужно читать с флагом --previous.
Deployment раскатан, образ правильный, приложение внутри контейнера
работает. При этом счётчик RESTARTS растёт, а статус скачет между
Running и CrashLoopBackOff.
NAME READY STATUS RESTARTS AGE
app-7c9d8f4b6-x2k4j 0/1 CrashLoopBackOff 4 2m
Классический случай: контейнер убивает не сбой в приложении, а livenessProbe - проверка, по которой kubelet решает, жив ли контейнер. Ниже разбор, как это выглядит в диагностике.
Что показывает kubectl
Смотреть стоит в трёх местах и именно в таком порядке.
1. kubectl get pod
Первое, что замечают глаза: RESTARTS больше нуля и растёт.
READY 0/1 значит, что контейнер запущен, но не прошёл readinessProbe
(проверку готовности принимать трафик) или вообще упал. Четыре рестарта
за две минуты это уже паттерн, а не случайная ошибка.
2. kubectl describe pod
Эта команда показывает три вещи, которых нет в get:
- Events в конце: последние 30 минут лога kubelet про этот Pod.
- State и Last State: что с контейнером сейчас и почему он умер в прошлый раз.
- Containers: spec того, что реально применилось (probe, ports, env).
Типичный вывод при битой liveness:
State: Running
Last State: Terminated
Reason: Error
Exit Code: 137
Events:
Warning Unhealthy liveness probe failed: Get "http://10.42.0.7:8081/": dial tcp 10.42.0.7:8081: connect: connection refused
Normal Killing Container app failed liveness probe, will be restarted
Exit code 137 это 128 + SIGKILL(9). Контейнер прибил kubelet за то,
что probe три раза подряд (по умолчанию failureThreshold: 3) не
получила ответ.
Ключевое слово в Events - connection refused по адресу с портом
8081. Приложение на этом порту не слушает.
3. kubectl logs --previous
kubectl logs pod показывает текущий запуск контейнера. Если он только
что рестартанул, в выводе будут свежие строки, а не те, где случилась
проблема. Флаг --previous (или -p) даёт логи прошлого запуска.
kubectl logs app-7c9d8f4b6-x2k4j --previous
Вывод вроде listening on 0.0.0.0:8080 рядом с probe, которая бьёт в
:8081, закрывает вопрос о причине.
Состояния, которые путают
| Статус | Что значит |
|---|---|
Pending | Pod не запланирован или образ тянется. Смотри Events. |
Running | Контейнер запущен. Готовность к трафику это отдельный вопрос. |
CrashLoopBackOff | Контейнер падает, kubelet увеличивает паузу между рестартами (10s, 20s, 40s, 80s, до 5 минут). |
Error | Контейнер вышел с ненулевым exit code при restartPolicy: Never. |
Completed | Вышел с exit 0. Для Job это норма, для Deployment плохо. |
ImagePullBackOff | Образ не качается: опечатка в имени, нет авторизации, нет интернета. |
CreateContainerConfigError | Pod ссылается на ConfigMap или Secret, которого нет. |
OOMKilled | Превысил resources.limits.memory. Видно в Last State. |
Probe против реального состояния приложения
livenessProbe отвечает на вопрос «жив ли контейнер». Если probe
говорит «нет» три раза подряд, kubelet рестартит контейнер. Дальше
возможны два сценария.
Первый: приложение действительно упало или зависло. Probe сработала правильно, чинить надо приложение.
Второй: probe настроена криво. Приложение живо, но проверка бьёт не
туда - неправильный порт, неправильный путь, слишком маленький
initialDelaySeconds для медленного старта. Этот вариант встречается
чаще. Особенно когда контейнер слушает на 8080 (типично для
unprivileged-образов nginx), а в probe остался порт 80 из шаблона.
Медленный старт под liveness выглядит как бесконечный CrashLoopBackOff:
контейнер не успевает подняться за initialDelaySeconds, и его убивают
снова. Лечит startupProbe, а не отключение проверки.
Как читать Events
kubectl describe pod в конце даёт таблицу:
Events:
Type Reason Age From Message
Warning Unhealthy 10s kubelet Liveness probe failed: ...
Normal Killing 10s kubelet Container app failed liveness probe
Normal Pulled 11s kubelet Container image "..." already present
Normal Created 11s kubelet Created container app
Normal Started 11s kubelet Started container app
Читать надо снизу вверх по времени (Age): сначала Started, потом
Killing, потом снова Started. Цикл повторяется. Reason Unhealthy со
словами Liveness probe failed указывает прямо на probe.
Если бы контейнер убивала реальная смерть процесса, картина была бы
другой: OOMKilled в Last State и события Killing без упоминания
probe.
Как чинят
Порт в probe сверяют с портом, на котором реально слушает приложение:
kubectl get deploy app -o jsonpath='{.spec.template.spec.containers[0].livenessProbe}'
Дальше правят шаблон Deployment, а не Pod:
kubectl patch deploy app --type=json \
-p='[{"op":"replace","path":"/spec/template/spec/containers/0/livenessProbe/httpGet/port","value":8080}]'
или через kubectl edit deploy app. Deployment создаёт новый
ReplicaSet, старые Pod уезжают, у новых счётчик RESTARTS остаётся на
нуле. Это и есть признак того, что починка сработала.
Как не допустить
Порт в probe и containerPort берут из одного места. Когда в манифесте
используется именованный порт, probe ссылается на имя, а не на число:
ports:
- name: http
containerPort: 8080
livenessProbe:
httpGet:
path: /healthz
port: http
Тогда смена порта в одном месте не оставляет probe с устаревшим значением.
Второе правило: liveness и readiness проверяют разное. Readiness убирает Pod из балансировки, пока он не готов, и не убивает контейнер. Liveness убивает.
Домашка
Теорию закрепляет задание на своём кластере: 1 шаг с проверкой. Кластер поднимается по треку Kubernetes локально, задания лежат в репозитории k8s-practice.
./run.sh start debug-probe # развернуть стартовое состояние
./run.sh verify debug-probe # проверить текущий шаг
Коротко
- Растущий
RESTARTSпри живом приложении почти всегда указывает на livenessProbe. - Exit code 137 значит SIGKILL от kubelet после
failureThresholdнеудачных проверок. - Текст ошибки лежит в Events:
Liveness probe failedплюс адрес и порт. kubectl logs --previousпоказывает логи упавшего запуска, обычныйlogsуже нет.- Правят шаблон Deployment, после чего новые Pod идут с нулём рестартов.
Прошли материал до конца? Отметьте урок пройденным.
Прогресс сохранён в этом браузере
Почистишь историю или откроешь с телефона, и отметки пропадут. Оставь почту, они переедут в аккаунт. Пароль придумывать не нужно.
Готово. теперь в аккаунте: прогресс виден с любого устройства. Мой прогресс