Хранение кода в SCM

Kate

Administrator
Команда форума
Смотри, ты устроился работать в большую компанию, где много команд, каждая разрабатывает свой продукт, часть из них создаёт микросервисы вокруг ядра, часть создаёт свои отдельные полноценные продукты. И, допустим, вся разработка до сих пор не использует централизованное хранение кода, работает без CI/CD и без наработок DevOps. Твоей первой задачей поставили организовать подход к хранению исходного кода в рамках всей компании. По секрету скажу, большие компании любят, когда используется единый подход, индивидуализм для бизнес-конвейеров всегда означает сложность управления сроками разработки, поставки и т.д. Задача, которая кажется простой на первый взгляд, всегда обрастает сложностями в нюансах.

Ты настроен на волну NotOps и в этой статье я постараюсь ответить на вопросы:

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

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

Кстати, по поводу применения изменений, погрузимся сразу здесь в жизненный цикл таких репозиториев. Чаще всего, монорепы живут в мире trunk based development. Не буду нагружать всей спецификой, передам только суть: разработчики не делают долгоиграющих веток. Вся разработка или коммитит напрямую в основную ветку постоянно или делает короткие feature бранчи, которые живут не дольше пары дней, а после заносятся в основную ветку через PR, с прогоном всей сборки/тестирования. Первый случай будет работать до тех пор, пока коммиты не так активно появляются в ветках, лично мне кажется, что даже 10 в день от каждого участника команды из 10 человек это уже много, что неизбежно приведёт к постоянным конфликтам слияний и начнёт замедлять разработку. Но второй способ лишь оттягивает эту неизбежность, так как конфликты никуда не денутся, просто наша основная ветка станет более "стабильной". И вот чтобы разрешить проблему очередности применения PR приходится придумывать какие-то собственные "решатели" конфликтов, которые ещё и будут формировать очередь из вливаемых PR. Понятное дело, пока вся компания это 10 человек, вместе с продуктом, разработчиками, тестировщиками и сопровождением и, фактически, у вас просто один проект на компанию, ваш монореп будет работать замечательно, восхитительно и быстро. Но я бы, если честно, такое монорепом назвал с натяжкой. Почему? Посмотрим на всем известных мастодонтов этой идеологии: MS, Google, Twitter, Yandex. Они монорепой называют не репозиторий, в котором лежит сразу бэк и фронт от одного проекта, не репозиторий с кодом и тестами, даже не репозиторий с исходниками и описанной инфраструктурой, для них монореп это именно солянка из проектов(каждый из которых может содержать свой фронт+бэк+тесты+инфраструктуру). Хоть один из них использует любой из инструментов версионирования без своих личных доработок и автоматизаций? Нет, конечно. Причём, существует мнение, что git не очень подходит для таких репозиториев, поэтому MS, например, сделали VFSForGit. Угадаете для чего он нужен? На самом деле, такие большие репозитории никто не хочет полностью клонировать на свою рабочую станцию, тем более, никто не хочет индексировать его постоянно. Поэтому нужно или обеспечить частичную индексацию только нужного кусочка репозитория или дать доступ до какой-то виртуальной дисковой системы, где лежит весь код, а разработчик работает с нужной ему частью. Вот как раз вторым путём и пошёл Microsoft в своей доработке. Первый путь, это, например, использование svn вместо git. Вообще работа git, svn, hg и прочих scm это тема для отдельного большого исследования, если хотите услышать от меня об этом - пишите в комментариях, приготовлю материал.

Второй вариант хранения - каждый проект в своём репозитории. Тут, конечно, с одной стороны можно дробить до бесконечности (проект - репозиторий, бэк - репозиторий, класс - репозиторий), но, естественно, нужно идти путём здравого смысла и не уходить в крайности. Насколько сильно можно дробить код для каждого отдельного репозитория - вопрос к компании-работодателю и к зоне ответственности DevOps. Лично моё мнение, в идеальном мире DevOps в одном репозитории должно хранится всё, что связано с проектом. У нас же команды, скорее всего, по Agile, а значит одна команда отвечает за один продукт. Всё, что связано с продуктом лежит отдельно от остального, но вся команда отвечает за успешность этого продукта в целом. Причём, когда я говорю всё я подразумеваю, что в репозитории лежит сам код для сборки фронта и бека, код автотестов(если они есть), код для сборки на этапе CI и код для деплоя на этапе CD. Иными словами, посмотрев в один репозиторий ты сможешь полноценно оценить состояние проекта. Конечно, эта модель будет работать только в том случае, если компания не только разрабатывает продукт, она же его тестирует и сопровождает. Первые два этапа обычно всё же идут вместе, а вот сопровождение может производиться отдельными людьми в виду специфики бизнеса, например, вы просто вендор, который готовит продукт для других компаний. Но даже в таком случае можно выстроить процесс так, что весь проект живёт внутри одного хранилища. Например, первоначальную версию деплоя сделали ваши разработчики, отдали его заказчикам, те использовали инструментарий, нашли тонкие места, где деплой не подходил в виду специфики рабочего окружения, указали доработки, которые им были необходимы, а разработчики, на основе обратной связи, довели деплой до условного "правильного" состояния. Иными словами, мы до сих пор сохраняем тот самый подход DevOps, то есть направленное взаимодействие двух составляющих и их тесную интеграцию. Но это всё, опять же, будет работать только в случае зрелой команды разработки и зрелой команды сопровождения, ну или хотя бы стремящихся к этой зрелости команд.

С точки зрения организации версионирования, на самом деле, нет сильного отличия от мира монорепозиториев. Множество проектов живёт в модели GitHub flow. Очень грубо говоря, у нас есть некоторый стабильный branch и если нам нужно реализовать новую feature, делаем новую ветку от текущего состояния main, создаём эту самую feature, а потом, после успешного прохождения CI, вносим все изменения в main через Pull Request (Merge Request). Релиз, в данном случае, происходит сразу после внедрения новой фичи в main, что не всегда удобно, поэтому мне кажется лучшей альтернативой релизом считать тег на каком-то конкретном коммите ветки main, с последующей сборкой этой самой ревизии в виде дистрибутива, то есть у нас сразу несколько фич или фиксов попадает в один релиз, что с одной стороны замедляет time to market, но с другой даёт чуть большее пространство для сборки весомых релизов. Следовательно, всё это очень грубо похоже на trunk based development, есть основная ветка, все изменения в неё попадают достаточно быстро, релизы происходят только с неё.

Помимо GitHub flow, есть изначально созданный Git flow, который достаточно сложно организован: есть отдельный main, который в себе содержит только релизные коммиты, на которые вешаются теги, есть отдельная ветка для каждого релиза, которая формируется из коммитов веток с хотфиксами, фичами и активной разработкой. Следовательно, вообразим себе, что мы разработчик, нам, в зависимости от типа задачи, над которой мы работаем, приходится создавать отдельную ветку или коммитить в уже существующую, их приходится время от времени мёрджить между собой в релизной ветке, когда релизная ветка будет наполнена весомым набором функционала (или подойдёт срок выпуска нового релиза), она должна будет влиться в main одним коммитом (желательно использовать стратегию squash, то есть все коммиты слить в единый большой коммит), на этот коммит будет выставлен тег и по этому тегу будет произведена сборка релизного дистрибутива.

Где-то рядом можем ещё вспомнить Gitlab flow, который, как мне кажется, является чем-то средним между предыдущими двумя подходами. Для деплоя у нас должны быть отдельные ветки со staging, pre-prod и prod, для разработки у нас есть development и набор production веток, а релизы сводятся к тому, что разработчики вносят все изменения в свою main development ветку, после этого, как только будет принято решение, что код готов к релизу - создаётся отдельная релизная ветка на основе текущего состояния main и она уже используется для deployment flow. В релизной ветке с этого момента больше не ведётся активная разработка, в худшем случае, в неё при помощи cherry-pick можно занести конкретные изменения из коммитов разработчиков, всё остальное, что происходит с релизной веткой - она двигается по deploy и в неё вносятся изменения от ops части команды: настройка CD pipeline, изменения ansible playbook и тому подобное.

А теперь испортим всё, о чём я тут наговорил, ролевыми моделями доступа к этим самым репозиториям. Понимаю, очень хочется жить в мире сферических коней, когда все имеют доступ ко всему, вокруг сплошные самоорганизованные специалисты, которые понимают, что они делают. Реальность, конечно же, далека от этой позиции. Поэтому на многих контрибьюторов накладывают искусственные ограничения, например, запрещают вносить изменения напрямую в main, разрешают только через PR, причём PR обязательно должен быть собран нашей системой CI, об успешности сборки должно быть объявлено внутри системы хранения кода, а сам изменённый код должна посмотреть команда и одобрить его. Но помимо таких внутрикомандных ограничений существуют ещё кросс-командные ограничения. Если ты работаешь в маленькой компании, то для тебя сейчас это всё будет дико, но в больших компаниях действительно приходится задумываться о защите кода одной команды от другой, в таких компаниях намного сложнее создать один монорепозиторий на всю компанию. В зависимости от усиления степени параноидальности, которая часто коррелирует с количеством этих самых команд и количеством штатных единиц внутри команд, запрет может быть не только на запись в чужой репозиторий, но и на чтение из чужого репозитория и даже на запись одной частью команды в репозиторий своей же команды. Но погоди, пока не начинай кричать об этих задолбавших корпоратах с их бесконечной бюрократией, давай попробую объяснить. С запретом пушить в релизную или main ветку, думаю, всё понятно, тут просто работает принцип "не навреди", особенно хорошо он работает в том случае, если есть в команде джуны и сеньорам не очень хочется получить нерабочий или некомпетентный код в релизе напрямую, наоборот, хочется всё же проследить за качеством кода. На самом деле, даже в случае этих ограничений, "плохой" код в базу попадает, потому что все мы люди, можем и не отследить все изменения, поэтому усиливается автоматизация проверок, но это тоже тема отдельного материала, сейчас мы в эти дебри с тобой не пойдём.

Теперь с запретом на запись в репозитории чужих команд, тут тоже более менее понятно, в больших компаниях требуется, чтобы команда занималась своим кодом и улучшала его, а не тратила время на улучшение кода чужой команды. В крайнем случае, если команда сделала свой проект настолько идеальным, что у неё есть время на доработки чужого, то ей или добавляют новых фич больше, или говорят, что их проект становится легаси, нужно делать новый, или команда делает fork чужого проекта и вносит изменения в чужой проект через него. Чаще всего большой работодатель стремится, чтобы команда работала над своим продуктом, а работа над другим продуктом должна производиться другой командой, тем самым, стараясь не сбавлять конвейерной мощности выпуска релизов всех продуктов, которые есть.

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

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

Ну и третий слой паранойи, когда компания запрещает части команды чтение репозитория своей же команды. На самом деле, об эту бюрократию разбивается мой идеальный мир "один репоз - один проект". Потому что в таких случаях для каждого этапа или для каждой части команды, если хотите, создаётся отдельный репозиторий. Это тоже всё может дробиться по-разному, но как пример возьмём такую модель. У разработчиков и бека и фронта свой репозиторий, в котором они создают сам продукт, у тестировщиков свой репозиторий с автотестами, но они ещё могут читать исходный код в репозитории разработки, а у сопровождения свой репозиторий, в котором они хранят код деплоя, плейбуки, пайплайны и вот это всё, при этом они могут читать исходный код, тесты и редактировать свой репозиторий. Но в их репозиторий у остальных частей команды доступа может не быть, более того, сопровождение может вообще хранить свой код в отдельной системе, грубо говоря, у этапа CI свой gitlab, а у сопровождения какой-то свой. И у всех частей команды, кроме сопровождения, отсутствует физический доступ (сервера не пингуются) до системы хранения кода с пайплайнами. Такие модели существуют в том случае, если зоны ответственности строго разделены между участниками команды, плюс, чтобы защитить от утечек и разрешить возможность разработчикам, например, жить в менее защищённом мире с доступом во внешнюю сеть, а вот сопровождению наоборот, запретить выход во внешнюю сеть в принципе. Эта паранойя может усилиться в том случае, если сопровождение разделяется на несколько подвидов: часть отвечает за инфраструктуру как железо, часть за инфраструктуру как ОС и её настройки, часть за деплой продукта на инфраструктуру и сопровождение его работы. Тогда появляются дополнительные репозитории, дополнительные разграничения прав и дополнительные этапы согласований тех или иных изменений.

Резюмируем: хранение кода можно организовать как в монорепозиторий для всей компании, так и в несколько репозиториев. Оба подхода, как бы сильно не хотелось адептам этих двух лагерей, сводятся примерно к одному и тому же, проекты разрабатываются внутри своих каких-то сущностей, общее взаимодействие через проекты это, первоначально, взаимодействие команд между друг другом. Грамотно выстроенный и, что не менее важно, грамотно внедрённый процесс внедрения новых фишек в общий функционал важнее, чем способ хранения самого кода. При этом, сам процесс может как выстроить прозрачную систему взаимодействия всех звеньев команды, так и сжать все команды в набор бюрократических процедур, когда, с одной стороны, у нас построен DevOps, но с другой стороны, он сильно завязан на ролевой модели.

Естественно, я тут пробежался только по самым верхам, здесь ещё можно копать в сторону технических возможностей сделать из полирепозиториев видимость монорепозиториев при помощи всяких git subtree, можно было бы поговорить о том, что для монореп использовать git - в целом не самая хорошая затея, когда есть svn и hg. Поговорить о большом количестве инструментов, которые помогают справляться с проблемами технического характера, но лично мне кажется, что как раз техническая сторона вопроса - вполне решаема и в любом случае проще исправляема, чем сторона процесса. Всё всегда идёт от людей и решать, что и как использовать, должны тоже люди для своего же удобства, а инструментарий будет найден под любой выбранный способ. Но, это лишь мнение очередного человека из интернета и, как человеку, мне свойственно ошибаться.

 
Сверху