← Все статьи

Одна забытая DHCP-опция положила весь Kubernetes-кластер

После ребута хоста около пятидесяти подов ушли в крашлуп, хотя все ноды были Ready. Причина — забытая DHCP-опция classless routes, которая молча убрала шлюз по умолчанию.

В моей домашней лаборатории живёт kubeadm-кластер из семи нод на одном хосте Proxmox. После переезда хоста кластер поднялся в худшем виде: около пятидесяти подов в CrashLoopBackOff или ImagePullBackOff по всему кластеру. CoreDNS не мог достучаться до API-сервера — connect: no route to host. GitLab-раннер ничего не резолвил. Образы не тянулись нигде.

При этом все семь нод были Ready. calico-node и kube-proxy работали, а таблица IPVS для ClusterIP API-сервера указывала на все три control-plane ноды — ровно как положено.

Неверная первая гипотеза

Всё кричало «сломан CNI». Поды ни с чем не могли связаться, Calico — очевидный подозреваемый, kube-proxy — сразу за ним. Первое время ушло на то, чтобы убедиться, что оба запрограммированы правильно. Так и было.

Зацепка

Проблему разрезало простое сравнение: под с hostNetwork работал, обычный — нет. Значит, собственный сетевой namespace ноды в порядке, сломан только выход подов наружу.

Изнутри сломанного пода ip route get до API-сервера резолвился, но TCP падал с EHOSTUNREACH. А ip neigh показал главную улику: link-local шлюз Calico 169.254.1.1 был в состоянии INCOMPLETE. Под не мог даже получить ARP-ответ от собственного шлюза.

На хосте ip route show default был пуст. Маршрута по умолчанию не было вообще.

Первопричина

На ARP-запросы к 169.254.1.1 Calico отвечает через proxy-ARP, а proxy-ARP отвечает только за адрес, до которого у хоста есть маршрут. Нет маршрута по умолчанию — нет ответа — у подов нет выхода никуда.

Почему все VM загрузились без маршрута по умолчанию? Мой роутер раздаёт по DHCP опцию 121 (classless static routes). Когда-то я настроил в ней пару маршрутов через старый шлюз, а после перенумерации LAN её никто не обновил. RFC 3442 говорит прямо: клиент, получивший опцию 121, обязан игнорировать опцию 3 — шлюз по умолчанию. Поэтому каждый клиент поставил только протухшие classless-маршруты и молча выкинул default gateway.

Каскад выглядел так: нет маршрута по умолчанию → нет proxy-ARP → поды не видят API → CoreDNS в крашлупе → кластерного DNS нет → всё, что резолвит имена, тоже в крашлупе. Сверху добавился зависший локальный резолвер на нодах — отсюда и проблемы с образами.

Исправление

На ходу я вернул маршрут по умолчанию на каждой ноде через ip route replace default via <шлюз>. Поды ожили в тот момент, когда 169.254.1.1 стал REACHABLE.

Постоянное исправление — на роутере: я переписал опцию 121 (и её близнеца от Microsoft, опцию 249) так, чтобы она несла маршрут по умолчанию — префикс нулевой длины и правильный шлюз. Удалить опцию целиком на дефолтной сети этого роутера было нельзя, поэтому надёжный путь — исправить значение на месте. Ещё два настоящих ребута спустя кластер собрался сам, без ручной помощи.

Выводы

  • Есть опция 121 — опция 3 игнорируется. Протухшие classless-маршруты способны снять маршрут по умолчанию с целой подсети. Проверяйте DHCP-опции после любой смены шлюза или подсети.
  • 169.254.1.1 INCOMPLETE в ip neigh пода — главная улика для Calico. CNI исправен, просто хост не может проложить путь к шлюзу.
  • Сравнивайте под с hostNetwork и обычный под как можно раньше. Это за один шаг вдвое сужает область поиска.
  • «Все ноды Ready» ничего не говорит о сети подов. Готовность ноды и сеть подов ломаются независимо.