Устранение неполадок в Kubernetes - стратегический подход

Работа с Kubernetes может серьезно озадачивать, особенно когда дело доходит до отладки и устранения сбоев. Основная сложность кроется в недостатке подробных сообщений об ошибках и сложности самой системы. Все еще больше усложняется огромным числом частей, движущихся в потоке оркестровки контейнеров, который представляется всего несколькими состояниями. Вы, к примеру, увидите, что есть не менее шести веских причин, по которым Pod может застрять в состоянии ContainerCreating или CrashLoppBackOff.

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

  1. Застревание Pod в состоянии ContainerCreating.
  2. CrashLoopBackOff и периодические перезапуски.
  3. Проблемы с сетью.

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

Как правило, при возникновении проблем чаще всего нужно начинать с изучения журнала событий (kubectl get events -n <NAMESPACE>). В нем фиксируются детали каждого шага оркестровки, которые и являются ключом к диагностике неполадок.

1. Застревание Pod в состоянии ContainerCreating 

Для понимания ContainerCreating, как и любого другого состояния Пода, главное точно знать его место в общем конвейере оркестровки. Эта информация помогает определить и исключить другие компоненты стека. Например, ContainerCreating подразумевает, что kube-scheduler выделил контейнеру рабочий узел и дал демону Docker команду запустить рабочую нагрузку. Но обратите внимание, что сеть на данной стадии еще не подготовлена  —  т.е. у рабочей нагрузки нет IP-адреса.

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

1.1 Сбой при обнаружении IP-адреса

KubeControlPlane запрашивает IP от CNI (например, Calico) и, если обнаружить IP не удается, ControlPane будет просто сидеть и ждать этот IP бесконечно, не делая записей в журнал и не выводя ошибок, при этом kubectl в терминале тоже ничего не проясняет. Регистрируется же данная проблема только в журналах kubelet/system.

Причины:

  1. Недостаток IP-адресов в IPPool, настраиваемом в CNI.
  2. Ошибки связи между kubelet и CNI.
  3. Ошибки конфигурации в CNI.

1.2 Сбой монтирования Configmaps

Configmaps содержат файлы, которые монтируются на (виртуальную) файловую систему контейнера в среде выполнения. Эта стадия оркестровки логируется в журналах событий.

Причины:

  1. Заполнение /var/lib/docker на узле, что препятствует правильной работе демона Docker.
  2. Ошибки/опечатки в имени Configmap при обращении во время развертывания Пода.

1.3 Сбой получения PV

Контейнеры, полагающиеся на информацию о состоянии, например базы данных или платформы сообщений, иногда тоже застревают на стадии ContainerCreating, если не могут смонтировать свой PersistentVolume через PersistentVolumeClaim, о чем можно узнать из журналов событий. Более же подробная информация содержится в системных журналах.

Причины:

  1. Ошибка связи между плагином CSI и облачным провайдером (Vsphere, AWS EBS и т.д.).
  2. При перемещении рабочих нагрузок между узлами кластера ControlPane для перемещения дисков выполняет процедуры attach и detach. Этот процесс может иногда затягиваться из-за таймаутов во время монтирования и размонтирования.

2. CrashLoopBackOff и периодические перезапуски

CrashLoopBackOff в первую очередь обозначает сбои в коде контейнера или самого ПО. Это состояние возникает, когда команда entrypoint в контейнере ошибочно завершается после запуска. У этого сбоя может быть очень много причин, но чаще всего объясняется он следующими.

2.1 Ошибка в скрипте запуска или InitContainers

Помимо ошибок ПО в самом контейнере, вызывать сбой при запуске и вести к CrashLoopBackOff могут и такие факторы, как внешние сбои при внедрении переменных среды, настройки монтирования, секреты или обращение к другим объектам K8s.

2.2 Превышение лимитов памяти

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

2.3 Недостаток пространства на диске или в хранилище

Контейнеры узла могут выдавать сбой из-за недостатка места хранилища в двух местах:

  1. PersistentVolume в Pod, что может повлиять на процессы контейнера.
  2. В /var/lib/docker (OverlayFS) рабочего узла, где запланирован Pod.

2.4 Liveliness probes

Механизм liveliness probe (датчик жизненности) позволяет ControlPane автоматически перезапускать контейнеры после сбоев, обеспечивая отказоустойчивость. Хотя эти датчики в то же время могут нарушать стабильность, если будут настроены либо интенсивно, либо просто ошибочно без учета отложенных запусков восстановления.

  1. Интенсивно настроенный Liveliness датчик принудит Kubernetes часто выполнять проверки. Но если контейнер, например, находится в середине критического события GC (в случае Java), тогда проверка его жизненности (liveliness) скорее всего провалится и приведет к перезапуску контейнера. 
  2. Измененный/отложенный запуск восстановления. Иногда запуск контейнера может затягиваться из-за попыток исправить вызванные сбоем ошибки. Такое часто наблюдается в приложениях с сохранением состояния, которые могут запускать процедуры восстановления для устранения таких проблем, как повреждение файловой системы. Поэтому, если датчик жизненности будет настроен недостаточно гибко, чтобы это допустить, контейнер может застрять в бесконечном цикле перезапусков.

3. Проблемы с сетью

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

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

3.1 Kube-proxy и таблицы IP

Главная назначение kube-proxy  —  облегчать взаимодействие между Service IP и его конечными точками в бэкенд (Pod IP). Поэтому, если ваш Pod находится на узле, имеющем сложности с подключением к IP сервиса, и возникают ошибки истечения времени подключения или отказа подключения, проблему зачастую может решить простой перезапуск kube-proxy, потому что, как и в случае с любой другой рабочей нагрузкой, возможно, контейнер kube-proxy на этом узле дал сбой. 

Ядро IP Table является основным компонентом маршрутизации трафика между сервисами и Подами в бэкенд, включая сообщение через node-ports. kube-proxy задействует таблицу IP-адресов для установки правил, облегчающих балансировку нагрузки и маскировку, в связи с чем эти таблицы нужно смотреть в первую очередь, если ваши Pod могут связываться с IP других Pod, но не с Service.

3.2 Kube-DNS

В случае сбоев при разрешении имен начинать поиск неисправности нужно во внутренней настройке DNS кластера K8s (по умолчанию DNS-сервис  —  это kubedns) и убедиться, что этот сервис досягаем через его IP-адрес Cluster или Pod.

Далее нужно убедиться, что resolv.conf в Подах и рабочих узлах содержит необходимые DNS домены и сервер имен для поиска.

3.3 Conntrack

Проблемы в Conntrack могут вести к вылетам подключений и несогласованности сетевого траффика. Коротко говоря, Conntrack используется ядром Linux для поддержания состояний подключения и логических потоков в системе. Это значит, что если ваше приложение, например, раскрывает внешний IP, и к нему выполняется миллион подключений, то состояние этих подключений и логические потоки отслеживаются в таблице ядра conntrack. При этом вполне разумно, что такие таблицы предусматривают жесткие лимиты, и если эти лимиты превышаются, то сеть приходит в беспорядок.

В RHEL лимиты подключений для conntrack можно проверить так:

$  sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_maxnet.netfilter.nf_conntrack_count = 167012
net.netfilter.nf_conntrack_max = 262144

3.5 CNI и таблица маршрутизации

Когда daemonset CNI (например, calico) терпит сбой на любом из узлов, то он нарушает маршрутизацию и сетевое подключение в кластере K8s, часто локально для подверженного сбою узла/узлов.

Как и в случае с kube-proxy, проблему может решить либо перезапуск контейнера calico на этом узле, либо контроллер calico. Аналогично всем прочим рабочим нагрузкам кластера Поды CNI тоже очень уязвимы для сбоев. 

Заключение

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

Мы рассмотрели три категории проблем и проанализировали многие связанные с ними неполадки:

  1. Застревание Pod в состоянии ContainerCreating.
  2. CrashLoopBackOff и периодические перезапуски.
  3. Проблемы с сетью.

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

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Koal Venkatesh Ganesan: Troubleshooting in Kubernetes: A Strategic Guide