Исследование и отладка приложений Kubernetes

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

Введение

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

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

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

Создание контролируемой неисправности

Запуск среды: В этой лабораторной работе для вас запускается полноценный кластер Kubernetes. Настройка плоскости управления, узла и сетевых компонентов обычно занимает 2–3 минуты. Дождитесь полной загрузки среды, прежде чем начинать работу.

Реальная отладка начинается с наблюдаемого симптома. На этом шаге вы развернёте исправную и намеренно сломанную рабочие нагрузки, чтобы сравнить их в одинаковых условиях кластера.

Перейдите в подготовленное рабочее пространство и выведите список файлов. Команда cd меняет текущий каталог, а ls выводит содержащиеся в нём имена. Команды находятся на отдельных строках и выполняются по порядку:

cd /home/labex/project/debug-lab
ls

Вы должны увидеть healthy-web.yaml и broken-web.yaml. Оба файла описывают Deployment с одной репликой, но один из них содержит незаметную ошибку конфигурации, которую вы диагностируете позже.

Примените оба манифеста. kubectl apply передаёт требуемое состояние API-серверу, а каждый параметр -f указывает входной файл. Одна команда может принимать несколько параметров -f:

kubectl apply -f healthy-web.yaml -f broken-web.yaml

Сначала дождитесь развёртывания заведомо исправного Deployment:

kubectl rollout status deployment/healthy-web --timeout=60s

Сообщение deployment "healthy-web" successfully rolled out подтверждает полезную исходную точку: кластер способен планировать Pod и запускать кэшированный образ NGINX.

Теперь дайте второму Deployment немного времени на развёртывание:

kubectl rollout status deployment/broken-web --timeout=15s || true

Превышение времени ожидания здесь ожидаемо. || true сообщает оболочке продолжить выполнение, поскольку этот сбой является частью задания и служит диагностическим фактом, а не причиной остановки лабораторной работы.

Сравните сводную информацию о Deployment:

kubectl get deployments

У healthy-web должно быть состояние готовности 1/1, тогда как у broken-web0/1. Теперь вы установили, что проблема относится к конкретной рабочей нагрузке, а не связана с отказом всего кластера.

Сужение области поиска с помощью сводной информации о ресурсах

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

Выведите связанные типы объектов одной командой. Запятые позволяют одному запросу kubectl get обратиться к нескольким типам ресурсов, а -o wide добавляет полезные столбцы, например сведения об узле и IP-адресе:

kubectl get deployments,replicasets,pods -o wide

Читайте вывод сверху вниз:

  • Deployment сообщает требуемое и доступное количество реплик.
  • ReplicaSet передаёт это требуемое количество дальше, к Pod.
  • Pod показывает готовность контейнера и краткую причину текущего состояния.

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

kubectl get pods -l app=broken-web -o wide

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

Запросите только поля, важные на этом этапе. -o custom-columns='...' формирует таблицу из явно указанных полей объекта. Каждая запись содержит заголовок, например NAME, а затем путь к полю JSON, из которого берётся значение. Завершающий обратный слеш объединяет две отображаемые строки оболочки в одну команду:

kubectl get pods -l app=broken-web \
  -o custom-columns='NAME:.metadata.name,READY:.status.containerStatuses[0].ready,WAITING_REASON:.status.containerStatuses[0].state.waiting.reason,NODE:.spec.nodeName'

Причиной ожидания сначала может быть ErrImagePull, а позже — ImagePullBackOff. Оба состояния означают, что контейнер не запустился: Kubernetes не смог получить его образ. Это описание гораздо точнее, чем простая формулировка «Pod не работает».

Исследование Pod с помощью describe

На этом шаге вы воспользуетесь describe, чтобы выяснить, почему контейнер ожидает запуска. Сводная информация показала, что не так; теперь нужно собрать объяснение.

Сохраните сгенерированное имя Pod в переменную оболочки. $(...) — это подстановка команды: оболочка выполняет внутреннюю команду kubectl и присваивает её вывод переменной BROKEN_POD. JSONPath выбирает имя первого подходящего Pod, а echo выводит сохранённое значение:

BROKEN_POD=$(kubectl get pods -l app=broken-web -o jsonpath='{.items[0].metadata.name}')
echo "$BROKEN_POD"

Получите подробное описание этого Pod:

kubectl describe pod "$BROKEN_POD"

Команда describe объединяет полезные поля с последними событиями. Обратите внимание на три раздела:

  • Containers → Image показывает точно запрошенный образ.
  • State → Waiting → Reason описывает текущее состояние контейнера.
  • Events содержит попытки kubelet и сообщения об ошибках.

В данном сценарии сообщение события указывает, что тег 1.27-alpine-missing не найден. Кластер выполняет то, что указано в манифесте; ошибка находится в самом требуемом состоянии.

Проверьте образ напрямую с помощью JSONPath:

kubectl get pod "$BROKEN_POD" -o jsonpath='Image: {.spec.containers[0].image}{"\n"}'

JSONPath особенно полезен, когда большой YAML-файл или вывод describe содержит больше информации, чем требуется. Здесь он позволяет выделить поле, которое впоследствии нужно исправить.

Чтение событий как временной шкалы

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

Выведите последние события пространства имён в хронологическом порядке. --sort-by сортирует объекты по указанному полю метаданных; кавычки не дают пути к полю в формате JSON разделиться на несколько аргументов:

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

Последние строки обычно соответствуют самым новым событиям. Найдите записи, в столбце OBJECT которых указан неисправный Pod, а в REASON встречаются такие значения, как Pulling, Failed или BackOff.

Уменьшить объём лишней информации можно, отфильтровав события по сгенерированному имени Pod. --field-selector фильтрует поля объектов на стороне сервера, а не метки. Запятая означает, что должны совпасть оба условия, а обратные слеши позволяют записать одну команду в нескольких удобных для чтения строках:

BROKEN_POD=$(kubectl get pods -l app=broken-web -o jsonpath='{.items[0].metadata.name}')
kubectl get events \
  --field-selector involvedObject.kind=Pod,involvedObject.name="$BROKEN_POD" \
  --sort-by='.metadata.creationTimestamp'

Рассматривайте get, describe и events как взаимодополняющие представления:

  • get быстро находит неисправный объект.
  • describe объединяет конфигурацию, состояние и связанные события для одного объекта.
  • events показывает упорядоченную по времени картину, которая может выявить повторяющиеся попытки.

Повторяющиеся записи BackOff не означают, что Kubernetes отказался от Pod. Они показывают, что после неудач система увеличивает интервалы между повторными попытками загрузки образа.

Исправление требуемого состояния и проверка восстановления

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

Выведите строки с образами из обоих манифестов для сравнения. grep ищет текст, -n добавляет к каждому совпадению номер строки, а одна команда одновременно просматривает оба файла:

grep -n 'image:' healthy-web.yaml broken-web.yaml

В исправном манифесте используется nginx:1.27-alpine, а в неисправном добавлен несуществующий суффикс -missing.

Замените только этот суффикс. sed выполняет текстовую замену в формате s/old/new/, а -i изменяет указанный файл непосредственно, вместо того чтобы только выводить изменённый текст:

sed -i 's/nginx:1.27-alpine-missing/nginx:1.27-alpine/' broken-web.yaml

Проверьте исправленный файл локально:

kubectl apply --dry-run=client -f broken-web.yaml

Предварительно просмотрите различия между файлом и объектом в кластере:

kubectl diff -f broken-web.yaml || true

kubectl diff завершается с кодом 1, если обнаруживает различия, поэтому || true позволяет продолжить выполнение задания. В выводе различий строка, начинающаяся с -, содержит старый образ, а строка, начинающаяся с +, — исправленный.

Примените исправление и дождитесь восстановления:

kubectl apply -f broken-web.yaml
kubectl rollout status deployment/broken-web --timeout=60s

Убедитесь, что оба Deployment теперь исправны:

kubectl get deployments

Оба должны показывать готовность 1/1. Kubernetes создал новый ReplicaSet и Pod на основе исправленного шаблона Pod; вручную восстанавливать неисправный Pod не потребовалось.

Чтение журналов приложения

На этом шаге контейнер уже запускается, поэтому вы получите новый источник данных — журналы приложения. kubectl logs извлекает потоки стандартного вывода и стандартной ошибки контейнера.

Выберите новый исправный Pod, которым управляет broken-web. Здесь повторяются подстановка команды и использование JSONPath из предыдущих шагов. --field-selector=status.phase=Running добавляет серверное условие, гарантирующее, что выбранный Pod работает:

WEB_POD=$(kubectl get pods -l app=broken-web \
  --field-selector=status.phase=Running \
  -o jsonpath='{.items[0].metadata.name}')
echo "$WEB_POD"

У NGINX может ещё не быть записи в журнале доступа, поскольку никто не запрашивал страницу. Создайте такую запись изнутри Pod. В конструкции kubectl exec POD -- COMMAND параметр -- отделяет параметры kubectl от команды, выполняемой внутри контейнера. wget -qO- загружает страницу без лишнего вывода и направляет её в стандартный вывод; канал | передаёт результат команде head, которая показывает только начало:

kubectl exec "$WEB_POD" -- wget -qO- http://127.0.0.1 | head

HTML начинается с <!DOCTYPE html>, что подтверждает: NGINX ответил локально на порту 80.

Теперь прочитайте последние записи журнала. --tail=10 ограничивает вывод десятью последними строками, чтобы сообщения запуска не скрывали полезную запись о запросе:

kubectl logs "$WEB_POD" --tail=10

Найдите HTTP-запрос, содержащий GET / HTTP/1.1, и код ответа 200. Журналы особенно полезны, когда контейнер работает, но приложение ведёт себя неправильно. При ошибке загрузки образа они обычно бесполезны, поскольку контейнер ещё не запустился.

Исследование изнутри контейнера

На этом шаге вы исследуете восстановленное приложение изнутри его контейнера. kubectl exec выполняет команду в уже работающем контейнере и позволяет проверить файловую систему, процессы, окружение, представление DNS или локальное сетевое поведение.

Повторно получите имя работающего Pod:

WEB_POD=$(kubectl get pods -l app=broken-web \
  --field-selector=status.phase=Running \
  -o jsonpath='{.items[0].metadata.name}')

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

kubectl exec "$WEB_POD" -- hostname

Вывод совпадает с именем Pod, поскольку Kubernetes по умолчанию задаёт Pod имя хоста, совпадающее с его именем.

Проверьте синтаксис конфигурации NGINX внутри контейнера:

kubectl exec "$WEB_POD" -- nginx -t

Сообщения syntax is ok и test is successful показывают, что конфигурация приложения внутренне корректна.

Наконец, выполните компактную проверку работоспособности изнутри. >/dev/null отбрасывает загруженный HTML, а && запускает echo только в случае успешного выполнения wget. Поэтому сообщение об успехе появляется лишь после получения HTTP-ответа:

kubectl exec "$WEB_POD" -- wget -qO- http://127.0.0.1 >/dev/null && echo "NGINX responded inside the Pod"

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

get -> describe -> events -> repair manifest -> rollout status -> logs -> exec

При других неисправностях диагностика может остановиться на другом этапе, но переход от быстрых сводных данных к более глубокому исследованию помогает сохранять фокус.

Итоги

Вы прошли полный цикл начальной отладки в Kubernetes v1.35. Вы сравнили исправную и неисправную рабочие нагрузки, сузили область поиска с помощью меток и кратких полей, использовали describe и события для выявления неверного тега образа, исправили декларативный источник истины и проверили восстановление с помощью статуса развёртывания, журналов и команд внутри контейнера.

Главный вывод: выбирайте диагностические данные в соответствии с текущим этапом жизненного цикла рабочей нагрузки. Если контейнер ещё не запустился, изучайте состояние и события. Когда контейнер уже работает, журналы и exec помогают выявить поведение приложения на уровне самой программы. В следующем задании вам предстоит самостоятельно применить этот процесс.