Как с помощью DevOps построить полноценную inhouse-разработку — опыт ВТБ
Практики DevOps работают. Мы убедились в этом сами, когда сократили время установки релизов в 10 раз. В системе FIS Profile, которую мы используем в ВТБ, установка теперь занимает не 90 минут, а 10. Время сборки релиза снизилось с двух недель до двух дней. Число постоянных дефектов внедрения при этом упало почти до минимума. Чтобы уйти от «ручного труда» и устранить зависимость от вендора, нам пришлось пройти через работу с костылями и найти неожиданные решения. Под катом — подробная история о том, как мы построили полноценную внутреннюю разработку.
Пролог: DevOps — это философия
За минувший год с небольшим мы провели немалую работу по организации внутренней разработки и внедрения практик DevOps в ВТБ:
- Выстроили процессы внутренней разработки по 12 системам;
- Запустили 15 pipeline, четыре из которых довели до продуктива;
- Автоматизировали 1445 сценариев тестирования;
- Успешно внедрили ряд релизов, подготовленных инхаус-командами.
Одной из самых сложных для организации inhouse-разработки и внедрения практик DevSecOps оказалась система FIS Profile — розничный продуктовый процессор на нереляционной СУБД. Тем не менее мы смогли построить разработку, запустить pipeline, выполнить установку на продуктив отдельных внерелизных пакетов и научились собирать релизы. Задача была непростая, но зато интересная и без явных ограничений в реализации: вот система — нужно построить inhouse-разработку. Единственное условие — использовать CD до продуктивной среды.
Поначалу алгоритм реализации казался простым и понятным:
- Нарабатываем начальную экспертизу разработки и добиваемся от команды кода приемлемого уровня качества без грубых дефектов;
- Максимально встраиваемся в существующие процессы;
- Для передачи кода между очевидными этапами пилим pipeline и упираем его одним из концов в прод.
За это время команда разработчиков необходимой численности должна наработать скилы и повысить долю своего вклада в релизы до приемлемого уровня. И все, можно считать задачу выполненной.
Казалось бы, вполне себе энергоэффективный путь к требуемому результату: вот DevOps, вот метрики производительности команды, вот набранная экспертиза… Но на практике мы получили очередное подтверждение того, что DevOps — это все-таки про философию, а не «прикрутить к процессу gitlab, ansible, nexus и далее по списку».
В очередной раз проанализировав план действий, мы поняли, что строим внутри себя некий аутсорс вендора. Поэтому в описанный выше алгоритм добавили реинжиниринг процессов, а также наработку экспертизы по всему маршруту разработки для достижения ведущей роли в этом процессе. Не самый простой вариант, но таков путь идеологически правильной разработки.
С чего начинается inhouse-разработка
Работать предстояло с далеко не самой дружелюбной системой. Архитектурно она представляла собой одну большую нереляционную СУБД, состояла из множества отдельных исполняемых объектов (скриптов, процедур, батчей и т. д.), которые вызывались по необходимости, и работала по принципу черного ящика: получает запрос — выдает ответ. Среди других сложностей стоит отметить:
- Экзотический язык (MUMPS);
- Консольный интерфейс;
- Отсутствие интеграции с популярными средствами автоматизации и фреймворками;
- Объем данных в десятки терабайт;
- Нагрузку свыше 2 млн операций в час;
- Значимость — Business-Critical.
При этом репозиторий исходного кода на нашей стороне отсутствовал. Совсем. Документация имелась, но все ключевые знания и компетенции были на стороне внешней организации.
К освоению разработки по системе мы приступили практически с нуля, учитывая ее особенности и слабое распространение. Начали в октябре 2018 года:
- Изучили документацию и основы кодогенерации;
- Проштудировали полученный от вендора краткий курс по разработке;
- Освоили начальные скилы разработки;
- Составили методичку для новых участников команды;
- Договорились о включении команды в работу в «боевом» режиме;
- Решили вопрос с контролем качества кода;
- Организовали стенд для разработки.
На наработку экспертизы и погружение в систему мы потратили три месяца, а уже с начала 2019 года inhouse-разработка начала свое движение навстречу светлому будущему, иногда со скрипом, но уверенно и целенаправленно.
Миграция репозитория и автотесты
Первая задача DevOps — репозиторий. О предоставлении доступа договорились быстро, но нужно было выполнить миграцию из текущего SVN с одной trunk-ветвью в целевой для нас Git с переходом на модель из нескольких ветвей и проработкой Git Flow. А еще у нас 2 команды со своей инфраструктурой плюс часть команды вендора за границей. Пришлось жить с двумя Git«ами и обеспечить синхронизацию. В такой ситуации это было меньшее из зол.
Миграция репозитория неоднократно откладывалась, выполнили ее только к апрелю не без помощи коллег из фронт-линии. С Git Flow решили для начала не мудрить, остановились на классической схеме с hotfix, develop и release. От master (он же prod-like) решили отказаться. Ниже мы объясним, почему такой вариант оказался для нас оптимальным. В качестве рабочего использовали внешний репозиторий, принадлежащий вендору, единый для двух команд. Он синхронизировался с внутренним репозиторием по расписанию. Теперь с Git и Gitlab можно было заниматься автоматизацией процессов.
Вопрос автотестов решился на удивление легко — нам предоставили готовый фреймворк. С учетом особенностей системы вызов отдельной операции представлял собой понятную часть бизнес-процесса и параллельно являлся unit-тестом. Оставалось подготовить тестовые данные и задать нужный порядок вызова сценариев с оценкой результатов. По мере наполнения перечня сценариев, сформированного на основе статистики выполнения операций, критичности процессов и существующей методики регресса, начали появляться автотесты. Теперь можно было начинать заниматься построением pipeline.
Как было: модель до автоматизации
Сложившаяся модель процесса внедрения — отдельная история. Каждая доработка вручную передавалась в виде самостоятельного инкрементального установочного пакета. Далее шла ручная регистрация в Jira и ручная установка на среды. Для отдельных пакетов все выглядело понятно, но с подготовкой релиза дела обстояли сложнее.
Сборка выполнялась на уровне отдельных поставок, которые являлись самостоятельными объектами. Любое изменение — новая поставка. Помимо прочего, к 60–70 пакетам основного состава релиза добавлялось 10–15 технических — версий, полученных при добавлении или исключении чего-либо из релиза и отражении изменений прода внерелизами.
Объекты внутри поставок пересекались между собой, особенно в части исполняемого кода, который был уникален менее чем наполовину. Было множество зависимостей как от уже поставленного кода, так и от того, установка которого только планировалась.
Для получения нужной версии кода требовалось строго соблюдать порядок установки, в ходе которой объекты многократно перезаписывались физически, некоторые по 10–12 раз.
После установки партии пакетов приходилось вручную выполнять инструкции по инициализации настроечных параметров. Релиз собирал и устанавливал вендор. Состав релиза уточнялся практически до момента внедрения, что влекло за собой создание «развязочных» пакетов. В результате заметная часть поставок переезжала из релиза в релиз со своим хвостом «развязок».
Теперь понятно, что при таком подходе — сборке релизного пазла на уровне пакетов — единая master-ветвь не имела практического смысла. Установка на прод занимала от полутора до двух часов ручного труда. Хорошо, что хотя бы на уровне установщика задавался порядок обработки объектов: поля и структуры заезжали раньше, чем данные для них и процедуры. Впрочем, это работало только в рамках отдельного пакета.
Логичным результатом такого подхода были обязательные дефекты установки в виде кривых версий объектов, лишнего кода, пропущенных инструкций и неучтенных взаимовлияний объектов, которые лихорадочно устранялись после релиза.
Первые обновления: сборка по коммиту и доставка
Автоматизацию начали с передачи кода через трубу по такому маршруту:
- Забрать готовую поставку из хранилища;
- Установить ее на выделенную среду;
- Прогнать автотесты;
- Оценить результат установки;
- Вызвать следующий pipeline на стороне команды тестирования.
Следующий pipeline должен зарегистрировать задачу в Jira и ожидать команды для разливки на выбранные контуры тестирования, которые зависят от сроков внедрения задачи. Триггер — письмо о готовности поставки на заданный адрес. Это, конечно, был явный костыль, но с чего-то нужно было начать. С мая 2019 началась передача кода с проверками на наших средах. Процесс пошел, осталось привести его в приличный вид:
- Каждая доработка выполняется в отдельной ветви, которая соответствует установочному пакету и вливается в целевую мастер-ветвь;
- Триггер запуска pipeline — появление нового коммита в мастер-ветви через merge request, который закрывают мейнтейнеры из команды инхаус;
- Синхронизация репозиториев происходит раз в пять минут;
- Запускается сборка установочного пакета — при помощи сборщика, полученного от вендора.
После этого шли уже существующие шаги по проверке и передаче кода, по запуску трубы и сборке на нашей стороне.
Данный вариант запустили в июле. Трудности перехода повлекли за собой некоторое недовольство вендора и фронт-линии, но за следующий месяц нам удалось убрать все шероховатости и наладить процесс в командах. У нас появилась сборка по коммиту и доставка.
В августе удалось выполнить первую установку отдельного пакета на прод средствами нашего pipeline, а с сентября все без исключения установки отдельных внерелизных пакетов выполнялись через наше средство CD. Кроме того, нам удалось достичь доли inhouse задач в 40% состава релиза при меньшей по численности, чем у вендора, команде — это определенный успех. Оставалась самая серьезная задача — собрать и установить релиз.
Финальное решение: кумулятивные установочные пакеты
Мы прекрасно понимали, что скриптовать инструкцию вендора — так себе автоматизация, нужно было переосмыслить сам процесс. Решение лежало на поверхности — собирать кумулятивную поставку из релизной ветви со всеми объектами нужных версий.
Начали с proof of concept: собрали руками релизную поставку по составу прошедшего внедрения и выполнили ее установку на наших средах. Все получилось, концепция оказалась жизнеспособна. Далее решили вопрос со скриптованием инициализирующих настроек и включением их в коммит. Подготовили новый пакет и проверили его уже на средах тестирования в рамках обновления контура. Установка была успешной, хоть и с обширным рядом замечаний от команды внедрения. Но главное, что нам дали добро на выход в продуктив в ноябрьском релизе с нашей сборкой.
Оставалось чуть больше месяца, собранные вручную поставки прозрачно намекали, что времени в обрез. Сборку решили делать из релизной ветви, но от чего ее отбранчевать? Prod-like у нас нет, а существующие ветви не годятся — много лишнего кода. Надо срочно пилить prod-like, а это свыше трех тысяч коммитов. Собирать руками — вообще не вариант. Набросали скрипт, который бежит по логу установки продуктива и набирает коммиты в ветвь. Раза с третьего он сработал корректно, и после «доработки напильником» ветвь была готова.
Сборщик установочного пакета написали свой, справились за неделю. Потом пришлось дорабатывать установщик из core-функциональности системы, благо она open-source. После ряда проверок и доработок результат признали успешным. Тем временем оформился состав релиза, для корректной установки которого пришлось выравнивать тестовый контур с продуктивом, написали для этого отдельный скрипт.
Естественно, к первой установке было много замечаний, но в целом код «заехал». А уже примерно с третьей установки все стало неплохо смотреться. Контроль состава и версионность объектов отслеживали отдельно в ручном режиме, что на данном этапе было вполне оправданно.
Дополнительная сложность заключалась в большом количестве внерелизов, которые приходилось учитывать. Но с Prod-like ветвью и Rebase задача стала прозрачной.
С первого раза, быстро и без ошибок
К моменту релиза мы подошли с оптимистичным настроем и более чем десятком успешных установок на разные контуры. Но буквально за сутки до срока оказалось, что вендор не выполнил работы по подготовке релиза к установке принятым способом. Если по каким-то причинам наша сборка не зайдет, то релиз будет сорван. Причем нашими стараниями, что особенно неприятно. Путей для отступления у нас не было. Поэтому мы продумали альтернативные варианты, подготовили планы действий и начали установку.
Удивительно, но весь релиз, состоящий из 800 с лишним объектов, заехал корректно, с первого раза и всего за 10 минут. Целый час мы выверяли логи в поисках ошибок, но не нашли ни одной.
Весь следующий день в релизном чате царила тишина: никаких проблем внедрения, кривых версий или «левого» кода. Даже как-то неловко было. Позже вылезли отдельные замечания, но на фоне других систем и предыдущего опыта их количество и приоритетность были заметно ниже.
Дополнительным эффектом от кумулятива оказалось повышение качества сборки и тестирования. За счет многократных установок полного релиза своевременно выявлялись дефекты сборки и ошибки развертывания. Тестирование в полных релизных конфигурациях позволило дополнительно выявлять дефекты взаимовлияния объектов, которые не проявлялись при инкрементальных установках. Это определенно был успех, особенно на фоне нашего вклада в релиз на уровне 57%.
Итоги и выводы
Менее чем за год нам удалось:
- Построить полноценную внутреннюю разработку по экзотической системе;
- Устранить критическую зависимость от вендора;
- Запустить CI/CD для весьма недружелюбного legacy;
- Поднять на новый технический уровень процессы внедрения;
- Заметно сократить длительность развертывания;
- Значительно уменьшить количество ошибок внедрения;
- Уверенно заявить о себе как о ведущей экспертизе разработки.
Разумеется, многое из описанного выглядит как откровенная костылизация, но таковы особенности системы и существующие в ней процессные ограничения. На данный момент изменения коснулись продуктов и сервисов ИС Profile (мастер-счета, пластиковые карты, накопительные счета, эскроу, кредит наличными), но потенциально подход можно применить к любой ИС, для которой поставлена задача внедрения практик DevOps. Кумулятивную модель можно смело тиражировать на последующие внедрения (в том числе внерелизные) из множества поставок.