Kubernetes

Теория: Отладка и мониторинг

Представьте, три часа ночи. Алерт: «Сервис недоступен». Вы открываете терминал и понимаете, что не знаете, с чего начать. Pod вроде Running, но что-то не работает.

Как думать при диагностике

Хаотичная отладка — потеря времени. «Попробую посмотреть логи... нет, сначала describe... или events?» Так можно крутиться часами.

Вместо этого двигайтесь от общего к частному. Сначала поймите, что происходит в кластере в целом. Потом найдите проблемный компонент. Потом разберитесь, почему он сломался. И только потом лезьте внутрь.

На практике это выглядит так: вы начинаете с событий — они показывают, что произошло недавно. Затем смотрите на Pod'ы — кто из них в проблемном состоянии. Потом describe — почему этот Pod в таком состоянии. Логи расскажут, что думает само приложение. И если всё ещё непонятно — заходите внутрь контейнера и исследуете.

Давайте пройдём этот путь на реальных примерах.

События: что произошло в кластере

События — это «чёрный ящик» Kubernetes. Сюда записывается всё важное: почему Pod не запустился, почему был убит, почему не смог подключить volume. Начинайте диагностику отсюда.

kubectl get events --sort-by=.metadata.creationTimestamp

Флаг --sort-by сортирует вывод по указанному полю. В данном случае — по времени создания события. Без него события выводятся в произвольном порядке, и вы не поймёте, что произошло раньше, а что позже.

Вывод покажет хронологию: что происходило в кластере за последний час (события живут около часа, потом удаляются). Ищите строки с Warning — это обычно проблемы.

Если нужны события конкретного Pod:

kubectl get events --field-selector involvedObject.name=my-pod

Флаг --field-selector фильтрует по полям объекта. Здесь мы говорим: «покажи только события, где поле involvedObject.name равно my-pod». Это как WHERE в SQL.

Что означают типичные события

  • FailedScheduling — планировщик не смог разместить Pod на узле. Обычно это значит, что на всех узлах не хватает ресурсов (CPU или памяти), или Pod требует узел с определёнными характеристиками (nodeSelector, affinity), а таких узлов нет. Смотрите describe pod — там будет подробнее, чего именно не хватило.
  • Failed to pull image — Kubernetes не смог скачать образ контейнера. Либо вы опечатались в имени образа или теге, либо registry недоступен, либо нужна авторизация (imagePullSecrets). Проверьте имя образа в describe pod и попробуйте сделать docker pull вручную.
  • OOMKilled — контейнер убит из-за превышения лимита памяти. Приложение попыталось выделить больше памяти, чем разрешено в limits. Либо увеличивайте лимит, либо ищите утечку памяти в приложении.
  • Liveness probe failed — проверка живучести не прошла. Kubernetes считает, что приложение зависло, и перезапускает контейнер. Проверьте, что endpoint для probe существует и отвечает вовремя. Возможно, нужно увеличить timeout или initialDelaySeconds.
  • FailedAttachVolume / FailedMount — не удалось подключить volume. Причин много: PVC не привязан к PV, storage недоступен, volume уже примонтирован к другому Pod (для ReadWriteOnce). Смотрите describe pvc и события PV.

Состояние Pod: что означают статусы

После событий смотрим на Pod'ы:

kubectl get pods

Вы увидите список Pod и колонку STATUS. Но статус — это только верхушка айсберга. Давайте разберём, что каждый из них означает и что делать.

  • Pending — Pod создан, но не запущен. Он ждёт чего-то: свободных ресурсов на узле, подходящего узла по nodeSelector, привязки PVC к PV. Это нормальное состояние в первые секунды, но если Pod висит в Pending минуту и больше — проблема. Смотрите события и describe pod.
  • ContainerCreating — Pod назначен на узел, идёт подготовка: скачивается образ, монтируются volumes, выполняются init-контейнеры. Тоже нормально в первые секунды, но если застряло — проверяйте события (обычно проблема с образом или volume). Running — контейнеры запущены. Но внимание: это не значит, что приложение работает правильно! Pod может быть - Running, но не Ready — это значит, что readiness probe не проходит. Смотрите колонку READY: если там 0/1 вместо 1/1, приложение не готово принимать трафик.
  • CrashLoopBackOff — контейнер падает сразу после запуска. Kubernetes пытается перезапустить, но контейнер снова падает. После нескольких попыток Kubernetes начинает ждать всё дольше между перезапусками (backoff). Это серьёзная проблема — приложение не может стартовать. Смотрите логи предыдущего контейнера:
kubectl logs my-pod --previous

Флаг --previous показывает логи предыдущего экземпляра контейнера — того, который упал. Без этого флага вы увидите логи текущего контейнера, который, возможно, ещё не успел ничего написать перед падением.

ImagePullBackOff — не удаётся скачать образ. Kubernetes пробует снова и снова с увеличивающимися интервалами. Проверьте имя образа и тег в describe pod, убедитесь, что registry доступен.

OOMKilled — контейнер убит из-за превышения лимита памяти. Вы увидите это в describe pod в секции Last State. Увеличьте лимит или разберитесь, почему приложение ест столько памяти.

Evicted — Pod вытеснен с узла. Это происходит, когда на узле заканчиваются ресурсы (диск, память) и kubelet начинает «выселять» Pod'ы, чтобы спасти узел. Evicted Pod'ы нужно удалять вручную, они не восстанавливаются автоматически.

Для более подробной информации добавьте флаг -o wide:

kubectl get pods -o wide

Это покажет дополнительные колонки: на каком узле запущен Pod, его IP-адрес, сколько раз перезапускался. Если RESTARTS больше нуля — контейнер падал и перезапускался, это повод разобраться.

Describe: глубокий анализ

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

kubectl describe pod my-pod

Вывод большой, но смотреть нужно на конкретные секции.

Conditions — состояние Pod как набор условий:

Conditions:
  Type              Status
  Initialized       True
  Ready             False
  ContainersReady   False
  PodScheduled      True

Если PodScheduled = False, Pod не смог разместиться на узле (см. события). Если Ready = False при Running статусе — readiness probe не проходит. Conditions — это быстрый способ понять, на каком этапе проблема.

Container State — состояние каждого контейнера:

State:          Waiting
  Reason:       CrashLoopBackOff
Last State:     Terminated
  Reason:       Error
  Exit Code:    1

Restart Count:  5

Exit Code — код выхода приложения. 0 означает успешное завершение, любое другое значение — ошибку. Exit Code 137 обычно означает OOMKilled (128 + 9, где 9 — сигнал SIGKILL). Exit Code 1 — общая ошибка приложения, смотрите логи.

Events — события, связанные с этим Pod. Та же информация, что в kubectl get events, но отфильтрованная по конкретному Pod. Смотрите сюда, чтобы понять хронологию проблемы.

Describe работает и для других объектов:

kubectl describe service my-servicekubectl describe node my-node
kubectl describe pvc my-pvc

Для Service особенно важна секция Endpoints — это IP-адреса Pod'ов, на которые Service направляет трафик. Если Endpoints пустой, Service не нашёл Pod'ы (проблема с selector).

Логи

События и describe показывают, что происходит с точки зрения Kubernetes. Логи показывают, что происходит с точки зрения приложения.

kubectl logs my-pod

Если в Pod несколько контейнеров, укажите какой:

kubectl logs my-pod -c my-container

Для отслеживания логов в реальном времени (как tail -f):

kubectl logs -f my-pod

Флаг -f (follow) держит соединение открытым и показывает новые строки по мере их появления. Нажмите Ctrl+C, чтобы выйти.

Часто нужно посмотреть логи не с начала, а за определённый период:

kubectl logs my-pod --since=1h

Флаг --since показывает логи за указанный период: 1h — час, 30m — 30 минут, 2h30m — два с половиной часа. Это удобнее, чем пролистывать тысячи строк.

Можно ограничить количество строк:

kubectl logs my-pod --tail=100

Флаг --tail показывает только последние N строк. Полезно, когда логов много и вам нужен только «хвост».

Для поиска ошибок комбинируйте с grep:

kubectl logs my-pod | grep -i error
kubectl logs my-pod | grep -i -E "(error|exception|failed)"

Логи нескольких Pod

Если у вас Deployment с несколькими репликами, можно посмотреть логи всех Pod сразу:

kubectl logs -l app=nginx --prefix=true

Флаг -l (selector) выбирает Pod'ы по метке. Флаг --prefix добавляет имя Pod перед каждой строкой, чтобы вы понимали, откуда она.

Учтите, что это агрегирование может быть шумным: если у вас 10 реплик, логи смешаются. Для серьёзного анализа логов используйте централизованные системы (об этом в конце урока).

Логи упавшего контейнера

Если контейнер в CrashLoopBackOff, обычный kubectl logs может показать пустоту — контейнер падает быстрее, чем пишет логи. Используйте:

kubectl logs my-pod --previous

Это покажет логи предыдущего экземпляра контейнера — того, который уже упал. Часто именно там находится сообщение об ошибке.

Исследование изнутри: exec и debug

Иногда нужно зайти внутрь контейнера: проверить файлы, переменные окружения, сетевую связность.

kubectl exec -it my-pod -- /bin/bash

Флаг -i (interactive) держит stdin открытым. Флаг -t (tty) создаёт терминал. Вместе они дают интерактивную сессию. После -- идёт команда, которую нужно выполнить в контейнере.

Если в контейнере нет bash (alpine-образы, минималистичные образы), попробуйте sh:

kubectl exec -it my-pod -- /bin/sh

Можно выполнить одну команду без интерактивной сессии:

kubectl exec my-pod -- envkubectl exec my-pod -- cat /app/config.yaml
kubectl exec my-pod -- netstat -tlnp

Что проверять внутри контейнера

Переменные окружения — здесь могут быть неправильные значения из ConfigMap или Secret:

env | sort

Сетевые соединения — слушает ли приложение на нужном порту:

netstat -tlnp
# или
ss -tlnp

Важно: приложение должно слушать на 0.0.0.0:порт, а не на 127.0.0.1:порт. Если слушает на localhost, другие Pod'ы не смогут подключиться.

DNS — работает ли резолвинг имён:

nslookup other-service
cat /etc/resolv.conf

Связность с другими сервисами:

curl -v http://other-service:8080/health

Отладка контейнеров без shell

Некоторые образы (distroless, scratch) не содержат ни bash, ни sh, ни других утилит. Kubernetes позволяет подключить отладочный контейнер:

kubectl debug -it my-pod --image=busybox --target=my-container

Эта команда создаёт временный (ephemeral) контейнер с образом busybox в том же Pod и подключает его к namespace процессов указанного контейнера. Вы сможете видеть процессы основного контейнера и исследовать файловую систему.

Если команда не работает, проверьте версию Kubernetes и настройки кластера — ephemeral containers должны быть включены (в современных версиях включены по умолчанию).

Для сетевой отладки удобен образ nicolaka/netshoot — в нём есть curl, dig, tcpdump, netstat и другие инструменты:

kubectl run debug --image=nicolaka/netshoot --rm -it --restart=Never -- sh

Флаг --rm удалит Pod после выхода. Флаг --restart=Never создаёт просто Pod, а не Deployment.

Сетевые проблемы

Сетевые проблемы — самые коварные. «Не работает» может означать что угодно: DNS не резолвит, Service не находит Pod'ы, NetworkPolicy блокирует трафик.

Проверка Service и Endpoints

Когда сервис недоступен, первым делом проверьте, есть ли у Service endpoints:

kubectl get endpoints my-service

Endpoints — это список IP-адресов Pod'ов, на которые Service направляет трафик. Если список пустой, Service не нашёл ни одного Pod'а.

Почему так может быть? Service ищет Pod'ы по selector. Если selector не совпадает с labels Pod'ов — endpoints будут пустыми:

kubectl get service my-service -o jsonpath='{.spec.selector}'
kubectl get pods --show-labels

Флаг -o jsonpath позволяет вытащить конкретное поле из объекта. Здесь мы получаем selector сервиса. Сравните его с labels Pod'ов — они должны совпадать.

Ещё одна причина пустых endpoints — Pod'ы не в состоянии Ready. Service направляет трафик только на Ready Pod'ы. Проверьте:

kubectl get pods -l app=my-app

Если в колонке READY стоит 0/1, Pod не готов, и Service его игнорирует.

Проверка DNS

Если endpoints есть, но сервис всё равно недоступен, проверьте DNS. Запустите отладочный Pod:

kubectl run debug --image=nicolaka/netshoot --rm -it --restart=Never -- sh

Внутри проверьте резолвинг:

nslookup my-service
nslookup my-service.default.svc.cluster.local

Если DNS не работает, проверьте CoreDNS:

kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns

NetworkPolicy

Если DNS работает, endpoints есть, но соединение не устанавливается — возможно, трафик блокирует NetworkPolicy.

По умолчанию, без NetworkPolicy, весь трафик в Kubernetes разрешён. Но если в namespace есть NetworkPolicy типа default-deny, трафик блокируется, пока вы явно не разрешите его.

Проверьте, есть ли NetworkPolicy:

kubectl get networkpolicy
kubectl describe networkpolicy

Если политики есть, убедитесь, что они разрешают нужный трафик (по портам, по labels источника).

Проблемы с ресурсами

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

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

Для просмотра текущего потребления нужен metrics-server:

kubectl top nodes
kubectl top pods

Если команда выдаёт ошибку «Metrics API not available», metrics-server не установлен. Это компонент, который собирает метрики с kubelet. В managed-кластерах (EKS, GKE, AKS) он обычно уже есть, в самостоятельных установках нужно ставить отдельно.

Команда kubectl top pods --sort-by=memory отсортирует Pod'ы по потреблению памяти — удобно для поиска «прожорливых».

Почему Pod не размещается

Если Pod в Pending из-за ресурсов, посмотрите, сколько ресурсов доступно на узлах:

kubectl describe nodes | grep -A 5 "Allocated resources"

Вы увидите что-то вроде:

Allocated resources:
  Resource           Requests     Limits
  cpu                1800m (90%)  3000m (150%)

  memory             2Gi (70%)    4Gi (140%)

Если Requests близки к 100%, новые Pod'ы не смогут разместиться — все ресурсы уже «забронированы» существующими Pod'ами.

OOMKilled на практике

Создайте Pod, который превысит лимит памяти:

apiVersion: v1
kind: Pod
metadata:
  name: memory-hog
spec:
  containers:
  - name: stress
    image: polinux/stress
    resources:
      limits:
        memory: "100Mi"
    command: ["stress"]

    args: ["--vm", "1", "--vm-bytes", "150M", "--timeout", "60s"]

Примените и подождите 10–20 секунд:

kubectl apply -f memory-hog.yaml
kubectl get pod memory-hog

kubectl describe pod memory-hog | grep -A 5 "Last State"

Вы увидите OOMKilled — контейнер запросил 150M памяти при лимите 100Mi и был убит.

Полная диагностика

Давайте соберём всё вместе. Создайте «сломанный» deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: broken-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: broken
  template:
    metadata:
      labels:
        app: broken
    spec:
      containers:
      - name: app
        image: nginx:1.21
        ports:
        - containerPort: 80
        readinessProbe:
          httpGet:
            path: /ready
            port: 80
---
apiVersion: v1
kind: Service
metadata:
  name: broken-app
spec:
  selector:
    app: wrong-label
  ports:
  - port: 80

Здесь две проблемы: readiness probe обращается к несуществующему endpoint /ready, и selector в Service не совпадает с labels Pod'ов.

Примените и начните диагностику:

kubectl apply -f broken-app.yaml

Шаг 1 — события:

kubectl get events --sort-by=.metadata.creationTimestamp | tail -10

Вы увидите Readiness probe failed.

Шаг 2 — состояние Pod'ов:

kubectl get pods -l app=broken

Pod'ы Running, но READY: 0/1.

Шаг 3 — describe:

kubectl describe pod -l app=broken | grep -A 10 "Conditions:"

Ready: False. В Events — Readiness probe failed with statuscode: 404.

Шаг 4 — проверка Service:

kubectl get endpoints broken-app

Пусто. Даже если бы probe проходил, Service не нашёл бы Pod'ы из-за неправильного selector.

Теперь вы знаете обе проблемы и можете исправить.

Рекомендуемые программы

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845