Как стать автором
Обновить
296.02
ГК ЛАНИТ
Ведущая многопрофильная группа ИТ-компаний в РФ

40 релизов в неделю при разработке государственного Amazon или почему Agile is dead

Время на прочтение22 мин
Количество просмотров28K

Что бы вы сказали, если бы вам потребовалось срезать косты вашей команды разработки в два раза? А если бы пришел босс и потребовал делать в два раза больше той же командой? Обычно интуиция нас подводит и мы в ответ на эти вопросы делаем не то, что нужно - в итоге не улучшаем, а только ухудшаем ситуацию. Самое интересное, что значительно улучшить результаты можно потратив относительно небольшие усилия - что же это за бесплатный сыр такой? Как это связано с релизами? От какого важного элемента Agile мы заодно отказываемся? Ответы на эти вопросы я попробую дать в своей статье.

Источник: https://miro.medium.com/max/1400/1*XYIsVmE8U8B3R2HQHrUdRA.jpeg
Источник: https://miro.medium.com/max/1400/1*XYIsVmE8U8B3R2HQHrUdRA.jpeg

Пять лет назад я встречался с разработчиками Amazon, и они делились своим опытом работы. Меня больше всего удивил знаменитый слайд о том, что у них среднее время между релизами - 12 секунд. Я подумал, что тут точно есть какой-то подвох. Мы в то время делали релиз каждый месяц, у нас в среднем между ними была (с учетом хотфиксов - ХФ) неделя-полторы. Мне ответили, что причина в том, что они разбивают свои системы на маленькие сервисы, которые развиваются небольшими автономными командами, и в том, что они стараются выпускать очень маленькие изменения. “Мы можем выпустить изменение одной строчки кода”, - говорили они мне. Я недоумевал: “Зачем маленькие команды? Зачем делать маленькие изменения так часто? Гораздо проще и эффективнее сделать один большой релиз с большим количеством изменений”. Я помню, что ушел, так и не поняв в чем дело. Но мысль, что все не просто так и что стоит в этом разобраться, засела у меня в голове.

С тех пор прошло несколько лет, и я наконец-то могу сказать, что я понял подход “непрерывной поставки” и внедрил его в своих командах, при котором мы делаем очень частые и маленькие релизы. Самое главное - теперь я могу объяснить, зачем это нам надо и почему лучше сделать тысячу релизов по одной строчке, чем одно изменение на тысячу строк. По сути в этой статье я и попробую это сделать.

Примечание. На эту же тему я недавно выступал на AgileDays 2022 (программа, слайды). Выступление я готовил к февралю, поэтому на конференции я рассказывал про то что мы релизим 20 раз в неделю. Однако, с тех пор собачка успела подрасти - мы теперь релизим еще чаще.

Рисунок 1. Мое резюме в терминах частоты релизов
Рисунок 1. Мое резюме в терминах частоты релизов

Я начал работать в компании ЛАНИТ в 2001 году, и до 2009 года меня не занимал вопрос частоты релизов. Потом я несколько лет работал на проекте по созданию http://zakupki.gov.ru, и в то время мы ориентировались на контракт. Если при заключения контракта заказчик решил, что этап будет длиться четыре месяца, то мы четыре месяца разрабатывали и потом выкатывали релиз в опытную или в промышленную эксплуатацию. Потом я работал несколько лет на проекте по созданию ГИС ЖКХ, и тогда пришло осознание, что нужно отвязывать контрактные этапы от релизов на продуктив и переходить на ежемесячные релизы. Основной упор делался на внедрение итеративной разработки в стиле Agile и других различных практик Agile. В 2019 году я понял, что нужно двигаться в сторону DevOps, развития инженерных практик и непрерывной поставки. Сейчас на моем основном проекте ГИС Торги мы делаем около 40 релизов в неделю. В этой статье попробую рассказать об основных практиках, которые нужно внедрить, чтобы перейти на  “непрерывную поставку”, поделюсь своим мнением, зачем это надо и что это вам даст.

Спойлер. По своему опыту мы видим, что по сравнению с итеративными Agile-подходами переход на непрерывную поставку дает улучшения не на какие-то проценты, а буквально в разы. И это очень заметно. Например, мы видим, что мы теперь делаем сходный объем задач, но при этом используем в два-три раза меньшую команду. Высвободившиеся силы мы используем для того, чтобы кардинально улучшить качество нашей работы - повысить доступность системы (на 5-10%), быстрее исправлять инциденты (часы вместо дней) и т.д. Это становится очень заметно и заказчику, и пользователям. Теперь я вас заинтриговал. Читаем дальше!

Немного о ГИС Торги

ГИС Торги - это система, через которую государственные организации обязаны продавать или сдавать в аренду свое имущество. На сегодняшний момент в ней зарегистрировано более 12 тысяч государственных организаций из всех регионов нашей страны. Что там только не продается! Например, можно приобрести самолет или пони. Рекомендую самим полазить и посмотреть. Это очень интересно.

Рисунок 2.  Источник: https://torgi.gov.ru/new/public/lots/lot/21000030590000000006_1/(lotInfo:info)
Рисунок 2.  Источник: https://torgi.gov.ru/new/public/lots/lot/21000030590000000006_1/(lotInfo:info)

ГИС Торги вышла в продуктив с начала 2022 года, и за первые четыре месяца уже было размещено 30 тыс. торгов на 89 млрд рублей. Мы за это время сделали 330 релизов, что в среднем как раз и дает 20 релизов в неделю. За май-июнь мы немного поменяли наши процессы и теперь мы уже релизимся примерно 40 раз в неделю.

Страшная история

Хотите я расскажу страшную историю? Нет? Тогда слушайте! На одном из моих проектов много лет назад случилась описанная ниже история (ну почти такая).

Прихожу я утром на работу, и тут на меня набрасывается руководитель службы сопровождения с огромными глазами и криками “Хьюстон, у нас проблемы!” После того, как он немного пришел в себя, я понял, что одному из VIP-клиентов нужно крайне срочно разместить некоторые данные, но при сохранении возникает системная ошибка (например, превышен размер поля в БД). Ну ничего, задача выглядит не очень сложной, нахожу разработчика, он за пару минут вносит исправления и пушит их в main.

Рисунок 3. Тайминг страшной истории. Фикс ошибки занял 14 часов, даунтайм составил 2 часа, отвлекли кучу народа.
Рисунок 3. Тайминг страшной истории. Фикс ошибки занял 14 часов, даунтайм составил 2 часа, отвлекли кучу народа.

Далее по процедуре мне нужно протестировать доработку и сделать мини-регресс, чтобы убедиться, что ни одна из имеющихся функций не поломалась. К сожалению, оказывается, что один из двух стендов занят регрессионным тестированием очередной версии, которая планируется к выпуску через неделю. Иду смотреть, чем занят второй стенд. На нем также отлаживается какая-то инфраструктурная доработка - меняют настройки кластеров. Менеджер, отвечающий за эту доработку, мне не рад, так как ему хочется завершить свои работы как можно быстрее. Поэтому мне приходится адресовать вопрос руководителю проектов (РП). Мы долго не могли его найти - на письма он не отвечал, звонки сбрасывал. Он был на важном совещании у заказчика, но смог вырваться на 15 минут. Я рассказал ему, что произошло и почему я хочу отжать стенд у инфраструктурной доработки. Вижу, что ему сложно переключиться и вообще сложно принять решение, так как обе задачи достаточно важные. В сердцах он даже сетует, что мы его просим выбрать, какую руку отрезать - левую или правую. В конце концов он решает, что ему будет больнее, если мы не сможем быстро поправить критический дефект VIP-клиента, и разрешает мне забрать стенд.

Фуух… На часах 15:00, прошло каких-то пять часов и стенд наконец-то мой. Но я не могу просто так развернуть на него мой хотфикс, т.к. на стенде изменены многие параметры кластеров. Поэтому мне приходится сначала попросить DevOps-инженера вернуть все настройки назад. Это довольно непростая и кропотливая процедура, которая удается не с первого раза, но в конце концов стенд готов.

В 16:00 мы запускаем процедуру развертывания хотфикса на наш стенд. Система у нас не маленькая, это большой монолит, который разрабатывался 50 разработчиками в течение года. Если ничего не ломается, развертывание занимает около часа. Часто все идет не так гладко - то что-то не компилируется, то скрипт миграции упадет, но сегодня, к счастью, все хорошо, и через час мы готовы к тестированию.

В 17:00 я подключаю двух тестировщиков, которые уже несколько часов ждут, когда им можно будет приступить к тестированию. Сначала все идет хорошо, с доработкой все в порядке. Однако в рамках мини-регресса выясняется, что разработчик забыл внести соответствующие изменения в интеграционные схемы данных, поэтому после внесения нужных данных через UI при отправке сообщения контрагентам мы получаем системную ошибку. Я нахожу разработчика, бью его по почкам (шутка), он быстро вносит нужные изменения, пушит в main, и цикл повторяется снова.

Наконец-то в 20:00 мы имеем готовый протестированный дистрибутив хотфикса! К сожалению, мы не умеем выпускать в продуктив наживую без остановки системы, поэтому каждый выпуск мы делаем только по согласованию с заказчиком в ночные часы. Мы связываемся с заказчиком, согласовываем регламентные работы (РР) на период с 22:00 до 24:00, готовим инструкции для дежурной группы и ждем ночи.

В 22:00 дежурная группа закрывает систему на РР, разворачивает хотфикс, проводит смоук-тестирование и открывает систему. Когда разворачиваем ежемесячный релиз, то развертывание занимает гораздо больше времени, иногда что-то идет не по плану, но в данном случае все прошло успешно. В 24:00 мы открываемся.

Мы сделали это! Все хорошо! … или… А так ли все хорошо? Если подумать, то мы получили два часа недоступности системы (пусть ночью, но все же), мы оторвали от важных дел заказчика, РП. Мы затормозили важную инфраструктурную доработку, отвлекли тестировщиков от других задач и т.п. Эйфория у меня достаточно быстро сменилась нарастающим чувством неудовлетворенности и бессмысленности всего происходящего. Мне пришлось как белка в колесе крутиться целый день для того, чтобы выпустить в продуктив буквально две строчки кода!

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

Идеальный вариант

После некоторых раздумий воображение мне нарисовало следующую идеальную картину. В 10:00 разработчик (я) прихожу на работу и вижу алерт от системы мониторинга о системной ошибке в логах. Я понимаю, что проблема связана с размером поля в БД, тут же вношу изменения в код, добавляю тесты и пушу в main. Это занимает 10-15 минут. Автоматически запускается пайплайн развертывания, в рамках которого в специальной песочнице прогоняются модульные тесты, интеграционные тесты, на специальных стендах прогоняются end-to-end тесты, нагрузочные тесты и т.п. Через несколько минут вижу, что пайплайн упал с ошибкой - не отправляется сообщение контрагенту через интеграцию из-за размера поля. Я быстренько вношу исправление в схемы интеграции, пушу в main и опять жду, пока не завершится пайплайн. На этот раз все отлично, и сборка автоматически наживую без даунтайма разворачивается на продуктиве.

Рисунок 4. Тайминг идеального варианта. Фикс ошибки занял 20-30 минут, без даунтайма, со всем справился сам разработчик.
Рисунок 4. Тайминг идеального варианта. Фикс ошибки занял 20-30 минут, без даунтайма, со всем справился сам разработчик.

Что мы имеем? У нас не возникло никакого даунтайма системы, время исправления оказалось примерно 20-30 минут. Самое приятное, что ошибку исправил сам разработчик без привлечения РП, тестировщиков, DevOps-инженеров и т.п.

Вот примерно так мы и работаем сейчас на проекте ГИС Торги. Дальше я расскажу о том, какие основные инженерные практики надо внедрить для того, чтобы работать так же.

Примечание: В реальности, наверное, нерационально делать end-to-end тест, который отловил бы проблему в интеграции  (см. раздел про АТ), поэтому проблема скорее всего не была бы выявлена в рамках запуска пайплайна, и сборка ушла бы в продуктив. Однако разработчик вскоре увидел бы в логах ошибку с интеграцией, исправил бы ее, дописал соответствующий модульный тест и развернул окончательный фикс в продуктив.

Практика №1. Уменьшение размера задач

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

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

Сейчас мы договорились с аналитиками, что они прорабатывают концепцию решения с заказчиком, но детальные спецификации не пишут. Вместо этого мы “нарезаем” решение на большое количество маленьких задач. Задачи должны быть не больше, чем на два-три дня разработки, но при этом обязательно понятны пользователю и заказчику. Таким образом, для нас делать задачи по типу “запилить таблицы в БД”, “запилить UI для того-то” - это антипаттерн. При этом мы стараемся отказываться от излишней детализации и в рамках задачи обычно прописываем только самое необходимое - структуры данных, макеты экранов, сценарий действия пользователя и т.п. 

Например, раньше для того, чтобы реализовать реестр лотов (лот - это описание того, что хотят продать), мы действовали следующим образом. Мы поручали задачу аналитику, который сначала писал детальную спецификацию в течение месяца. Затем эта спецификация передавалась в команду разработки, которая 2-3 месяца разрабатывала код, потом месяц-другой все это тестировалось и стабилизировалось. Затем, спустя  полгода, мы торжественно релизили реестр лотов на продуктив и открывали его пользователю. Сейчас мы действуем по-другому. Мы нарезаем эту тему на 30-40 маленьких задач, делаем их постепенно и сразу релизим в продуктив по мере готовности.

Рисунок 5. Разбиваем функциональность реестра лотов на маленькие задачи и реализуем их постепенно в соответствии с приоритетами.
Рисунок 5. Разбиваем функциональность реестра лотов на маленькие задачи и реализуем их постепенно в соответствии с приоритетами.

Сначала мы делали простое отображение списка лотов в порядке их создания. Потом добавляли возможность фильтрации по базовым атрибутам. Затем реализовывали поиск по категориям (к слову, поиск по категориям - это не простая тема, которая сама по себе вылилась в пяток задач). В какой-то момент у нас одна из команд закончила работать над реестром жалоб, и мы добавили возможность поиска по признаку наличия жалоб в реестре лотов. В какой-то момент мы захотели добавить “карусельки” из картинок по лотам, которые загружает пользователь в своем личном кабинете. Потом мы добавили разные виды сортировок. После того как у нас появилась отправка информации на электронные торговые площадки, мы  стали отображать информацию об этом и т.д.

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

Практика №2. Фича-флаги

Фича-флаги - это просто if then в коде, который активирует или деактивирует функциональность в зависимости от настройки. Например, мы хотим деактивировать целый раздел сайта, пока он не реализован. Мы около месяца разрабатывали реестр жалоб. Все это время раздел выпускался на продуктив, но в деактивированном виде. В какой-то момент у нас появилась возможность фильтрации лотов по признаку наличия жалоб, но она не показывалась пользователям так же, как и другие функции этого раздела. С другой стороны, на нужных стендах мы могли включать реестр жалоб, например, на тестовых стендах или на демо-стендах, если мы хотели показать реестр заказчику или экспертам.

Рисунок 6. Фича-флаги помогают активировать/деактивировать функциональность на стенде. Например, пока мы несколько месяцев разрабатывали реестр жалоб, то возможность фильтрации по жалобам в лотах была деактивирована на продуктиве. Фича-флаги также позволяют проверять гипотезы и технические решения.
Рисунок 6. Фича-флаги помогают активировать/деактивировать функциональность на стенде. Например, пока мы несколько месяцев разрабатывали реестр жалоб, то возможность фильтрации по жалобам в лотах была деактивирована на продуктиве. Фича-флаги также позволяют проверять гипотезы и технические решения.

Фича-флаги очень удобны для разработки экспериментальных функций. Например, мы изначально реализовали отображение “каруселей” из картинок по лоту просто из оригинальных картинок, загруженных пользователями. Однако картинки могли быть большими, это приводило к тому, что для отображения страницы реестра лотов пользователю приходилось загружать до 30 Мб данных. У нас возникла идея, почему бы не использовать плагин для Nginx, который на лету генерировал бы эти картинки и кешировал их у себя под ногами. Мы оперативно реализовали это решение, выпустили на продуктив и включили. Однако мы сразу  увидели резкий всплеск загрузки CPU на серверах Nginx, наш главный DevOps-инженер забраковал такое решение и отключил его.  Может быть, кто-то скажет, что вот так тестировать на продуктиве - это плохо и лучше сделать полноценное нагрузочное тестирование, но это заняло бы очень много времени, и неизвестно, насколько надежным был бы результат. Вместо этого мы потратили 30 минут на создание фича-флага, который позволил нам протестировать решение и получить абсолютно реальную статистику.

Когда вы начнете использовать фича-флаги, то поймете, что они  дают много дополнительных возможностей. Например, они позволяют делать Blue/Green Deployment для повышения доступности. Они позволяют использовать канареечное тестирование, что делает ваши релизы более безопасными и снижает затраты на тестирование.

Практика №3. Автоматизированное тестирование

Из книг Роберта “Боба” Мартина мы знаем, что вы не можете считать себя профессиональным разработчиком, если не используете автоматизированное тестирование (далее АТ) и даже TDD (протестируй себя). Эта практика является сердцем “непрерывной поставки”, но, к сожалению, является также самой сложной для внедрения. В замечательной книге Software Engineering at Google говорится о том, как практику автоматизированного тестирования стали внедрять в Google в 2004-2005 годах и столкнулись с очень большим сопротивлением. Сотрудники компании применяли разные креативные подходы для того, чтобы переломить ситуацию (например, в рамках инициативы TotT вешали постеры с советами по АТ над писсуарами в туалетах). В итоге они признали, что один из действенных подходов, который позволил переломить ситуацию, - обучать вновь нанятых сотрудников практикам АТ. С учетом того, что Google бурно рос в то время, то количество новичков быстро превысило количество старичков, и противники АТ быстро остались в меньшинстве.

Источник: https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSSoeFgeMeiae15LcaAGoHTt6umEiWCln284Q&usqp=CAU
Источник: https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSSoeFgeMeiae15LcaAGoHTt6umEiWCln284Q&usqp=CAU

К сожалению, многие компании, внедряющие АТ, совершают одну и ту же ошибку. Они начинают внедрение АТ с создания end-to-end тестов. Опыт компаний, где АТ прижился, говорит об обратном - подавляющее количество тестов должны быть модульными и только совсем малая их часть может быть end-to-end тестами (речь идет о т.н. пирамиде автоматизированного тестирования). У нас не все команды используют end-to-end тесты и показывают хорошие результаты.

Если при написании модульных и интеграционных тестов все более-менее понятно (их пишут сами разработчики и запускают при каждом коммите в систему контроля версий, и работают они быстро и надежно), то с end-to-end тестами возникают сплошные проблемы. Во-первых, end-to-end тесты очень медленные. Если скорость работы модульных тестов у нас примерно 150 тестов/сек, то у end-to-end тестов - примерно 2 теста/мин. Во-вторых, end-to-end тесты очень хрупкие и довольно часто ломаются не по причине багов. Это не только мы заметили, у Google такие же проблемы

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

В четвертых, многие команды совершают ошибку, когда пытаются запускать end-to-end тесты раз в две недели, раз в неделю, перед релизом и т.п. Тут работает чисто психологический момент. Тесты требуют постоянных усилий на их актуализацию. Это занятие не очень популярное у разработчиков, поэтому если если тесты упали, то вряд ли кто-то бросит свои дела и побежит диагностировать, в чем дело. Поэтому, если менеджер не будет следить все время за тестами и не будет бегать за разработчиками, заставлять их актуализировать тесты, то быстро оказывается, что они почти всегда сломаны и никто на них не обращает внимание. Мы попробовали все и решили, что единственный вариант, когда end-to-end тесты оказываются рабочими, если запускать их при каждом мерже в main. Если мержи тоже маленькие - не более пары дней работы одного разработчика - то сразу видно кто сломал тесты и кто должен их сразу же починить. 

Практика №4. Автоматизация инфраструктуры

Однажды я работал с продвинутой командой, которая делала частые и быстрые сборки, писала много АТ и т.п. К сожалению,  выпуск в продуктив был возможен только по согласованию с заказчиком и только в специальные регламентные окна силами специальной команды DevOps-инженеров. Как-то раз мы выпустили релиз ночью и утром обнаружили критическую проблему. К счастью, проблему можно было легко исправить простым перезапуском одного из модулей (что-то связанное с кешированием). Мне потребовалось 5 часов и 30 писем для того, чтобы договориться и сделать перезапуск! Что интересно, DevOps- инженер перезапустил не тот модуль и не в тот момент, когда было нужно. Тогда я задумался, что такие специализированные команды, которые забирают на себя право вносить изменения на прод, встречаются довольно часто, они призваны  сделать изменения более безопасными. Однако в результате мы чаще всего получаем обратный эффект. Никакая специализированная команда не может сравниться в компетентности, мотивации и заинтересованности с командой, непосредственно отвечающей за разработку системы.

Мы поменяли ситуацию кардинально. Я где-то читал, что в компаниях, где практикуют DevOps, ходит шутка, что DevOps-инженер - это единственная роль, которая ничего не делает руками на продуктиве и, по-хорошему, у него нужно забрать все права. Действительно, в нашем подходе DevOps-инженер занимается разработкой и настройкой платформенных решений и API, которые потом позволяют разработчикам делать доработки в том числе влияющие на инфраструктуру. Например, если в рамках какой-то функциональной задачи нужно сделать новый топик в Kafka, то это делается разработчиком как изменение в коде, которое попадает на продуктив в рамках стандартной процедуры развертывания - сам топик создается либо при развертывании, либо при первом обращении.

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

Часто встречается еще одна проблема. Бывает так, что доступ к логам приложения возможен только через DevOps-инженеров по запросу. Кажется, мелочь, и доступ работает достаточно быстро 90% времени, но все равно это ограничивает. Если нужно сделать мало-мальски нестандартный анализ или не совсем понимаешь, что тебе нужно, то процесс превращается в мучение. Поэтому мы предоставляем персонализированный доступ разработчиков к логам, а также используем мониторинговую инфраструктуру, которая кидает алерты в случае возникновения каких-то известных проблем.

Рекомендую почитать книги Джина Кима, где очень большой фокус делается на улучшении проектной инфраструктуры (например, Проект Феникс или The Unicorn Project). Много информации можно почерпнуть из книг Google по SRE.

Практика №5. CI/CD пайплайн

Если мы протестировали какую-нибудь доработку, то прежде, чем выпустить ее в продуктив, нам нужно снять ряд рисков. Нам нужно убедиться, что ничего не сломается из того, что работало раньше (по крайней мере, из основных функций). Если требуются миграции данных, они будут выполнены корректно, система не упадет под нагрузкой и будет корректно работать в отказоустойчивой конфигурации. Хорошая новость в том, что если мы уже инвестировали в практики, о которых я говорил выше, то будет достаточно легко автоматизировать все необходимые для релиза тесты. 

Эта практика называется CI/CD-пайплайн. Суть ее заключается в том, что по триггеру или после ручного запуска мы создаем дистрибутив и прогоняем на нем всю необходимую последовательность тестов. Если все тесты завершились удачно, то дистрибутив считается корректным и его можно развернуть на продуктив. Если же что-то пошло не так, то мы забраковываем дистрибутив, вносим исправление и запускаем новый пайплайн. 

Так же, как и в случае с АТ, если вы сделаете пайплайн и будете его запускать очень редко, например, раз в две недели, то отдача от него будет минимальной. Может быть, даже наоборот - вы получите только больше проблем, потому что так редко проще прогонять все тесты руками.  

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

Наилучший эффект от CI/CD-пайплайна достигается, если он запускается автоматически при каждом коммите каждого разработчика (если коммиты сами по себе тоже частые). В этом случае, если что-то поломалось, то диагностика тривиальна, а исправление, как правило, занимает очень мало времени.

На ГИС Торги по каждому коммиту в main мы запускаем пайплайн, в рамках которого мы делаем сборку, запускаем модульные и интеграционные тесты, разворачиваем сборку на тестовое окружение, запускаем на нем end-to-end тесты, разворачиваем сборку на стенд НТ и запускаем там нагрузочные тесты.  В дальнейшем, как правило, раз в день или при необходимости лид микросервиса может сделать релиз успешной сборки на продуктив. По сути это наша реализация подхода “непрерывная поставка” (Continuous Delivery, CD). Такой подход нам позволял делать где-то 20 релизов в неделю.

Рисунок 7. По каждому коммиту в main запускается пайплайн в GitLab
Рисунок 7. По каждому коммиту в main запускается пайплайн в GitLab

Сейчас мы уже экспериментируем с тем, чтобы каждую успешную сборку выпускать сразу в продуктив. В этом случае упрощается диагностика проблем, если они возникают, и значительно упрощается управление зависимостями между задачами. Это уже похоже на подход, который называется “непрерывное развертывание” (Continuous Deployment). На данный момент такой подход позволяет нам уже делать по 40 релизов в неделю.

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

Общая схема

Как правило, новая система изначально разрабатывается как монолитное приложение. Это абсолютно разумный и оправданный подход, т.к. монолитное приложение - это самое простое и надежное, что можно представить. Однако с течением времени, если система бурно развивается, чтобы вы не делали, но CI/CD-пайплайн будет отрабатывать все дольше и дольше, пока не перешагнет некоторый разумный предел, после которого уже им будет нельзя пользоваться на каждый коммит (уже при 20 минутах на пайплайн разработчик перестанет дожидаться его завершения, и вы будете часто видеть ситуации, когда пайплайн сломался, но никто на это не обращает внимание). Для того, чтобы кардинально решить эту проблему, нужно разрезать вашу систему на независимые модули, т.н. микросервисы. Мы пошли по такому пути, работая над ГИС Торги.

Рисунок 8. Изначально ГИС Торги разрабатывался как монолит, но с течением времени это стало ограничивающим фактором - CI/CD-пайплайн стал выполняться очень долго, различные доработки начали тормозить друг друга. Поэтому пришлось разбить систему на независимые микросервисы, которые могут разрабатываться и релизиться независимо.
Рисунок 8. Изначально ГИС Торги разрабатывался как монолит, но с течением времени это стало ограничивающим фактором - CI/CD-пайплайн стал выполняться очень долго, различные доработки начали тормозить друг друга. Поэтому пришлось разбить систему на независимые микросервисы, которые могут разрабатываться и релизиться независимо.

Мы разбили ГИС Торги на десять с лишним основных микросервисов. Каждый микросерис - это независимое приложение со своим UI, логикой и хранилищами. Каждый микросервис имеет свой репозиторий, АТ, CI/CD-пайплайн, и может выпускаться независимо от других микросервисов. Микросервисы - это сама по себе очень большая и сложная тема, достойная отдельной статьи. Тут очень много подводных камней и своих хаков.

Каждый день по утрам мы проводим мини-регрессионное тестирование.  Некоторое время назад мы ориентировались на регресс - все доработки выпускали лиды микросервисов по его итогам. Теперь же мы уже делаем релизы в основном сразу по готовности каждой задачи. Получается так, что каждый день выпускаются доработки по нескольким микросервисам. В совокупности сейчас это дает 6-10 релизов в день в рамках всей системы.

Зачем же все это надо?

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

Эта мысль так или иначе встречается очень часто. Например, это одна из основных идей книги Ф. Брукса “Мифический человеко-месяц”. 

Выше я приводил много примеров, связанных с тестированием: 

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

  • если задача большая, то сложно диагностировать ее причину и часто оказывается, что исправлять должен не тот человек, о котором думали сначала - отсюда дополнительные затраты;

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

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

Если команда переходит на режим “непрерывной поставки”, с очень частыми релизами, то затраты на менеджмент резко снижаются. Например, отпадает необходимость в спринтах и во всех связанных с ними “ритуалах” - не нужно планировать спринт, не нужно делать отчеты о статусе спринта, не нужно разбираться с проблемами, если какая-то задача, которая была запланирована в спринт, не может быть сделана к плановой дате и т.д. 

Д. Андерсен еще в 2014 году писал о том, что итерации, на которых во многом основан Agile, - это тупиковый подход (см. тут и тут). Если мы отказываемся от спринтов, то что же Agile is dead?

Рисунок 9. С ростом размера релиза затраты на разработку функции растут экспоненциально и являются доминирующими по сравнению с инвестициями в инфраструктуру для частых релизов. Общий график затрат на разработку функции имеет оптимум (по нашему опыту) в крайней левой части графика. Поэтому уменьшение размера релиза - это очень выгодная стратегия для сокращения затрат. 
Рисунок 9. С ростом размера релиза затраты на разработку функции растут экспоненциально и являются доминирующими по сравнению с инвестициями в инфраструктуру для частых релизов. Общий график затрат на разработку функции имеет оптимум (по нашему опыту) в крайней левой части графика. Поэтому уменьшение размера релиза - это очень выгодная стратегия для сокращения затрат. 

Таким образом, график изменения затрат на разработку задачи в зависимости от размера релиза имеет экспоненциальный вид (например, см. тут принцип B11). По нашему опыту, инвестиции, которые нужно сделать для того, чтобы выпускаться часто, - минимальные. Доминирующим фактором являются затраты на разработку. Поэтому общий график затрат на задачу будет иметь форму клюшки, а оптимальный размер релиза будет находиться в районе минимальных размеров релиза. Почему так? Дело в том, что все, о чем рассказывается в данной статье, - это никакая не ракетная наука. Ведущие технологические компании занялись этим 10-15 лет назад, и за это время прошли немалый путь - набили шишки, разработали различные инструменты, написали книги, сделали обучающие курсы и т.д. Сейчас для того, чтобы поднять всю необходимую инфраструктуру, чтобы релизиться несколько раз в день, не требуется больших затрат.

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

Уменьшение размера релиза позволяет существенно (экспоненциально) снизить производственные затраты. Однако это еще не все плюшки, которые мы получаем. Уменьшение размера релиза позволяет не только делать задачи значительно дешевле, но и дает заказчику возможность  заниматься более важными задачами. Дело в том, что частые релизы позволяют заказчику и всем заинтересованным лицам гораздо лучше понимать, где мы находимся, т.к. они всегда видят, все что сделано, на продуктиве или на демо-стендах. Частые релизы позволяют нам делать MVP и тем самым быстро проверять свои гипотезы. В случае, если у заказчика изменяются приоритеты, то производственная команда может очень быстро изменить направление своего движения и при этом понести адекватные накладные расходы.

Получается, что уменьшение размера релиза - это отличный способ улучшать экономику проекта/команды с относительно минимальными затратами? В принципе да. Дело в том, что экспоненциальный характер кривой затрат говорит нам о том, что если ваша команда находится в правой части кривой (т.е. релизы очень большие и очень редкие, например, лишь несколько раз в год), то тогда даже небольшое уменьшение размера релиза даст значительное улучшение. Разница между командами, которые находятся в крайних положениях может быть очень заметна (буквально в разы). Поэтому, Д. Рейнертсен в своей замечательной книге советовал всегда начинать с уменьшения размера релиза. 

К сожалению, я до сих пор очень редко вижу, чтобы этот подход применялся на практике. Если спросить разработчиков, используют ли они практику автоматизированного тестирования, то окажется, что лишь очень малый процент делают это (протестируй себя). Если спросить типичного менеджера, что он будет делать, если нужно сократить расходы на команду? Он скорее всего ответит, что пойдет искать более дешевого подрядчика. Если спросить: у тебя теперь в два раза больше работы, что будешь делать? Он скорее всего ответит, что пойдет просить увеличения команды в два раза. Очевидно, что с таким подходом мы наоборот убиваем экономику наших проектов. Хотя могли бы получить лучший результат, просто уменьшая размер релиза! 

Заключение

В рамках статьи были приведены основные инженерные практики, которые нужно внедрить для того, чтобы перейти в режим “непрерывной” поставки. Основные практики включают в себя уменьшение размера задач, фича-флаги, автоматизацию тестирования, автоматизацию инфраструктуры, CI/CD-пайплайн. При масштабировании системы в какой-то момент потребуется переходить на микросервисы. 

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

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

Теги:
Хабы:
Всего голосов 131: ↑128 и ↓3+125
Комментарии36

Публикации

Информация

Сайт
lanit.ru
Дата регистрации
Дата основания
Численность
свыше 10 000 человек
Местоположение
Россия
Представитель
katjevl