Кульминацией разработки является доставка функционала конечному потребителю. Этот момент наиболее важен и наиболее опасен, так как, в случае ошибки, пользовательский опыт может ухудшиться. Здесь я опишу какие меры можно предпринимать, чтоб избежать ошибки и когда.
Начну с последнего. Очень распространено отношение, что доставкой в прод является факт появления новой версии приложения на окружении. Но, доставкой изменений в прод является любая манипуляция.
Так распространёнными будут:
обновление инфраструктурных компонентов, например .net
изменение схем данных
миграции и ручные правки данных
нестандартные развертывания (в полуавтоматическом или полностью ручном режиме)
что угодно, что затрагивает прод.
Примечательно, что доставка новой версии приложения обычно сопряжена с наибольшим количеством проверок. Это и тестирование разработчиком; несколько видов автоматизированного тестирования, например unit, integration, e2e, load и т. д; проверки QA и ответственным менеджером; в конце концов может случиться демо и даже запуск на пилотную группу пользователей. Это обусловлено тем, что доставка версии самая распространённая операция и количественно больше всего инцидентов связано с доставками версий. Как доставки, так и инциденты случаются так часто, что кажется все уже знают, как обеспечивать надежность и что делать нельзя, но практика показывает, что нет. К сожалению, люди даже с многолетним опытом могут выпустить изменения в прод не обеспечив всех необходимых проверок. Существует довольно широкий набор мер обеспечения качества:
Тестирование
Есть разные инструменты тестирования:
Ручная проверка изменений разработчиком. Абсолютно обязательная мера.
Unit тестирование. Отсутствие такой практики обычно говорит о низкой культуре разработки и, как следствие, качестве продукта. Не покрывать unit тестами можно одноразовые либо вспомогательные инструменты, чаще всего не имеющие релизных циклов и представляющие минимальную ценность.
Ручное тестирование QA. Обычно используется в молодых компаниях, со временем замещаясь автоматизацией. В обязательном порядке должно применяться либо ручное, либо автоматизированное тестирование.
Интеграционное тестирование. Это очень широкий термин и может обозначать как пухлые unit тесты на стероидах, так и тесты разрабатываемые QA инженерами и рассматривающие продукт как черный ящик. Для упрощения картины мы отнесем первые к Unit тестам, а вторые к e2e тестам.
E2e тесты или тесты полного цикла. Для backend сервиса это клиент API выполняющий какие-то операции на некотором окружении. Отсутствие таких тестов, сильно удорожает регрессионное тестирование. Чаще всего говорит о низкой степени зрелости процесса разработки. Традиционно первые проверки продукта осуществляются в ручном режиме. Но если автоматизация не добралась до сюда после некоторого количества релизов может говорить, что процессы не налажены должным образом. Отсутствие е2е тестов для крупных коммерческих продуктов сильный негативный сигнал.
Нагрузочное тестирование. Разновидность автоматизированного тестирования. Применяется в основном для тестирования нагруженных или требовательных к ресурсам систем. Существует много разных форм нагрузочного тестирования, направленных на выявление деградаций производительности, точек отказов, способности к самовосстановлению и т. д.
Хаотичное тестирование. Применяется для оценки работоспособности системы при разного рода отказах. Например, может ответить на вопрос "а что будет, если мы отключим эту БД". Чаще всего представляет интерес с точки зрения понимания работоспособности систем переключения нагрузки на резервные мощности, либо автоматического восстановления системы после отказа.
Тестирование на выносливость. Способ тестирования продукта, когда версию разворачивают на окружении и подвергают продолжительной нагрузке подобной той, что мы ожидаем на прод окружении. Эта нагрузка может быть сгенерирована машиной, либо получена за счет обычных действий сотрудников компании. Так можно написать скрипты, эмулирующие нагрузку в течении нескольких дней. Можно применять изменения с прод окружения к тестовому на протяжении некоторого периода времени. Можно развернуть версию на Dev/QA окружении и дать отстояться некоторое время. В конце концов, если компания пользуется собственным продуктом можно выпустить версию только для внутренних сотрудников.
И другие. Тестирование обязательная и наиболее эффективная мера обеспечения надежности.
Окружения
Важным инструментом надежности являются изолированные окружения, на которых происходит тестирование продукта. Самым первым окружением будет чаще всего локальная машина разработчика. Далее могут быть:
Эфемерные окружения. Короткоживущие окружения, создающиеся на время этапа тестирования.
Локальное окружение команды.
Общее Dev окружение.
Более стабильное QA окружение для нужд тестирования.
Staging или препрод окружение. На такие окружения часто направляется копия нагрузки с прода.
Прод окружение. Наша цель.
Крайне желательно в цепочке окружений иметь максимально приближенную к прод окружению конфигурацию. Это позволит провести максимально надежные проверки и может избавить от сюрпризов. О них позже.
На что следует обратить внимания, что окружения должны быть именно изолированными. Прод и остальные окружения часто располагают в рамках изолированных подсетей, настолько это важно. Несоблюдение изоляции приводит к сращиванию окружений, когда ошибки на одном, начинают отражаться на работоспособности другого. Исключением будут процессы наполнения окружений. Например, можно создавать тестовые окружения на базе слепка прода. Можно доставлять в прод репозитории версии из репозиториев на предварительных окружениях. При этом сервисы между окружениями никак не взаимодействуют. И это очень важный момент.
Чем больше окружений на пути продукта в прод, тем больше шансов поймать ошибку раньше конечного пользователя.
Безопасные развертывания
Отличной мерой может оказаться и порционное переключение нагрузки (canary deployment). Подразумевает, что в проде существует несколько версий приложения и на новую нагрузка переключается малыми порциями с сопутствующим анализом состояния сервиса.
Другой популярной мерой отказоустойчивых развертываний будет blue-green развертывание. В этом случае используется два экземпляра приложения активный и пассивный. В каждый момент времени полезную нагрузку получает только активный экземпляр. В то время как на пассивный может разворачиваться новая версия. После завершения развертывания на пассивном экземпляре хорошей практикой будет запустить тесты. В случае прохождения тестов активный и пассивный экземпляр меняются местами. В экстренной ситуации откат происходит максимально быстро за счет обратного переключения.
Зоны риска
Часто мы забываем о необходимости применения мер обеспечения надежности в разных ситуациях. Например:
Сложные развертывания
Полуавтоматические развертывания с дополнительным ручным контролем. Это отдельная история ошибок. Очень часто к нестандартным развертываниям относятся недостаточно серьезно. Хотя сам факт необходимости нестандартных мер уже говорит, что ситуация сложнее регулярной. Я крайне рекомендую для таких ситуаций готовить детальный план, в обязательном порядке включающий инструкции по отмене изменений, отправлять его коллегам на ревью и тестировать на всех тех же окружениях, что применяются для тестирования продукта. Особо подчеркну важность наличия плана отмены изменений. Отмена изменений (откат) — это наиболее популярная эффективная мера реакции на инцидент. Вторая по популярности – перезапуск. Обе меры должны быть максимально доступны. Перечисленное может спасти прод от инцидента, либо минимизировать последствия. Я становился свидетелем отказов по каждой из причин:
отсутствие плана
отсутствие ревью плана
отсутствие тестирования плана
отсутствие плана отката изменений
А иногда не только свидетелем, но и виновником.
Миграции данных
Довольно распространённым случаем операции на прод окружении будет миграция данных. К миграциям можно отнести как перенос данных из одной базы данных в другую, так и изменения структуры данных. Например, добавление поля, столбца или изменения правил индексации. Естественно, список не полный. Нередко, для подобных целей готовится некоторый скрипт и запускается в ручном режиме сразу на прод окружении. Очевидно, что не по плану здесь ничего пойти не может так как и плана-то нет. Отказы при таких манипуляциях дело обыденное. Хорошей практикой будет написать скрипт применения изменений, скрипт отмены изменений и передать оба скрипта на тестирование. Даже когда операция кажется элементарной, например, заменить одно значение на другое в столбце. Меры обеспечения надежности редко окажутся лишними. Очень часто мы недооцениваем сложность операции.
Сюрпризы
Инциденты на проде крайне редко являются запланированными событиями. Обычно из таких событий мы узнаем что-то новое, учимся, вырабатываем новые меры обеспечения надежности, улучшаем мониторинг. Инциденты происходят даже в тех компаниях, где максимально щепетильно относятся к доставке изменений в прод со всевозможными планами, согласованиями и множественными проверками. Это связано с тем, что работа конкретного программного обеспечения зависит от множества факторов и не все из них под контролем разработчика. Так для работы .net приложения в одном из типовых сценариев требуется:
непосредственно приложение
среда исполнения
окружение
операционная система
машина виртуализации
ресурсы виртуальной машины
еще одна операционная система
аппаратное обеспечение сервера
сетевые маршрутизаторы и ПО на них
потребитель со своей спецификой
И это очень упрощенная картина. Так вот, чаще всего мы тестируем так, как будто существует только наше приложение. Все остальные факторы мы просто игнорируем. Их нет, до тех пор, пока они не станут сюрпризом. Например, поведение может отличаться в новой версии среды исполнения. В конце концов, ее тоже разрабатывают люди и могут быть баги. Чаще же просто кто-то невнимательно прочитал release notes. Разные версии БД на разных окружениях или даже просто потерянный индекс тоже распространённые причины пропуска проблем на следующие окружения. Много кто может рассказать, как увлеченно он сравнивал cipher suites, чтоб наконец уже починить подключении по TLS между двумя компонентами системы. А как много человеко-часов погорело на разных конфигурациях оборудования. Причем возможны отказы и по причине, когда ресурсов слишком много. Известны случаи, когда к отказу приводило неожиданно большое количество ядер процессора, так и оперативной памяти. Сюрприз так сюрприз. Где-то на пути запроса может быть включен протокол HTTP/2 и ваши заголовки окажутся в нижнем регистре. Обслуживающая система может некорректно обработать такой запрос. Очень медленные потребители так же часто становятся источниками проблем занимая пулы, кеши и прочие внутренние структуры данных вашего сервиса. Но сюрпризы не были бы сюрпризами, если бы их можно было перечислить заранее. Они всегда новые. Я рассказываю это к тому, чтоб разбудить ужас в глазах у вас, когда очередной самоуверенный инженер, со словами "я тысячу раз так делал", возьмется провернуть что-то на проде.
P. S.
Все вышеперечисленное, это в арсенал на размышление. Ответственное лицо принимает решение о перечне мер в той или иной ситуации. Как определить будет ли перечень эффективным и адекватным рискам уже отдельная история и не имеет универсального решения. Ровно как не имеет универсального решения задача доставки изменений в прод.