Наша команда отвечает за эксплуатацию и развитие большого корпоративного продукта.
В начале 2017 года, передохнув от крупного внедрения и перечитав "lessons learned", мы твердо решили пересмотреть процесс разработки и доставки нашего приложения. Нас беспокоила низкая скорость и качество доставки, не позволяя нам обеспечивать уровень сервиса, который от нас ожидают заказчики.
Пора было переходить от слов к делу — менять процессы.
В этой статье будет кратко рассказано о том с чего мы начинали, что делали, какая ситуация сейчас, с какими трудностями столкнулись, что пришлось пока оставить за скобками, что ещё планируем делать.
Начало
Немного о системе
Приложение представляет собой классический пример монолитного энтерпрайз приложения "архитектурного разлива 2000-х годов":
- Эксплуатируется и развивается более 15 лет.
- Представляет собой набор из полутора десятков приложений WinForms, windows services и ASP .Net, привязанных к единой базе данных MS SQL.
- Размер кодовой базы: ~1MLOC для C#, ~9000 объектов базы данных. Значительная часть бизнес-логики исполняется на стороне базы данных.
- Приложение состоит из ~250+ solutions для создания win/web клента (одно решение на группу связанных форм). Это наследие предыдущего процесса разработки и архитектуры клиента.
- Приложение поддерживает несколько типов процессов (клиентов) путем изменения внутренней конфигурации: настройки процессов, полномочий, гибких полей, итд, в таблицах конфигурации базы данных системы. При этом кодовая база приложения — единая для всех клиентов.
- Приложение развернуто и поддерживается на 25+ площадках (каждая площадка — независимый экземпляр системы) и обслуживает в сумме несколько тысяч конечных пользователей в разных часовых поясах.
Процесс доставки до трансформации
- разработка и сборка готового приложения и его компонентов проводится подрядной организацией.
- код хранился на стороне подрядной организации (локальная версия MS TFS). Заказчику код передается на ежемесячной основе в виде архива текущей версии основной ветки репозитория.
- поставка осуществлялась путем поставки "дельта обновлений": для приложения (набор dll, exe, и т.д.) и компонентов базы данных (набор sql скриптов create/alter). Сборку приложения и подготовку "дельта пакетов" выполнял подрядчик.
- процесс развертывания поддерживался транспортной системой, применение изменений происходил автоматически.
Доставка проводится в рамках ежемесячных релизов (как он у нас устроен я рассказывал раньше здесь)
Существовавшие проблемы
Отсутствие контроля
- несмотря на формальное владение кодом, фактическую сборку приложения силами заказчика осуществить было невозможно.
- как следствие — невозможно убедиться в работоспособности кода, передаваемого заказчику.
- изменений в коде — не прозрачны для заказчика. Невозможно сопоставить затребованные и фактические изменения в продукте.
- анализ кода затруднён для SQL, и невозможен для C# компонентов
Трудоемкость и ошибки
- подготовка "дельта пакетов" — трудоёмкая процедура для разработки, источник ошибок и определенных проектных затрат.
- развертывание приложения из набора "дельта пакетов" требует отслеживания порядка пакетов. Ошибка нарушения порядка пакетов — основная проблема развертывания и источник значительной части инцидентов.
- регулярно возникают регрессии: ошибки, которые, вроде бы уже чинили и выкатывали исправления в продуктив появились опять.
Ограничения
- возможность восстановить состояние системы на момент в прошлом (откатить изменения) — фактически отсутствует.
- возможность эффективно масштабировать ресурсы разработки и раннего тестирования за счет привлечения сотрудников заказчика — практически отсутствует.
Ожидаемые результаты
В начале проекта мы ставили перед собой очевидные цели, призванные решить обозначенные выше проблемы.
- Перенести репозиторий кода под контроль заказчика
- Перенести процесс сборки приложения на сторону заказчика
- Модифицировать процесс распространения изменений отказавшись от "дельта изменений" в пользу полного обновления
Дополнительно, используя решения полученные при достижении первых двух целей, мы рассчитывали:
- Повысить техническое качество получаемых решений за счёт контроля кода
- Повысить вовлечённость и удобство тестирования за счёт предоставления self-service развертывания.
Этапы большого пути
Анализ текущего состояния процессов разработки
Первый шаг: проанализировать существующий процесс разработки подрядчика. Это помогло спланировать изменения так, чтобы, по возможности, не прерывать работу.
К сожалению, знакомство с процессом разработки показало, что в понимании IT отрасли настоящего времени — процесс отсутствовал.
- Код базы данных и бизнес логики к ней не поддерживался в репозитории в актуальном состоянии. Основная причина: отсутствие инструментов, реализующих сборку из кода в репозитории и развертывание результата. А значит, код в репозитории — всего лишь документация.
- "Реальная" версия кода базы данных — в общей "базе данных разработки", на которой работают десятки разработчиков.
- Код клиентского приложения (C#, ASP.NET) поддерживался в репозитории, но качество и своевременность комитов — не гарантировалась.
- Сборка компонент (не всего приложения) проводилась на станциях разработчиков. Не совсем понятно, как обновлялся код перед сборкой. Собранный компонент выкладывался на общую разделяемую папку. Оттуда же формировался "дельта пакет" для заказчика.
- Полное отсутствие практики ведения веток разработки. По косвенным признакам мы подозревали это давно — но после погружение в процесс все стало очевидно.
Переход на новый репозиторий и систему контроля версий
Зависимость от платформ MS и корпоративные стандарты предопределяли выбор среды для разработки — Team Foundation Server.
Однако, к моменту когда мы непосредственно начинали проект (апрель 2017) как раз вышла версия Visual Studio Team Services. Продукт показался очень интересным, был обозначен как стратегическое направление для MS, предлагал репозитории git, сборку и развертывание для on-prem и cloud.
Корпоративный on-prem TFS отставал по версии и функционалу от VSTS, миграция на новую версию была только в процессе обсуждения. Мы не хотели ждать. Мы решили сразу переходить на VSTS, поскольку это снижало наши накладные расходы на поддержку платформы и предоставляло нам полный контроль над тем как и что мы делаем.
На момент начала изменений у команды разработки был опыт работы с TFSVC, код приложения хранился в таком репозитории. С другой стороны, GIT давно фактически стал стандартом для ИТ сообщества — заказчик и сторонние консультанты рекомендовали перейти на эту систему.
Нам хотелось чтобы команда разработки была вовлечена в принятие решения о новой системе контроля версий, и сделала осознанный выбор.
Мы развернули в VSTS два проекта с разными репозиториями — TFSVC и GIT. Были определены набор сценариев, который предлагалось протестировать и оценить удобство работы в каждой из систем.
Среди оцениваемых сценариев были:
- Созданиеи слияние веток
- Организация совместной работы (над одной или разными ветками)
- Операции над цепочками изменений (фиксация, отмена)
- Интеграция сторонних изменений
- Возможность продолжать работу при недоступности сервера.
В результате, ожидаемо, был выбран GIT, и пока никто об этом не жалел.
В качестве процесса мы начали использовать GitFlow. Этот процесс предоставлял достаточно контроля за изменениями и позволял проводить доставку релизами, как мы уже привыкли.
- Мы защитили ветку develop политикой, которая требовала чтобы все изменения шли через пулл-реквесты.
- Мы стараемся придерживаться практики "один тикет — один пулл-реквкст". Изменения из разных тикетов никогда не объединяются в рамках одного изменения. Мы стараемся провести все возможное тестирования ещё на feature ветке, чтобы избежать ситуации с исправлениями в последующих пулл-реквкстах.
- При слиянии в develop все изменения объединяются в один комит (squash).
- Релизные ветки создаются из develop.
- Принеобходимости, в релизную ветку можно добавить последние изменения выборочно (cherry-pick) или все (rebase). Исправление непосредственно в релизной ветке мы не проводим.
- После развертывания на продуктиве последнего релиза он переходит в master через push force (это право имеют только несколько человек)
Автоматизация сборки продукта
Приложение представляло собой большое количество сборок, сотни решений. Как выяснилось в ходе аудита процессов, все это собиралось отдельно и "вручную".
Мы решили на первом этапе не переделывать все "с нуля" (чтобы не останавливать существующую доставку), а "обернуть" сборку в набор сценариев msbuild — один сценарий на компонент.
Таким образом мы достаточно быстро получили сценарии, которые проводили все необходимые промежуточные артефакты, и в конце — готовый продукт.
Отдельная история — проект базы данных. К сожалению, система содержит несколько CLR компонентов, которые были не очень хорошо структурированы. Зависимости не позволяют простой развернуть базу с содержимым. На данный момент это решается pre-deployment скриптом.
Кроме того, из-за неровного системного ландшафта (на разных точках были установлены SQL Server версий 2008 и 2014) пришлось организовывать сборку проекта базы для .Net версий 2.0 и 4.0.
После того как все сценарии были готовы и протестированы, они были использованы в build сценарии VSTS.
Непосредственно перед началом сборки версии всех продуктов обновлялись на общий стандартный номер, включающий сквозной номер билда. Такой же номер сохранялся в скрипте post-deployment. Таким образом все компоненты: база данных и все клиентские приложения, — выходили согласованными и одинаково пронумерованными.
Развертывания на тестовый стенд
После того, как первичной версия процесса сборки была завершена, мы приступили к подготовке сценария рпзвертывания.
Ожидаемо, больше всего хлопот доставила база данных.
Развертывание "поверх" копии реальной базы показало множество конфликтов между сборкой и состоянием реальных систем:
- Несогласованные версии в GIT и в реальной системе
- Схемы БД, владеемые пользователями, которых планировалось удалять.
Стабилизация процесса разработки
Об этом, конечно, странно говорить и, тем более — писать здесь, но самой серьезным изменением для разработчиков стало введение принцип "если этого нет в git — этого не существует". Раньше код комитился "для отчётности перед заказчиком". Теперь — без этого невозможно доставить ничего.
Сложнее всего было с кодом базы данных. После перехода на развертывание базы данных из репозитория, через сборку и развертывание с помощью sqlpackage, "delta" подход был заменен подходом "desired state". Пакеты уходили в прошлое, все должно было деплоиться автоматически.
Но! До момента полного перехода на новый процесс развертывания — все ещё нужно было доставлять изменения. И делать это нужно было по старинке -"дельта обновлениями".
Перед нами встала задача обеспечить полную и постоянную согласованность состояния системы при доставке дельта пакетами, и содержания репозитория.
Для этого мы организовали следующий процесс:
- Регулярно код из репозитория собирался и развертывания в пустую "модельную" базу.
- На основании "модельной" базы готовился специальный автотест. Для каждого объекта "модельной" БД вычислялись контрольные суммы. Автотест содержит в себе все эти контрольные суммы и при запуске вычисляет контрольные суммы соответствующих объектов "проверяемой" БД. Любое расхождение в составе объектов или их контрольных суммах приводит к падению теста.
- "Падающий" тест автоматически запрещал перенос пакетов из тестового окружения дальше по ландшафту. Такая интеграция уже была реализовано в предыдущей транспортной системе.
Таки образом, с помощью автоматического контроля удалось относительно быстро привести код базы данных продукта в git а актуальному состоянию и поддерживать его без дополнительных усилий со стороны проектной команды. Одновременно, разработчики стали привыкать к необходимости корректно и своевременно комитить код в репозиторий.
Развертывание продукта на окружения интеграционных тестов
После завершения предыдущего этапа мы перешли непосредственно к развертыванию приложения на тестовом окружении. Мы полностью прекратили применение дельта-пакетов к тестовым системам и перешли на автоматическое развертывание средствами VSTS.
Именно с этого момента вся команда начала получать первые плоды от затраченных ранее усилий: развертывание проходило без каких-либо дополнительных усилий. Закомиченный код автоматически собирался, развертывания и тестировался.
К сожалению, как мы поняли потом, проведенное "выравнивание репозитория" привело к тому что у нас появилась версия стабильно поддерживаемая версия "develop", но версия "production" была все также недоступна. И поэтому дальше тестового окружения — на QAS и PRD идти было не с чем.
Код приложения на стороне БД можно было сравнить с продуктивным и понять различия. Сравнивать клиентские приложения было не с чем — существовала только актуальная продуктивная версия в виде набора исполняемых файлов, а из чего они были собраны достоверно сказать было нельзя.
Тестирование продукта как результата автоматической сборки
После изменения подхода к сборке продукт пришлось подверкнуть большому регрессионному тестированию. Нужно было убедиться, что приложение работает и ничего не потеряно.
При тестировании как раз проще получилось с функционалом, размещенным на стороне БД. К счатью у нас был в наработан значительный набор автотестов, покрывающий критические области.
А вот для С# тестов не было — поэтому проверялось все руками. Это был значительный объем работы, и проверка заняла какое-то время.
"Прыжок веры" — пилотное развертывание на продуктив
Несмотря на проведенное тестирование, развертывать на продуктиве в первый раз было страшно.
Нам повезло — у нас как раз был запланирован очередное развертывание системы на новой площадке. И мы решили использовать этот шанс для пилотного развертывания.
Пользователи не видели, возможные ошибкиновой сборки было просто исправить, реальной продуктивной работы еще не началось.
Мы развернули систему, и несколько недель она была в режиме пред-продуктивного использования (низкая нагрузка, определенный патерн использования, который в продуктиве можно пропустить). За это время вскрылось несколько пропущенных при тестировании дефектов. Они исправялись по мере нахождения, и новая версия сразу же выкатывалась для пповерки.
После официально запуска и неделю пос-стартовой поддержки мы объявили, что это первый экземпляр собранный и доставленный "по новому".
Эта версия сборки стала первой стабильной версией ветки master, была обвешана праздничными тагами "fisrt_deployment" (значков с хэшем комита мы, правда, не заказали).
Масштабирование развертывания на весь продуктивный ландшафт
Как говорил Джеймс Бонд: "второй раз — гораздо проще". После успеха пилотного развертывания мы довольно быстро подключили остальные экземпляры систем аналогичного типа.
Но система имеет несколько типов использования — одна функциональность может использоваться для одного типа, и не использоваться в других случаях. Соответственно, функциональность проверенная на внедрении первого типа не обязательно гарантировала успех для других случаев.
Для проверки функционала оставшихся типов использования мы начали использовать активные проекты, которые находились в разработке. Идея была сходной и первым развертыванием — мы начали использовать автоматические сборки, "подсовывая" их пользователям вместе с проектной функциональностью. Таким образом, пользователи, работая с "проектной" версией продукта заодно проверяли и старую функциональность.
Само по себе масштабирование выявило неожиданные технические проблемы:
Неоднородный системный ландшафт
Помимо непосредственно развертывания приложения пришлось сначала позаботиться чтобы везде все было одинаково — версии .Net, Powershell и модули. Это все заняло изрядное время.
Сетевое соединение
На некоторых площадках сетевое соединение просто не позволяло прокачивать все компоненты сборки. Шли таймауты, повреждения в процессе передачи. Много чего проверяли и пробовали — не очень успешно.
Пришлось остановиться на следующем решении: сценарий сборки доработали так чтобы все результаты упаковывались в один большой архив, который потом нарезался на маленькие фрагменты (по 2 мб). Доработали сценарий развертывания чтобы исключить параллелизм при скачивании артифактов, принимали все 2-х мегабайтные фрагменты и восстанавливали из них то что уже можно разворачивать.
Конфликт с антивирусом
Еще одна странная проблема с которой мы столкнулись — конфликт антивирусного ПО и одно из шагов развертывания: когда из архивов артифактов начинают извлекаться всякие "подозрительные" файлы, типа .js, .dll, то антивирус начинает в них пристально смотреть. И самое странное — антивирус начинает бросаться на файл еще до окончания распаковки и процесс распаковки падает сообщением "файл занят другим процессом". Пока боремся с этим исключая из сканирования локальную папку с артифактами — не очень хорошо, но больше ничего не придумали.
Улучшение процессов
После стабилизации процессов сборки и развертывания, мы перешли к "пошивке сапогов для сапожников" — улучшение внутренних процессов.
- построили базовую интеграцию корпоративной системы ИТ поддержки (service-now.com) с VSTS для передачи тикетов в виде Work Items. Поправили политику слияния в develop — теперь мы обязательно требуем наличия подключенного тикета.
- подключили CI к всем feature веткам. Как только изменение кода публикуется — версия собирается автоматически и готова к тестированию
- на стороне подрядной организации развернули большой набор тестовых стендов и организовали "self-service" развертывание сборок для внутреннего тестирования
- на стороне заказчика также развернули несколько тестовых стендов — для каждого из типов систем. Любой разработчик или сотрудник отдела может инициировать развертывание, разрешение на развертывание дает сотрудник отвечающий за тип системы и координирующий тестирование.
- организовали инфраструктуру для работы над проектами: специальное именование проектных веток, CI/CD на выделенные экземпляры системы с которой работают разработчики, аналитики и конечные бизнес пользователи
- интегрировали хранение авто-тестов в общий репозиторий (раньше тесты хранились отдельно) и процессы сборки и развертывания. Теперь сборка продукта содержит код и тесты — как для веток разработки, так и для продукта в целом.
- в порядке эксперимента поставили VSTS агенты на локальных компьютеров аналитиков, которые занимаются тестированием и деплоим туда клиентскую часть, для сборки которую надо протестировать.
Итоги
Текущая ситуация
- Весь код приложения хранится и поддерживается в MS VisualStudio Team Services (с недавнего времени — Azure Devops) управляемой клиентом. Систем контроля версий — GIT
- Все изменения полностью прозрачны и увязаны с сервисными тикетами (инциденты/изменения)
- Благодаря переходу на git / GitFlow существенно упростилась параллельная разработка.
- Введена процедура code review с участием представителя заказчика.
- Сборка приложения проводится системой CI. Полная сборка проводится для как для основных, так и для feature веток, что позволяет организовать раннее тестирование.
- Развертывание приложения на все целевые узлы проводится на основании сценариев. Постепенно, к шагам развертывания приложения добавляются шаги конфигурации базовых компонент и приложения.
- Сотрудники ИТ подразделения заказчика могут сами инициировать развертывание сборок (финальных или промежуточных) на тестовые среды — для тестирования или раннего ознакомления. Бизнес-пользователи также имеют доступ к тестовым средам.
- мы по-прежнему придерживается основных циклов релиза в 1 месяц. Хотя в последний месяц выкатываем что-то практические еженедельно.
- появился процесс "эксперементального" выката версии на некоторые площадки.
Время по этапам
№ | Описание этапа | Длительность |
---|---|---|
1 | От начала проекта — до полного контроля над кодом, процессом сборки и доставки до тестового окружения | 6 мес |
2 | От первого развертывания на тестовое окружение — до первого пилотного релиза на продуктив | 3 мес |
3 | От пилотного развертывания на продуктив — до первого релиза на все экземпляры | 5 мес |
Общая продолжительность — 14 мес
Длительнось, особенно на финальном этапе, во многом определялась координацией, и согласованным календарем обслуживания систем.
Трудозатраты
Общие затраты вовлеченных сотрудников заказчика и подрядной организации на все работы, связанные с изменением — примерно 250 человек * дней.
Автор: Хитрин Сергей