Мой рассказ будет состоять из двух частей. В этой части я расскажу о том, чего мы хотели добиться от новой системы, как она выглядела в первом её варианте, и почему в итоге нам пришлось её переделывать. Во второй части речь пойдёт о процессе переделки системы и о том, какие неожиданные бонусы нам это принесло.
Изображение: источник
Когда-то давно у нас в компании каждый мог вносить свои изменения прямо в ветку мастера и собственноручно раскладывать их. Для этого у нас была написана специальная утилита MSCP, которая работала достаточно примитивно: копировала изменения с одной машины на другие и выставляла нужные права.
Время шло, компания росла — и нам пришлось задуматься об автоматизации процессов, включая процесс выкладки небольших изменений. Так у нас появилась идея создать систему патчей. В первую очередь мы хотели, чтобы она позволяла любому разработчику присылать свои изменения в Git и раскладывать их на серверы. С нашей стороны мы требовали, чтобы изменения посмотрел ещё один разработчик и чтобы они были привязаны к задаче из нашей багтрекинговой системы (мы используем Jira).
Кроме того, система задумывалась с целью чинить срочные проблемы, а найти ревьюера для патча в три часа ночи может быть непросто.
А что нам надо?
Первый вопрос мы решили достаточно быстро: сделали форму, к которой нужно было прикреплять свои изменения и указывать детали (ревьюера и задачу).
На второй странице можно было посмотреть изменения, отклонить или принять их.
После подтверждения изменения попадали в master.
Второй вопрос: как эти изменения быстро доставить до серверов? Сегодня многие используют непрерывную интеграцию, и она могла бы хорошо справиться с задачей, если бы наши «честные» сборка и раскладка не занимали так много времени.
Честная сборка
Наша сборка всегда была достаточно сложной. Общий принцип был такой: в отдельной директории мы раскладывали файлы так, как они будут лежать на конечных серверах; затем мы сохраняли это состояние в snapshot (снимок файловой системы) и раскладывали его.
Мы складывали в директорию PHP-код, который брали из репозитория как есть, к нему добавляли сгенерированные файлы (например, шаблоны и переводы). Статику мы раскладывали отдельно. Это довольно сложный процесс, и ему можно посвятить целую статью, но в результате у нас была карта версий, чтобы генерировать для пользователей ссылки на файлы, которая уезжала вместе с основным кодом.
Дальше состояние директории нужно было куда-то сохранить. Для этого мы использовали блочное устройство, которое мы называли лупом. Вся директория копировалась на пустое устройство, которое затем архивировалось и доставлялось на отдельные «главные» серверы. С этих серверов мы забирали архивы в процессе раскладки. Каждый архив был объёмом 200 Мб, а в распакованном виде луп весил 1 Гб. На сборку без статики у нас уходило около пяти минут.
Честная раскладка
Сначала нам нужно было доставить архив на конечные серверы. Их у нас тысячи, поэтому вопрос доставки для нас всегда был большой проблемой: у нас несколько платформ (дата-центров), и на самой «толстой» порядка тысячи серверов с кодом. В попытках добиться лучших показателей (минимум времени и ресурсов) мы перепробовали разные методы: от простого SCP до торрентов. В итоге мы остановились на использовании UFTP. Метод был быстрым (при хорошей погоде — минута времени), но, к сожалению, не беспроблемным. Периодически что-то ломалось, и нам приходилось бежать к админам и сетевикам.
После того как архив (как-то) очутился на серверах, его надо распаковать, что тоже не бесплатно. Особенно дорогой эта процедура кажется, если помнить, что она выполняется тысячи раз, пусть и параллельно на разных машинах.
Никакой сборки
Итак, честная выкладка изменений занимала много времени, а для системы патчей нам была очень важна скорость доставки, ведь предполагалось, что ею будут пользоваться, когда что-то уже не будет работать. Поэтому мы вернулись к идее с использованием MSCP: быстро и просто в реализации. Таким образом, после того как изменения оказывались в мастере, на отдельной странице можно было по очереди разложить изменённые файлы.
Оно живо
Система заработала. Несмотря на некоторое недовольство мелочами, разработчики могли делать свою работу, и им для этого не нужны были ни доступ в master, ни доступ к серверам.
Но, конечно, при таком способе раскладки у нас появились проблемы. Некоторые были предсказуемы, некоторые мы даже как-то решили. Большинство из них было связано с параллельным редактированием файлов.
Один патч на несколько файлов
Пример предсказуемой проблемы. Новые файлы раскладывались по очереди. Что делать, если нужно изменить несколько файлов и изменения в них связаны? Например, я хочу добавить новый метод в одном файле и сразу использовать его в других. Пока нет закольцованного использования методов (см. взаимная рекурсия), достаточно помнить о правильном порядке раскладки файлов.
Усложним задачу. Допустим, у нас есть директория Д, и нам нужно заменить её содержимое. Операция rename нам не поможет, потому что она не может заменить непустую директорию. Однако есть обходное решение: можно заранее заменить директорию Д на так называемую символическую ссылку (symlink). Тогда само содержимое будет лежать в другом месте, например, в директории Д_1, а Д будет ссылкой на Д_1. В тот момент, когда потребуется замена, новое содержимое записывается в директорию Д_2, на которую создаётся новая ссылка TMP. Теперь rename TMP Д сработает, потому что эту операцию можно применять к ссылкам.
Это решение выглядит подходящим: можно менять целиком директорию с кодом, копируя старые файлы и записывая новые сверху. Проблема в том, что копировать весь код — долго и дорого. Можно заменять только поддиректорию, где менялись файлы, но тогда все поддиректории с кодом должны быть ссылками, ведь заменить заполненную директорию на что-либо в процессе раскладки мы не сможем. Мало того, что это решение выглядит очень сложным, — нужно не забыть добавить какие-то ограничения, чтобы два процесса не могли менять одновременно одну и ту же директорию или директорию и ее поддиректории.
В итоге мы не смогли найти технического решения, но придумали, как немного упростить жизнь: сделали раскладку нескольких файлов одним действием в интерфейсе. Разработчик указывал порядок раскладки файлов, а система их доставляла.
Несколько патчей на один файл
Сложнее, если файл один, а разработчиков, которые хотят его изменить, — несколько. Первый патч мы применили, но не разложили. В этот момент приходит второй патч, и его просят разложить. Что делать? Ещё интереснее, если второй патч применился, и в этот момент нас просят разложить первый.
Наверное, нужно уточнить, что мы всегда раскладывали только последнюю версию из мастера. Иначе могли возникнуть другие проблемы. Например, раскладка старой версии поверх новой.
Действительно хорошего решения этой проблемы мы не придумали. Мы показывали разработчикам diff между тем, что они раскладывают, и тем, что есть на машинах в данный момент времени, но это не всегда работало. Например, изменений банально могло быть много, а разработчик мог спешить или просто лениться (всякое бывает).
Много патчей, и все меняют одни и те же файлы
Это худший вариант, на котором даже останавливаться не хочется. Если изменения нескольких разработчиков затрагивали несколько одних и тех же файлов, наша система патчей особенно помочь не могла — оставалось рассчитывать на внимательность разработчиков и их умение общаться друг с другом. Но в теории тут вполне можно получить «рыбу», когда при любом порядке раскладки в определённый момент на серверах окажется частично неработающий код.
Картинка: источник
Проблемы с железом
Ещё одна проблема возникала, когда по каким-либо причинам один из серверов становился недоступен. У нас был механизм исключения таких серверов из раскладки, который срабатывал достаточно хорошо; сложности появлялись после их возвращения в строй. Дело в том, что версии конфигов и кода на работающих серверах у нас проверяются (целый отдел мониторинга есть!), и мы следим за тем, чтобы при возвращении сервера в строй все версии были актуальными. Вот только для патчей никакого версионирования у нас не было — мы просто копировали новые файлы на текущий код.
Аккуратного способа версионировать разложенные патчи мы не придумали, а проблему пытались решать обходными путями. Например, rsync с соседней машины в конце процесса раскладки. Но вот проверить как-то, что всё хорошо, мы всё равно не могли.
Мы перебрали несколько вариантов решения этой проблемы, например, хотели применять патчи и на «главных» серверах (тут важно помнить, что мы раскладываем запакованную версию, то есть нам нужно применить патч и запаковать версию обратно), но это было достаточно сложно реализовать.
Ложка мёда
Но, кроме проблем, были и положительные моменты.
Во-первых, разработчики быстро разобрались, что, кроме починки вещей, с помощью системы патчей можно иногда выкладывать новый функционал, например, когда это нужно срочно. Как и в любой компании, у нас бывают форс-мажоры. Но если раньше нам приходилось создавать внеочередной билд, на который отвлекались тестировщики и релиз-инженеры, то теперь некоторые изменения разработчик мог разложить самостоятельно.
Во-вторых, больше не нужен был какой-то специальный человек с правами, чтобы что-то починить. Любой разработчик сам мог выложить свои правки. Но и это не всё: выкладки билдов в целом стали проще, теперь проблемы делились на критические и те, которые можно починить с помощью патчей. Это позволяло реже откатываться и быстрее принимать решение о том, удачно ли мы выложились.
Проще говоря, система нам нравилась и набирала популярность. Мы и дальше пытались усовершенствовать её, но с описанными проблемами пришлось прожить ещё несколько лет. А о том, как мы их решили, как система работает сейчас, и как мы в процессе обновлений чуть не угробили новогодние каникулы, я расскажу во второй части статьи.
Комментарии (32)
kozyabka
07.06.2018 17:28а как же контейнеры с оркестровкой? :D
mspain
07.06.2018 17:50А как же просто нормальные платформы, в которых всё это из коробки без костылей и лисапедов? Я сам не великий гуру по фронтендам, но намекаю на java ee. Собираешь артифакт, деплоишь хоть на миллиард серверов.
youROCK
08.06.2018 09:22«Хотфиксы» в Java называются hot swap и это очень костыльная вещь. Пересборка и перепрогрев (!) большого java сервера намного дольше, чем просто файлик на сервере подменять (как в случае PHP).
uyga
07.06.2018 18:27+1Способов решить задачу быстрофикса в продакшене — масса. Мы решили для себя что текстовые патчи в код на интерпретируемом языке нас вполне устраивают. Быстро, понятно, дешево, сердито, логируемо и т.д.
В чем преимущество усложнения системы дополнительными обвязками в виде контейнеров? К тому же там, где подобное необходимо, мы докер вполне используем. banuchka даже про это доклады и статьи делал.tagirb
08.06.2018 00:14+1Наверное, в ваших условиях (гигантская база кода, долгий и сложный процесс сборки, тысячи серверов с ручной орекстровкой) это приемлемое решение и контейнеры сами по себе тут ничего не изменят.
Но ведь в таких условиях возникает масса проблем, часть которых вы тут тоже затронули, и которые как раз и решаются переходом от монолитной к распределённой архитектуре, с быстрыми и однозначно репродуцируемыми сборками отдельных компонентов. Тогда появляется смысл эти компоненты распихать по контейнерам и отправить в дальнее странствие по кубернетесовым облакам.ShaggyRatte Автор
08.06.2018 00:14Спасибо за ваш комментария, все сразу хочется обсудить! )
У нас сложный процесс сборки и раскладки, это правда, но он не долгий. Я писал в статье про время, но оно не катастрофическое: в этой части истории сборка и раскладка занимали где-то минут 10-12 с инкрементальной сборкой статики. Оно покажется большим только в сравнении: mscp позволяет разложить изменения за минуту.
Что касается микросервисов, тут ведь вопрос не только в раскладке изменений. Такой подход изменит весь процесс разработки и тестирования, а текущий вариант нас вполне устраивает. Менять же свою жизнь настолько сильно только ради деплоя — звучит как не самая хорошая идея. Более того, я думаю, что с отдельными сервисами есть свои сложности. У нас есть команда, которая пишет сервисы (хоть и не всегда микро) и с ними хватает проблем :)
P.s. Но в отрыве от всего этого, идея с оркестрируемым решением, конечно, выглядит красивой.
erthad
08.06.2018 00:07Почему не пользуетесь стандартным функционалом git (или аналогов)?
Create branch, commit, push, merge.
Даже если у вас разработчики на диалапе и имеют доступ только к е-мейлу, есть жеgit format-patch
иgit am
.ShaggyRatte Автор
08.06.2018 00:31Лично для меня ситуация с отдельными ветками выглядит приблизительно как с pull request'ами, о которых спрашивали где-то выше. Если коротко, то они не сделают наш процесс удобнее. Скорее нам надо будет потратиться на поддержание отдельного типа веток для патчей.
А в чем преимущества у использования git-format-patch перед отдельной формой на сайте?erthad
08.06.2018 00:59> Скорее нам надо будет потратиться на поддержание отдельного типа веток для патчей.
Можно вот это пояснить, пожалуйста? Ветки в гит – базовая фича, какая еще поддержка нужна?
> А в чем преимущества у использования git-format-patch перед отдельной формой на сайте?
Они вообще о разном.
Я видимо неверно прочитал «чтобы она позволяла любому разработчику присылать свои изменения в Git и раскладывать их на серверы».
Я посчитал, что у некоторых людей прямого доступа к репе нет (например, у находящихся на необитаемом острове) и им приходится отправлять свои патчи по е-мейл, эти две команды для того и сделаны.
Если же линк до сервера с репой есть, то конечно, для всех удобнее будет делать пуш в репу напрямую.bo0rsh201
08.06.2018 10:35представьте, что у вас есть self hosted git репозиторий внутри компании.
не github, не bitbucket, которые действительно предоставляют функционал для pull request-ов из коробки, а просто git репозиторий.
основная сложность тут не в том, чтобы доставить ваш diff в систему контроля версий, а в том, чтобы организовать всю обвязку вокруг.
помимо diff-а, вам нужно через какой-то ui:
— выбрать ревьюера
— выбрать тикет к которому прилинковать hotfix
— автоматом прогнать unit и smoke тесты
— дождаться пока ревьюер нажмет approve
— дождаться, пока разработчик нажмет кнопку «все готово, поехали на продакшен»
(делать это сразу после approve не очень хорошая идея)
— выложить hotfix на продакшен и замержить в мастер
конкретный способ доставки diff-а в эту систему тут вообще не играет никакой роли, самое важное и сложное — это как раз все последующие шаги.
и раз уж вы все равно делаете веб-интерфейс для их реализации, логичным выглядит добавление туда поля diff, чтобы не придумывать сложный механизм автоматического обнаружения этих hotfix-веток.
если говорить о детялах, то положить git diff в clipboard отличается от git format-patch по сути только оформлением, суть абсолютно одна и та же.
killik
08.06.2018 17:03+1глубокое понимание состояния человека.
Тут взоржал и заплакал одновременно. Атака нейронок через рекламку распознаётся на раз-два, но это зомби-стайл же. Подсказываю: нет, люди не любят уничтожать зомби тысячами и миллионами. Ибо уныло.
TheGodfather
08.06.2018 20:15+1Я чего-то определенно не понял.
Как я почитал статью: «Ну, нам тут надо код ревью настроить. Да не, нафиг существующие тулы, запилим свое. Да не, нафиг нативная работа с гитом, будем фигачить код в формочку на сайте». WTF?
Я, конечно, недолюбливаю Gerrit, но в нашей компании он и используется. И, представьте себе, он делает именно то, что вы описали. Разработчик сделал коммит -> «git review» -> коммит появился в веб-интерфейсе, емейл-нотификашки прилетели всем указанным ревьюверам, коммит получил +2 от другого разработчика — можно замержить в мастер (одной кнопкой Submit в интерфейсе). В вашем же случае это какой-то ад — скопировать текст(!) патча, руками ввести чего куда, а если есть замечания по ревью, то опять повторять этот процесс? Да вы шутите…
Дальше как вы уже с мастера деплоите, это другая тема, не имеющая отношения к ревью.ShaggyRatte Автор
09.06.2018 00:51Я понимаю ваше возмущение, правда. Я знаю, что есть много систем, которые решают проблему мержа изменений: в Gerrit есть описанный вами функционал, в github есть pull request, в teamcity добавили automatic merge и я уверен, что есть еще минимум десяток других решений.
Все их можно адаптировать, но они так или иначе нам не подходят. Те же иструменты для ревью кода плохо справляются с нашими объемами репозиториев и ужасно медленно идексируют новые изменения. И теперь представьте: вы хотите быстрее разложить изменения, потому что знаете, что пользователи не могут зайти на сайт, а Вася не может ваш код поревьюить, потому что какой-нибудь fisheye не успел обновить у себя код.
Мы видим, что сторонние инструменты не стоят на месте и потихоньку становятся лучше. Периодически мы просмотриваем их с целью «выкинуть самописное — заменить готовым», но пока они не работают у нас, нам приходится как-то жить. Я бы даже предложил написать статью с обзором, но я боюсь, что у нас часто очень специфические требования и такой обзор будет достаточно бесполезным для других.TheGodfather
09.06.2018 10:29> Те же иструменты для ревью кода плохо справляются с нашими объемами репозиториев и ужасно медленно идексируют новые изменения
Я все еще вас не понимаю. О какой индексации речь? Пуш — и коммит мгновенно появляется в интерфейсе. Сабмит — и коммит мгновенно мержится в мастер.
>вы хотите быстрее разложить изменения, потому что знаете, что пользователи не могут зайти на сайт
Скажите честно, как часто у вас случается, что «пользователи не могут зайти на сайт»? Я, конечно, не эксперт, но если Вася пупкин сломал прод своим коммитом, а до этого в течение месяца вся команда по разу ложила прод — это вам не ревью, а тестирование и многоуровневое развёртывание надо улучшать. Я вижу ситуацию «прод лежит, срочно хотфикс» как очень исключительную и разовую и в таком случае даже можно пропушить фикс без ревью имхо — бизнес важнее. Поэтому я вас опять не понимаю.
В нашей компании от пуша на ревью до мержа в мастер в среднем проходит около 2 дней, пока другие разработчики поревьювят, потом исправить замечания и пр.
immaculate
Читаю после работы и может быть чего-то недопонимаю, но такое ощущение, что это недописанный черновик статьи. Слишком много моментов опущено.
Например, почему сложно создать задачу? Jira давно и не очень активно пользовался, неужели настолько сложно создать задачу? Не припоминаю таких сложностей.
Почему используются текстовые патчи, которые надо копи-пейстить в форму? Чем хуже стандартная функциональность pull request?
ShaggyRatte Автор
Здраствуйте,
Спасибо за комментарий )
Почему сложно создать задачу? Честно говоря, тоже не понимаю. Мне до сих пор кажется, что это минута времени и достаточно просто, но разработчики сопротивляются. Если кто-то поделится, в чем реально сложность — с удовольствием почитаю.
С pull request сложнее. Для меня основная причина их отсутствия — они не сделают процесс удобнее. Сейчас вы делаете изменения, копируете их и добавляете в интерфейс. Не нужно создавать специальных веток нигде. Ну и это какие-то дополнительные сложности для нас (РЕ) в поддержке. У нас будет новый вид веток, которые надо отличать от стандартных, для которых будет другой набор хуков выполняться, например (у нас их много, но мы не все гоняем для патчей — кому важно, как отформатирован код, если сайт лежит?).
werklop
GitFlow? Нет, не слышали…
Если нет объективных причин у разработчиков, то у вас просто не хватает волевого решения, зачем вам тогда руководители…
maksim_ms
Возможно разработчики леняьтся так как на разработку большинства задач уходит меньше времени чем на создание тасков в Jira.
Выход, как я вижу укрупнять задачи.
Странно что в статье не написанно про тестирование. Раскатка идет сразу в прод сервера? Есть ли автотесты?
nizkopal
Привет :)
Странное решение. У меня лежит прод и я могу поднять его одной строчкой. НО… не поправить ли мне за одно вон те стили, чтобы дифф был не маленьким? Так? :)
Тесты запускаются: необходимый набор unit-тестов (высчитывается из кавереджа) и smoke-тесты. Думаю, автор не упомянул об этом потому, что это не имеет непосредственного отношения к истории создания и устройству системы патчей.
maksim_ms
Если лежит прод и нужно поднять одной строчкой то это Hotfix, причем перед выкаткой в прод нужно протестировать эту одну строчку на другой среде.
При этом еще нужно понять почему лежит? почему не дотестировали? Как предотвратить в будушем?
nizkopal
Даже если прод лежит, фикс было бы неплохо протестировать, это да. Особенно на предмет того, что этот фикс действительно поднимет прод. Для этого достаточно запустить хотя бы один smoke-тест, который идет меньше секунды.
А заниматься разбором того, почему сломали, почему не дотестировали и как предотвратить в будущем, стоит уже после.
Надеюсь, я правильно понял суть Вашего вопроса. Он довольно сумбурный на мой взгляд.
ShaggyRatte Автор
Немного дополню ответ nizkopal: уже после того, как мы все починили, мы начинаем задавать вопросы "а что было?" и "как предотвратить?". В этом процессе мы обычно пишем штуку под названием постмортем, и об этом у нас рассказывал ableev в своем докладе.
P.s. А еще, у меня сложилось впечатление (возможно, ложное), что Вы немного смешали обычные задачи и патчи. У нас есть стандартный флоу, я на нем не останавливался, но "обычные" задачи уезжают дважды в каждый рабочий день: утром и вечером (в пятницу — только утром). А вот hotfix'ы часто закрываются патчами.
maksim_ms
Спасибо за разъяснения.
uyga
werklop gitflow это миф. Почитайте другие статьи в блоге Баду пожалуйста. У нас много интересного, уверяю — вам понравится. Начать можно вот с этого.
По поводу волевого решения — не сомневайтесь, там где нужно поднажать, поднажимаем. Но необходимо так же помнить о том, что рабство запрещено некоторое время назад. К тому же наша компания (как и многие другие) — не армия. И способов мотивации сотрудников тогда, когда это действительно нужно, сильно больше чем «кнут» и «пряник».
werklop
Gitflow я взял как один из примеров. Статью прочитал, благодарю. Если у вас достаточно ответственные разработчики и код на проды не деплоится раньше, чем его посмотрит QA, то хорошо, иначе это будет из разряда: «х… с, х… с — и в продакшен!»
Причем тут рабство и армия, я про здоровую субординацию и направление развития. Вы же не станете соглашаться, что каждый сотрудник у вас независим, работает за «печеньки» и делает только то, что он сам хочет. Не надо уж так утрировать
erthad
Считаем на пальцах.
Сейчас, как понимаю, что-то типа такого:
1. Выводим дифф на экран (кстати, как это делается у вас?)
2. Копируем в буфер
3. Открываем броузер
4. Открываем страницу с «интерфейсом»
5. Вставляем дифф и жмакаем кнопку «Готово»
6. Если забыли что-то добавить, то или забиваем или повторяем всю операцию для хотфикса
С обычными гитовскими ветками процесс будет примерно в три команды:
git branch my-new-feature
###.... правим код
git commit -a --verbose
git push
По-моему, написать три команды — более удобный процесс, чем жонглирование окнами броузера и буферами обмена, нет?
mayorovp
А где в схеме с ветками, собственно, создание PR/MR или слияние?
ShaggyRatte Автор
Кажется, Вы забываете о том, что наша система в первую очередь нужна для маленьких изменений и hotfix'ов. У нас есть отдельный стандартный флоу с my-new-feature ветками, где есть (достаточно сложный, к слову) процесса auto-merge, который сам заберет вашу задачу, если вы в день релиза находитесь в офисе.
Возвращаясь к патчам, здесь все равно придется открывать браузер: нужно отметить человека, который будет делать ревью, выбрать ветку (или вставить дифф в нашем случае) и отметить задачу, к которой это все относится.
Ну и посмотрите на разницу в командах:
vs
Наверное, оно выглядит интуитивно понятнее для Вас, но уверяю, что это просто дело привычки. На практике, оно не будет так уж здорово. Хотя я признаюсь, что мне хотелось бы найти что-то более интуитивное, простое и "гиковское" (в хорошем смысле этого слова) на замену простому копированию из консоли в браузер.
P.s.
Если вы про терминал, откуда обычно копируют изменения — git-diff, но мне показалось, что вопрос прозвучал более обширно. У нас есть собственная тулза для ревью изменений в коде, называется codeisok, мы выложили ее недавно в open-source. Этот инструмент получился путем сильной переделки старого gitphp (не нашего) и мы писали об этом, если вам интересно :)
DoGraf
А куда потом коммитятся эти изменения (хотфикс)? Как-то этот момент остался за кулисами.
ShaggyRatte Автор
Коммитятся в мастер ветку. Мы даже сначала их коммитим (а не после), а потом файлы раскладываем уже из мастера, чтобы не разрешать конфликты между разными версиями файлов на серверах (например, если было два патча на один файл).