Давно ли вам приходилось перезапускать стейджинговую систему, на которой развернута масса приложений и работает не одна сотня команд? Мы частенько издевались над стейджем, но никогда не выключали его целиком. И в процессе плановой замены сетевого стека в кластере k8s stage решили сделать масштабную проверку возврата системы и всех запущенных на ней приложений в работоспособное состояние после «внезапного отключения питания в локальном ЦОД».
Кабели никто перерезать не собирался, но идея «выключить и включить» традиционно выглядела как «приключение на 20 минут». Именно так всё и случилось: кластер k8s не смог вернуться в рабочий режим, приложения не запускались, и причины тому крылись не внутри, а снаружи.
Под катом хронология двухдневных драматических событий, варианты действий и некоторые размышления после проведённых «учений».

Что было до
В кластерах k8s была запланирована замена компонентов, которые отвечали за сетевой стек. В начале составления плана работ рассматривали вариант с полной заменой технологий, который предполагал фактически полное уничтожение кластера и заселение его приложениями заново.
Однако от полной замены сетевого стека отказались — это сулило длительное хождение по различным граблям. Зато нашли более удобное решение и возможность провести обновление практически бесшовно. С ним нам грозили замедление в обработке и потеря связности на части хостов на несколько десятков секунд. Тут можно легко выкрутить режим паранойи и терять сетевые пакетики только на одной ноде за раз — все приложения, которые жили в нескольких репликах, должны спокойно это пережить.
На нашей тестовой инсталляции из десяти нод было три управляющих, три ингресс и четыре несущих — так мы проверяли сам процесс замены сетевого стека. Провели все подготовительные работы на конфигурациях, которые повторяли существующие кластеры (ноды как на RHEL, так и на Debian), и составили пошаговый план замены существующих решений с минимизацией рисков.
Последним шагом плана была перезагрузка всех нод кластера, которые в безопасном режиме выполняются по очереди: выселяем все поды с одной ноды, перезагружаем её и переходим к следующей. На самом большом кластере k8s stage это должно было занять не меньше двух суток, а на самом маленьком кластере с пользовательской нагрузкой — не меньше трёх.
После обсуждения всех вопросов с архитектором приняли решение использовать вариант с полной остановкой кластера вместо многосуточного и мучительного для разработчиков процесса миграции. А заодно решили проверить, вернётся ли кластер в рабочее состояние после полного выключения. Все же помнят, как сгорел ЦОД OVH? Мы недавно уже теряли часть ЦОД по питанию, но осталась часть ресурсов, которая продолжила работать. Нам повезло, и Kubernetes оказался там. Но протестировать восстановление после такого нужно, чтобы заранее знать, к чему готовиться в случае реальной катастрофы.
В итоге в плане работ поменяли последние пункты с плавной последовательной перезагрузки на полное выключение кластеров.

ОНО
Пошутив про назначение старта работ на вечер ближайшей пятницы, решили начать во вторник утром.
09:20 Блокировка БД.
Во время смены голого etcd на API Kubernetes, который хранил информацию обо всём сетевом стеке k8s, требовалась блокировка базы данных — это нужно, чтобы минимизировать любые воздействия. Она приводит к невозможности запуска новых подов: деплоев и просто перезапусков подов вследствие выхода из строя ноды. Самый большой кластер стейджа переводится в режим ReadOnly, в это время база данных блокируется, бэкапится и начинается процесс переноса данных.
Шли строго по плану, всё было хорошо.
13:54 «Чуи, мы дома». eBPF работает на всём кластере, всё стабильно, ощутимых просадок не обнаружено.
14:01 Всем нодам кластера отправлена команда poweroff. Тот самый момент, когда стага начала останавливаться. Появилось странное чувство внезапной тишины вокруг. Если вы когда-нибудь заходили в ЦОД во время выключения хостов, то вам оно знакомо.
14:05 Включаю ноды. Сначала по-читерски управляющий слой (три ноды), затем через три минуты все остальные 138 нод.
14:10 K8s кряхтит и пытается возвращать к жизни поды. Мониторинга нет, идём по приборам. Практически единовременный запуск почти 5 000 подов — это серьёзная задачка.
14:20 Часть подов успешно запускается, а часть не может. Этой страдающей частью оказываются поды с белыми адресами, и их больше 700 штук.
14:50 Поды с белыми адресами падают по таймауту получения адреса. За выдачу адреса у нас отвечают несколько сетевых плагинов:
Multus. Это оркестратор, который отдаёт команды, какой плагин нужно запустить, чтобы началась магия. Он вне подозрений, потому что через него запускаются и поды без белых адресов.
Macvlan. Это плагин, который позволяет сделать так, чтобы выдаваемые белые адреса были доступны для банковской сети.
Whereabouts. Это IPAM — он просто выбирает свободные адреса и отдаёт их по запросу, ну и чистит удаляемое.
14:52 Отсматриваю все возможные вариации происходящего: на каких нодах, в каком виде. Вспоминаю, что при замене нод ETCD, если поды не могли достучаться до старого хранилища, они впадали в ступор и вели себя точно так же, как и в этот раз — зависали и убивались по таймауту. Тогда мы нашли решение — зачистка информации о запущенных контейнерах на ноде. Для этого нужно погасить containerd, удалить всю информацию о выделенных сетевых неймпейсах и запустить всё назад. Тогда придёт kubelet и расскажет containerd, что у него должны быть поды — и всё будет хорошо.
А ещё мы знаем, что whereabouts не умеет чистить за собой адреса. Если под c белым адресом вырывали с ноды с корнем, а poweroff по ноде — это как раз такой случай. Такие адреса остаются висеть мёртвым грузом, и из-за них свободных адресов для выдачи остаётся сильно меньше.
Бегаю к разным нодам, руками осуществляю эти схемы зачистки и добавляю зачистку информации о всех контейнерах, а не только сетевых групп. Нужно было проверить, как она себя ведёт на ингрессах, на обычных нодах с RHEL, на нодах с Debian и на нодах со StateFul-приложениями. Становится очевидно, что двигаюсь в верном направлении — после вычищения ноды приложения начинают запускаться нормально.
18:05 Успешно обежав с десяток нод, запускаю зачистку автоматами. Указываю, что запуск должен быть ��е более чем на двух нодах одновременно, чтобы не задавить систему ресурсоёмкими конкурентными запросами. Жду, пока скрипт заходит на две ноды, останавливает containerd, удаляет все данные, запускает containerd и переходит к следующей паре нод.
19:05 Заметили, что некоторые поды не могут запуститься, потому что из registry не читаются данные. Из-за запуска большого количества запуска подов registry в инфраструктурном кластере k8s не справляется и падает с OOM.
19:20 Скрипт отработал, но чудо не настало, большинство приложений всё ещё не может нормально запуститься.

19:40 Решаю запустить ещё одну зачистку, но на этот раз полную: вместо выключения containerd полностью выключить ноду, чтобы, когда все ноды выключатся, включить их снова, фактически запустив всё с нуля.
20:00 Запускаю все ноды. Чуда не случилось, сильно лучше не стало. Ну а то, что это решение сделало только хуже, я узнаю уже на следующий день.
21:20 Разбор логов наталкивает на один момент, который пропустили в тестовых кластерах: calico при переезде на eBPF обязательно нужно приучить работать с API Kubernetes либо через внешние адреса, либо поставив на ноду nginx и настроив ему апстримами адреса управляющих нод кластера. Это у нас уже было сделано давно, но только для calico не использовалось. А вот multus и whereabouts мы не приучили ходить через 127.0.0.1, и они пытаются ставить коннекты в фактически недоступный для них внутренний адрес и падают по таймауту.
Вношу правки, но это уже никак не спасает ситуацию.
После всех зачисток по логам и по состоянию процессов на серверах становится понятно, что теперь всё зависает только на плагине whereabouts.
Следующие сутки
01:00 Перестав мириться с жуткой мигренью, оставил всё до завтра. Все кругом говорят, что стейдж может лежать сутки, и это нормально. Морально к этому всё ещё не готов, но сил больше нет.
С 8 утра ещё пытался делать попытки что-то осознать, но головная боль сильно мешала.
11:30 Пришёл в свою команду по сопровождению Kubernetes, чтобы подключились те, у кого взгляд не замылен и голова не раскалывается.
13:30 2024-06-19T11:29:22+03:00 [debug] Started leader election
Строка, которая на больную голову увела меня в сторону, потому что я не нашел её в коде и посчитал, что мы использовали не ту версию приложения. Версия образа была валидна, я смотрел не ту ветку в git.
14:30 Удаляю объект lease в Kubernetes, с помощью которого whereabouts выдаёт подам айпишники, защищаясь от конкурентных запросов. Whereabouts создаёт новый, и в нём начинается жизнь, а в старом её не было. Это всё не сильно помогает, потому что поды уже заняли все адреса в IPAM.
15:30 Запускаю экстерминатус. В этот раз после зачистки и выключения нод также полностью очищаю IPAM — всё равно в кластере нет подов, занявших адреса. Чищу всю информацию о контейнерах на ноде, в том числе и образы.
Делаю слепок всех установленных deployment и statefulset и удаляю эти объекты из кластера, чтобы после запуска k8s смог запустить ноды и поднять только минимум системной обвязки, которой не требуются белые адреса.
15:39 Включаю серверы, запускается k8s, все ноды возвращаются в кластер минут за пять. Запускаю одно единственное тестовое приложение — всё запускается, адреса выдаются. Блаженство!
15:50 Запускаю деплой всех приложений из слепка. Сначала statefulset, затем минут через десять отправляю запускаться все deployment.
Оно работает...
Но из-за зачистки всего и вся, мы получили бутшторм не по CPU, а по сети — все встали в очередь за образами.
Successfully pulled image "private.registry.address/svc_tochka-id/resource-server/stage:stage-01be9cd0" in 30.761s (59m26.654s including waiting)
19:00 Большинство приложений смогли дождаться своих образов и наконец-то запустились.
Вместо заключения
Мы пытались объединить два вида работ — замену сети и выключение. И это перемножило нам количество работы, а не сложило, как мы бы хотели.
Без каких-то приложений на стейдже, вероятно, можно жить часами, днями или неделями. Но у инфраструктуры фактически нет разделения на стейдж\пре\прод. Да, послабления на стейдже есть, потому что клиенты не страдают напрямую, но целый чат приободряющей поддержки во время сбоя — это мощно.
Решения для кластеров с парой десятков нод, скорее всего, не подходят для кластеров с сотнями нод. Но такие учения по восстановлению работоспособности всего и вся, хоть и очень болезненны для многих, возможны лишь на кластерах, приближённых к проду. Наш k8s в стейдже понемногу пытается догнать продовые кластеры в Kubernetes по количеству нод: в проде — 240 и 194 ноды, в препроде — 109 и 77, в стейдже — 141.
Из-за запуска большого количества подов в стейдже registry, который находится не в стейдже, падает по ООМ. В этот раз ресурсов не хватило, так как не были готовы к такой нагрузке и такому объёму запросов. Сделали выводы, поняли ресурсы registry и теперь готовы к ещё более худшему на проде.
Насмотренность за счёт таких учений и возможность поделиться знаниями по происшествиям — это чуть ли не единственный способ показать, на что стоит обращать внимание, если вращаешь чёрные ящики, а не приложение, в котором знаешь каждую строчку кода наизусть.
