День, когда Земля остановилась

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

Кабели никто перерезать не собирался, но идея «выключить и включить» традиционно выглядела как «приключение на 20 минут». Именно так всё и случилось: кластер k8s не смог вернуться в рабочий режим, приложения не запускались, и причины тому крылись не внутри, а снаружи.

Под катом хронология двухдневных драматических событий, варианты действий и некоторые размышления после проведённых «учений».

587621819fafb1cfe5b168a9216da911.png

Что было до

В кластерах k8s была запланирована замена компонентов, которые отвечали за сетевой стек. В начале составления плана работ рассматривали вариант с полной заменой технологий, который предполагал фактически полное уничтожение кластера и заселение его приложениями заново. 

Однако от полной замены сетевого стека отказались — это сулило длительное хождение по различным граблям. Зато нашли более удобное решение и возможность провести обновление практически бесшовно. С ним нам грозили замедление в обработке и потеря связности на части хостов на несколько десятков секунд. Тут можно легко выкрутить режим паранойи и терять сетевые пакетики только на одной ноде за раз — все приложения, которые жили в нескольких репликах, должны спокойно это пережить.

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

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

После обсуждения всех вопросов с архитектором приняли решение использовать вариант с полной остановкой кластера вместо многосуточного и мучительного для разработчиков процесса миграции. А заодно решили проверить, вернётся ли кластер в рабочее состояние после полного выключения. Все же помнят, как сгорел ЦОД OVH? Мы недавно уже теряли часть ЦОД по питанию, но осталась часть ресурсов, которая продолжила работать. Нам повезло, и Kubernetes оказался там. Но протестировать восстановление после такого нужно, чтобы заранее знать, к чему готовиться в случае реальной катастрофы.

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

898fbf5fc87c51cd001e3696d317baab.png

ОНО

Пошутив про назначение старта работ на вечер ближайшей пятницы, решили начать во вторник утром.

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 и теперь готовы к ещё более худшему на проде.

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

© Habrahabr.ru