Хабрахабр

История выкатки, которая затрагивала всё


Enemies of Reality by 12f-2

В принципе мы постоянно катим новые фичи в прод (как и все). В конце апреля, пока белые ходоки осаждали Винтерфелл, у нас произошло кое-что поинтереснее, мы сделали не совсем обычную выкатку. Масштаб её был таков, что любые потенциальные ошибки, которые мы могли допустить, поаффектили бы все наши сервисы и пользователей. Но эта была не такая, как все. Статья — о том, как мы этого добились и как желающие могут это повторить в домашних условиях.
Я не буду сейчас описывать принятые нами архитектурные и технические решения, рассказывать, как оно все работает. В итоге мы всё выкатили по плану, в запланированный и анонсированный срок даунтайма, без последствий для прода. Не претендую на полноту или технические детали, возможно, они появятся в другой статье. Это скорее заметки на полях о том, как проходила одна из самых сложных выкаток, которую я наблюдал и в которой принимал непосредственное участие.

Предыстория + что же это за такая функциональность

Мы строим облачную платформу Mail.ru Cloud Solutions (MCS), где я работаю техническим директором. И вот — пришло время приделать к нашей платформе IAM (Identity and Access Management), который обеспечивает единое управление всеми пользовательскими аккаунтами, пользователями, паролями, ролями, сервисами и прочим. Зачем он нужен в облаке — вопрос очевидный: в нем хранится вся пользовательская информация.

Но в MCS исторически сложилось немного по-другому. Обычно такие вещи начинают строить на этапе самых стартов любых проектов. MCS строился двумя частями:

  • Openstack с собственным модулем авторизации Keystone,
  • Hotbox (S3-хранилище) на базе проекта Облако Mail.ru,

вокруг которых затем появились новые сервисы.

Плюс мы использовали некоторые отдельные разработки Mail.ru, например — общее хранилище паролей Mail.ru, а также самописный openid-коннектор, благодаря которому обеспечивалась SSO (сквозная авторизация) в панели Horizon виртуальных машин (нативный UI OpenStack). По сути, это было два разных типа авторизации.

При этом не потерять никакого функционала по дороге, создать задел на будущее, который позволит нам прозрачно его дорабатывать без рефакторинга, масштабировать по функциональности. Сделать IAM для нас значило соединить это всё в единую систему, полностью свою. Также на старте у пользователей появилась ролевая модель доступа к сервисам (центральный RBAC, role-based access control) и некоторые другие мелочи.

И главное — тысячи живых пользователей на боевой продакшен-системе. Задача оказалась нетривиальной: python и perl, несколько бекендов, независимо написанные сервисы, несколько команд разработки и админов. Все это надо было написать и, главное, — выкатить без жертв.

Что мы собрались выкатить

Если очень грубо, где-то за 4 месяца мы подготовили такое:

  • Сделали несколько новых демонов, которые агрегировали функции, раньше работавшие в разных уголках инфраструктуры. Остальным сервисам прописали новый бекенд в виде этих демонов.
  • Написали свое центральное хранилище паролей и ключей, доступное для всех наших сервисов, которое можно свободно модифицировать, как нам нужно.
  • Написали с нуля 4 новых бекенда для Keystone (пользователи, проекты, роли, role assignments), которые, по сути, заменили его базу, и теперь выступает единым хранилищем наших пользовательских паролей.
  • Научили все наши сервисы Openstack ходить за своими политиками в сторонний сервис политик вместо того, чтобы читать эти политики локально с каждого сервера (да-да, по-умолчанию Openstack так и работает!)

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

Сначала мы решили немножко заглянуть в будущее. Как выкатывать такие изменения и не облажаться?

Стратегия выкатки

  • Можно было бы сделать выкатку в несколько этапов, но это бы увеличило срок разработки раза в три. Кроме того, на какое-то время у нас была бы полная рассинхронизация данных в базах. Пришлось бы писать свои инструменты синхронизации и долго жить с несколькими хранилищами данных. А это создаёт самые разнообразные риски.
  • Всё, что могли подготовить прозрачно для пользователя, сделали заранее. На это ушло 2 месяца.
  • Мы позволили себе даунтайм в течение нескольких часов — только на операции пользователей по созданию и изменению ресурсов.
  • Для работы всех уже созданных ресурсов даунтайм был недопустим. Мы запланировали, что при выкате ресурсы должны работать без даунтайма и аффекта для клиентов.
  • Чтобы снизить влияние на наших заказчиков, если что-то пойдёт не так, мы решили выкатываться вечером в воскресенье. Ночью меньше заказчиков занимается управлением виртуальными машинами.
  • Всех наших клиентов мы предупредили о том, что в выбранный для выкатки период управление сервисами будет недоступно.

Отступление: что есть выкатка?

<осторожно, философия>

Ставишь CI/CD, и автоматом все доставляется на прод. Каждый айтишник легко ответит, что такое выкатка. 🙂

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

Выкатка состоит из четырёх больших аспектов: А вся картинка такова.

  1. Доставка кода, включая изменение данных. Например, их миграции.
  2. Откат кода — возможность вернуться, если что-то пойдёт не так. Например, через создание бэкапов.
  3. Время каждой операции выкатки / отката. Надо понимать тайминг любой операции первых двух пунктов.
  4. Затронутый функционал. Нужно обязательно оценить как ожидаемый позитивный, так и возможный негативный эффект.

Все эти аспекты необходимо учесть для успешной выкатки. Обычно оценивают только первый, в лучшем случае второй пункт, и тогда выкатка считается успешной. Но третий и четвертый даже более важны. Какому пользователю понравится, если выкатка займет 3 часа вместо минуты? Или если на выкатке зааффектится что-то лишнее? Или даунтайм одного сервиса приведет к непредсказуемым последствиям?

Акт 1..n, подготовка к релизу

Сначала я думал кратко описать наши встречи: всей командой, ее частями, кучи обсуждений в кофе поинтах, споров, тестов, брейнштормов. Потом подумал, что это будет лишним. Четыре месяца разработки всегда состоят из такого, особенно когда пишешь не то, что можно доставлять постоянно, а одну большую фичу на живую систему. Которая затрагивает все сервисы, но у пользователей ничего не должно поменяться, кроме «одной кнопки в веб-интерфейсе».

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

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

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

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

И вот…

Акт финальный, перед выкаткой

… настало время выкатываться.

Надо сделать волевое усилие, понимая, что всего не найдешь, но веря, что сделал все разумные предположения, предусмотрел все возможные случаи, закрыл все критичные баги, и все участники сделали всё, что могли. Как говорится, произведение искусства невозможно завершить, только закончить над ним работать. Чем больше кода выкатываешь, тем сложнее убедить себя в этом (к тому же любой понимает, что предусмотреть всё невозможно).

То есть — что угодно может пойти не так, кроме: Мы приняли решение, что готовы выкатываться, когда убедились, что мы сделали всё возможное, чтобы закрыть все риски для наших пользователей, связанные с неожиданными аффектами и даунтаймами.

  1. Аффекта (священной для нас, драгоценнейшей) пользовательской инфраструктуры,
  2. Функциональностей: использование нашего сервиса после выкатки должно быть таким же, как и до неё.

Выкатка


Двое катят, 8 не мешают

На это время у нас есть как план выкатки, так и план отката. Берем даунтайм на все запросы от пользователей в течение 7 часов.

  • Сама выкатка занимает примерно 3 часа.
  • 2 часа — на тестирование.
  • 2 часа — запас на возможный откат изменений.

Составлена диаграмма Ганта на каждое действие, сколько времени оно занимает, что идет последовательно, что производится параллельно.

Ценнейший инструмент синхронизации
Кусочек диаграммы Ганта выкатки, одна из ранних версий (без параллельного исполнения).

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

Хроника событий

Итак, 15 человек приехало на работу в воскресенье 29 апреля, в 10 вечера. Помимо ключевых участников, некоторые приехали просто на поддержку команды, за что им отдельное спасибо.

Выкатываться без тестирования невозможно, прорабатываем варианты. Отдельно стоит упомянуть, что наш ключевой тестировщик — в отпуске. Коллега соглашается потестить нас из отпуска, за что ей безмерная благодарность от всей команды.

Стоп
Останавливаем пользовательские запросы, вешаем шильдик, мол, технические работы. 00:00. Проверяем, что ничего не упало, кроме того, что должно было. Мониторинг вопит, но все штатно. И начинаем работы по миграции.

После каждого действия сверяемся с таймингами, что не превышаем их, и все идет по плану. У всех есть распечатанный план выкатки по пунктам, все знают, кто что делает и в какой момент. 🙂 Те, кто не участвует в выкатке непосредственно на текущем этапе, готовятся, запустив онлайн-игрушку (Xonotic, типа 3 кваки), чтобы не мешать коллегам.

Выкатили
Приятный сюрприз — заканчиваем выкатку на час раньше, за счет оптимизации наших баз и скриптов миграции. 02:00. Все переходят в режим тестирования, разбираются на кучки, и начинают смотреть, что в итоге получилось. Всеобщий клич, «выкатили!» Все новые функции в проде, но в интерфейсе пока видим только мы.

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

Две большие проблемы vs четыре глаза
Обнаруживаем две большие проблемы. 02:30. Обе связаны с несовершенством скриптов миграции для некоторых краевых случаях. Поняли, что заказчики не увидят некоторые подключенные сервисы, и возникнут проблемы с аккаунтами партнёров. Надо фиксить сейчас.

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

-2 проблемы +2 проблемы
Две предыдущие большие проблемы пофикшены, почти все мелкие тоже. 03:00. Приоритезируем, распределяем по командам, некритичное оставляем на утро. Все незанятые в фиксах активно работают в своих аккаунтах и репортят, что находят.

Не все политики сервисов доехали правильно, так что некоторые пользовательские запросы не проходят авторизацию. Опять запускаем тесты, они обнаруживают две новые большие проблемы. Бросаемся смотреть. Плюс новая проблема с аккаунтами партнёров.

Экстренный синк
Одна новая проблема исправлена. 03:20. Понимаем, что происходит: предыдущий фикс починил одну проблему, но создал другую. Для второй мы устраиваем экстренный синк. Берем паузу, чтобы разобраться как сделать правильно и без последствий.

Шесть глаз
Осознаём, какое должно быть итоговое состояние базы, чтобы все было хорошо у всех партнёров. 03:30. Пишем запрос в 6 глаз, прокатываем на предпроде, тестируем, катим на прод.

Всё работает
Все тесты прошли, критичных проблем не видно. 04:00. Чаще всего тревога ложная. Периодически в команде у кого-то что-то не работает, оперативно реагируем. Сидим, фиксим, фиксим, фиксим. Но иногда что-то не доехало, где-то не работает отдельная страница. Отдельная команда запускает последнюю большую фичу — биллинг.

Точка невозврата
Близится точка невозврата, то есть время, когда, если начнем откатываться, не уложимся в выданный нам даунтайм. 04:30. Есть несколько багов на отдельных страничках, действиях, статусах. Есть проблемы с биллингом, который всё знает и записывает, но упорно не хочет списывать деньги с клиентов. Принимаем решение, что выкатка состоялась, откатываться не будем. Основной функционал работает, все тесты проходят успешно.

Открываем на всех в UI
Баги пофикшены. 06:00. Открываем интерфейс всем. Какие-то, не аффектящие пользователей, оставлены на потом. Продолжаем колдовать над биллингом, ждем фидбека пользователей и результатов мониторинга.

Проблемы с нагрузкой на API
Становится ясно, что мы немного неправильно распланировали нагрузку на наше API и тестирование этой нагрузки, которое не смогло выявить проблему. 07:00. Мобилизуемся, ищем причину. В итоге ≈5% запросов фейлится.

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

Фикс API
Выкатили фикс для нагрузки, фейлы ушли. 08:00. Начинаем расходиться по домам.

Всё
Все пофикшено. 10:00. Остался биллинг, его будем восстанавливать уже завтра. В мониторинге и у заказчиков тихо, команда постепенно уходит спать.

Далее в течение дня были выкатки, которые починили логи, нотификации, коды возврата и кастомы у некоторых наших клиентов.

Могло бы быть, конечно, и лучше, но мы сделали выводы о том, чего нам не хватило, чтобы достигнуть совершенства. Итак, выкатка прошла успешно!

Итого

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

Во время выкатки:

  • новых и измененных демонов — 5 штук, заменивших 2 монолита;
  • изменений внутри баз данных — все 6 наших баз с данными пользователей затронуто, выполнены выгрузки из трех старых баз в одну новую;
  • полностью переделанный фронтэнд;
  • количество выкаченного кода — 33 тысячи строк нового кода, ≈ 3 тысяч строк кода в тестах, ≈ 5 тысяч строк кода миграции;
  • все данные целы, ни одна виртуалка заказчика не пострадала. 🙂

Хорошие практики для хорошей выкатки

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

  1. Первое, что надо — понять, как выкатка может повлиять или повлияет на пользователей. Будет ли даунтайм? Если будет, то даунтайм чего? Как это отразится на пользователях? Какие возможны наилучшие и наихудшие сценарии? И закрывать риски.
  2. Всё спланировать. На каждом этапе нужно понимать все аспекты выкатки:
    • доставка кода;
    • откат кода;
    • время каждой операции;
    • затронутый функционал.
  3. Проиграть сценарии до тех пор, пока не станут очевидны все этапы выкатки, а также риски на каждом из них. Если в чем-то есть сомнения, можно взять паузу и исследовать сомнительный этап отдельно.
  4. Каждый этап можно и нужно улучшить, если это поможет нашим пользователям. Например, уменьшит даунтайм или уберет какие-то риски.
  5. Тестирование отката гораздо важнее, чем тестирование доставки кода. Нужно обязательно проверить, что в результате отката система вернётся в первоначальное состояние, подтвердить это тестами.
  6. Все, что может быть автоматизировано, должно быть автоматизировано. Все, что не может быть автоматизировано, должно быть заранее написано на шпаргалке.
  7. Зафиксировать критерий успешности. Какой функционал должен быть доступен и в какое время? Если этого не происходит, запускайте план отката.
  8. И самое главное — люди. Каждый должен быть в курсе, что делает, для чего и что от его действий зависит в процессе выкатки.

А если одной фразой, то с хорошим планированием и проработкой можно выкатить без последствий для прода всё что угодно. Даже то, что аффектит все ваши сервисы в проде.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть