В начале был “workflow”

f104dad7d9cca5261f6114d0d5787b85.png

Добрый день! Меня зовут Кирилл,  и я DevOps-инженер. За свою карьеру мне не раз приходилось внедрять DevOps-практики как в существующие,  так и в новые команды, поэтому хочу поделиться своим опытом и мыслями по поводу стратегий ветвления. Существует множество различных типов рабочих процессов,  и чтобы разобраться что к чему, предлагаю рассмотреть пример создания нового программного продукта.

Часть 1: Рабочий процесс

Мы в начале пути. Создали репозиторий и начали писать код, неспешно, коммит за коммитом, публикуя изменения в мастер. Появляется первый прототип приложения, появляются тесты, проходит сборка,  и вот настал момент развёртки приложения с целью предоставить свой продукт пользователям.  

А далее как обычно бывает: всплывают первые запросы от пользователей на добавление новых фич/устранение багов и т.д., разработка кипит. Для того чтобы ускорить выход новых версий, принимается решение расширить команду DevOps«ом, и для решения насущных проблем DevOps предлагает построить CI/CD-конвейер (pipeline). И вот пришло время рассмотреть, как же CI/CD-конвейер ляжет на наш рабочий процесс,  где у нас сейчас только мастер.  

bbdb2048c529a71f432a6e8ebe490a48.png

Для примера мы взяли простой конвейер с одним окружением. И вроде всё выглядит хорошо: разработчик запушил код в мастер, запустился конвейер, код прошёл ряд проверок, собрался и развернулся в окружении. 

А теперь рассмотрим ситуацию, когда конвейер прервался на тестах. 

d98600d70a48f2e1a03e61a193b45f02.png

То есть тесты показали, что в текущей версии мастера есть ошибки. Нам на руку, что в нашем примере конвейер прервался, и на окружении до сих пор работающее приложение, и пользователь остаётся довольным. А вот что начинается в команде разработки:

340d403107fd26fac90444ee9430d66c.png

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

  • уменьшение производительности,  

  • впустую потраченное время,  

  • много головной боли. 

Для решения насущных проблем можно прибегнуть к изменению рабочего процесса.  

Первым делом добавим небезызвестные feature-ветки. 

c271dead0da9c19f6fdd44c61c1ae729.png

В отдельных feature-ветках каждый разработчик может без стресса заниматься своей задачей. При этом мы блокируем коммиты напрямую в мастер (или договариваемся так не делать),  и впоследствии все новые фичи добавляются в мастер через «merge request».   

И в очередной раз проиграем проблему: в feature-ветке обнаружен баг. 

d8e5bb74028333ab410503861d60f1f0.png

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

Но что,  если на окружение попал новый мастер,  и спустя какое-то время обнаружен баг (не углядели, всякое бывает).  

595b47377e0ced23f55feaba6d41e347.png

Соответственно, это уже критическая ситуация: клиент не доволен, бизнес не доволен. Нужно срочно исправлять! Логичным решением будет откатиться. Но куда? За это время мастер продолжал пополняться новыми коммитами. Даже если быстро найти коммит,  в котором допущена ошибка,  и откатить состояние мастера, то что делать с новыми фичами, которые попали в мастер после злосчастного коммита? Опять появляется много вопросов.  

Что ж, давайте не будем поддаваться панике,  и попробуем ещё раз изменить наш рабочий процесс,  добавив теги.  

0b6981ab6d34ac5527e9adc46ea1ef2a.png

Теперь,  когда мастер пополняется изменениями из feature-веток, мы будем помечать определённое состояние мастера тегом.  

Но вот в очередной раз пропущен баг в теге v2.0.0, который уже на окружении.  

40f3d477cd9445dce9f1eb6fd1e530dc.png

Как решить проблему теперь?   

Правильно, мы можем повторно развернуть версию v1.0.0, считая её заведомо рабочей.  

217f50e309c914f22b2595199c23ecb6.png

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

  • сэкономили время и,  как следствие,  деньги,  

  • восстановили работоспособность окружения,   

  • предотвратили хаос,   

  • локализовали проблему в версии v2.0.0. 

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

Для примера возьмём и рассмотрим давно всем известный Git Flow:   

f0dc3c8c603a36c0bd922026496d93a1.png

Сравним его с нашим последним примером и увидим,  что у нас нет develop-ветки, а ещё мы не использовали hotfixes-ветки. Следовательно,  мы не можем сказать, что использовали именно Git Flow. Однако мы немного изменим наш пример, добавив develop- и release-ветки.  

665adac09e9c089adca1bd667830b94d.png

И теперь в каком-то приближении наш пример стал похожим на Git Flow. Однако что мы получили в этом случае? Какие проблемы нам удалось решить и как нам удалось улучшить нашу жизнь? По моему мнению,  добив наш рабочий процесс до Git Flow, который многие используют как эталонную модель,  мы всего-навсего усложнили себе жизнь. И здесь я не хочу сказать, что Git Flow плохой, просто в наших простых примерах он определённо излишний.  

Что ж, на Git Flow жизнь не заканчивается, ведь есть не менее известный GitHub Flow.  

d17ad9b16e7cbf013c33100e33b27918.png

И первое,  что мы можем заметить, так это то, что он выглядит в разы проще,  чем Git Flow. И если сравнить с нашим примером, то мы можем заметить, что здесь не используются теги. Но, как мы можем вспомнить,  мы ведь добавляли их не просто так, а с целью решить определённые проблемы, поэтому и здесь мы не можем сказать, что мы использовали конкретно GitHub Flow.  

Собственно, сравнив наш рабочий процесс из примера с Git Flow и GitHub Flow,  хотелось бы подчеркнуть следующее: безусловно,  существование паттернов для построения рабочих процессов — это огромный плюс, так как мы можем взять существующий паттерн и начать работать по нему,  и в определённых случаях какой-либо определённый паттерн идеально впишется в процесс разработки. Однако это работает и в другую сторону:  какой-либо существующий паттерн может вставить нам палки в колеса и усложнить процесс разработки. 

8585737f4d23f8044d1b31ff97aaa5dc.jpeg

Поэтому не стоит забывать, что Git и его рабочие процессы — это лишь инструменты, а инструменты,  в свою очередь,  призваны облегчить жизнь человека, и иногда стоит посмотреть на проблему под другим углом для её решения.

Часть 2: Участь DevOps’а

В первой части мы рассмотрели, как выглядит рабочий процесс, а теперь посмотрим, почему для DevOps-инженера так важен корректно настроенный рабочий процесс. Для этого вернёмся к последнему примеру,  а именно к построению того самого конвейера для реализации процесса CI/CD. 

Так как конвейер может быть реализован различными инструментами, мы сфокусируемся конкретно на том, почему рабочий процесс важен для DevOps.   

Собственно, построение конвейера можно изобразить вот такой простой картинкой:  

22fc94510ce5df367c56507305cd3b29.png

Ну или одним вопросом: «как связать между собой код в репозитории и окружение?» 

Следовательно, нужно понимать,  какой именно код должен попасть в окружение, а какой нет. К примеру, если в ответ на вопрос: «Какой рабочий процесс используется?» мы услышим: «GitHub Flow», то автоматически мы будем искать нужный код в master-ветке. И ровно наоборот, если не построен никакой рабочий процесс и куски рабочего кода разбросаны по всему репозиторию, то сначала нужно разобраться с рабочим процессом, а лишь потом начинать строить конвейер. Иначе рано или поздно на окружение попадёт то, что возможно не должно там быть, и как следствие,  пользователь останется без сервиса/услуги.  

Сам конвейер может состоять из множества шагов, в том числе у нас может быть несколько окружений. 

9f402d993c62390f3504dd47616ffad3.png

Но для наглядности далее рассмотрим два основных этапа в CI/CD- конвейерах: build и deployment/delivery. И начнем мы,  пожалуй,  с первого — build.  

Build — процесс, конечным результатом которого является артефакт.   

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

Так вот, у нас есть отличная возможность взять имя тега для артефакта и опубликовать его. Но что,  если у нас нет никакого рабочего процесса?  Что ж, тут уже сложнее. Конечно, мы можем взять хеш коммита,  или дату, или придумать что-либо ещё для идентификации артефакта. Но очень скоро разобраться в этом будет практически невозможно.

И вот пример из реальной жизни. 

Представьте ситуацию, когда вы хотите загрузить новую версию Ubuntu, и вместо такого списка версий:   

90596c12d36349698e7839d64b85ab8b.png

… у вас будет список хешей коммитов. Следовательно, это может быть неудобно не только для команды, но и для пользователя. 

Бывают случаи, когда мы можем пренебречь именованием. Поэтому рассмотрим ещё один небольшой пример:  у нас нет конкретного рабочего процесса;  как следствие,  у нас нет понимания,  что именно мы должны хранить в нашем хранилище. Что,  в свою очередь,  может быть чревато последствиями, так как хранилище так или иначе ограничено: либо деньгами, либо местом, либо и тем,  и другим. Поэтому в ситуации, когда у нас нет конкретного рабочего процесса,  мы можем начать публиковать артефакт из каждой feature-ветки (так как чёткой определённости у нас нет), но в таком случае рано или поздно возникнет ситуация, когда ресурсы закончатся, и придётся расширяться, что опять же несёт за собой трату как человеческих, так и денежных ресурсов.  

Конечно,  на этом примеры не заканчиваются, но думаю,  что теперь мы можем перейти к delivery/deployment.  

Delivery — процесс,  в рамках которого развёртка приложения на окружении происходит вручную.

Deployment — процесс,  в рамках которого развёртка приложения происходит автоматически.  

В случае с Delivery мы можем автоматизировать процесс развёртки в окружение и запускать его вручную. Однако если не будет выстроен рабочий процесс, то тогда мы вернёмся к той ситуации, которая возникала в наших примерах с рабочим процессом ранее, когда в коммите обнаруживался баг.  

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

Следовательно, и здесь нам крайне важно наличие рабочего процесса, по крайне мере в том случае, когда преследуется цель сделать хорошо. 

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

Заключение

В конце хотелось бы добавить, что основная мысль,  которую мне хотелось донести этой статьёй, — это то,  что есть книги, теория и так далее, а есть здравый смысл. Конечно,  есть множество различных практик и примеров, которые успешно работают в том или ином продукте, но это не значит, что эти практики сработают в вашей команде. Поэтому если вас мучает вопрос:  «Почему у нас это не работает?» или «Что нам нужно сделать,  чтоб у нас это заработало?»,  то попробуйте задаться вопросом:  «А надо ли оно нам?» 

© Habrahabr.ru