Давно ли вам приходилось перезапускать стейджинговую систему, на которой развернута масса приложений и работает не одна сотня команд? Мы частенько издевались над стейджем, но никогда не выключали его целиком. И в процессе плановой замены сетевого стека в кластере 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 и теперь готовы к ещё более худшему на проде.

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