Обновление процесса CI/CD: подготовка и планирование

В 2020, наверняка, достаточно сложно найти проект в описании стека которого не было бы одного из следующих слов: IaC, микросервисы, kubernetes, docker, aws/azure/gcloud, блокчейн, ML, VR и так далее. И это здорово! Прогресс не стоит на месте. Мы растем, вместе с нами растут наши проекты, появляются более удобные и функциональные инструменты, которые решают современные проблемы.

Здравствуйте. Так я хотел начать эту статью. Но, потом я пересмотрел некоторые вещи, пообщался со своими коллегами, и понял что был бы не прав. Всё ещё существуют проекты которым уже по 15+ лет, у которых менеджеры и участники староверы, а соответственно у этих проектов древний стек технологий, который достаточно сложно поддерживать в существующем зоопарке. И по каким-либо причинам глобально обновить этот проект не получается (заказчик — старовер, нет аппрува, проект очень большой, и миграция затягивается, или всех все устраивает), и приходится его поддерживать. Еще более неприятно когда подобный проект все еще активно девелопится. Это как снежный ком. Заказчик и публика требуют фич, код требует доставки, сервера требуют внимания и заботы… А битбакет — так вообще, перестал поддерживать меркуриал. К рассмотрению предлагается как раз такой случай.

Что будет рассмотрено: конвертация mercurial-git, переезд CI с CruiseControl.NET на TeamCity, с git-deployment на Octopus c небольшим описанием всего процесса.

Текста получилось очень много, поэтому он будет разбит на отдельные части для облегчения восприятия. Тут будет оглавление.
Часть 1: что есть, почему оно не нравится, планирование, немного bash. Я бы назвал эту часть околотехнической.
Часть 2: teamcity.
Часть 3: octopus deploy.
Часть 4: за кадром. Неприятные моменты, планы на будущее, возможно FAQ. Скорее всего, тоже можно назвать околотехнической.

Я бы не назвал это руководством к повторению по многим причинам: недостаточное погружение в процессы проекта ввиду нехватки времени, недостаточное количество практики подобных вещей, огромный монолит, в котором все подпроекты тесно переплетены, и куча других нюансов которые заставляют гореть, удивляться, мириться с ними, но ни в коем случае не радуют. А также, ввиду особенностей проекта (он является достаточно уникальным), некоторые шаги будут подстроены исключительно под этот случай.

Классическое введение

У нас был mercurial репозиторий, 300+ (открытых!) бранчей, ccnet, еще один ccnet + git (для деплоев), и целое множество модулей проекта с собственными конфигами и отдельными клиентами, четыре окружения и целое множество пулов в IIS, а также cmd скрипты, SQL, больше пяти сотен доменов, две дюжины билдов, и активный девелопмент в придачу. Не то чтобы все это было необходимо, но это работало, причем неудобно и долго. Единственное что вызывало у меня опасение — это наличие других проектов требующих моего внимания. Ничто в процессе работы с задачами такого масштаба не бывает более опасным, чем отсутствие концентрации и прерывания.
Я знал, что рано или поздно мне придется уделять внимание другим задачам, и именно поэтому потратил огромное количество времени на изучение существующей инфраструктуры, чтобы впоследствии хотя бы по ней не возникало нерешаемых вопросов.

Полное описание проекта привести, к сожалению, не могу, по причине NDA, так что будут рассмотрены общие технические моменты. Также будут закрашены все названия относящиеся к проекту. Прошу прощения за черную размазню на скринах.

Одной из ключевых особенностей проекта является то, что у него есть некоторое количество модулей, которые имеют одно ядро, но отличаются его конфигурациями. Также есть модули с «особым» подходом, предназначенные для реселлеров и особо крупных клиентов. Один модуль может обслуживать больше одного клиента. Под клиентом следует понимать отдельную организацию или группу людей которые получают доступ к конкретному модулю. Каждый клиент получает доступ по собственному домену, имеет собственный дизайн и свои уникальные настройки по-умолчанию. Идентификация клиента происходит по домену который он использует.

Общую схему данной части проекта можно представить следующим образом:

Обновление процесса CI/CD: подготовка и планирование — IT-МИР. ПОМОЩЬ В IT-МИРЕ 2020

Как видим, ядро везде используется одно и то же, и это можно будет использовать.

Причины того, почему вообще возникла задача пересмотреть и обновить CI/CD процессы:

  1. В качестве билд-системы использовался CruiseControl.NET. Работать с ним — сомнительное удовольствие в принципе. XML конфиги на несколько экранов, куча зависимостей и линковок конфигов друг на друга, отсутствие современных решений современных проблем.
  2. У некоторых разработчиков (в основном лидов) на проекте должен быть доступ к билд серверам, и они любят иногда менять конфиги ccnet которые менять не следует. Догадайтесь, что происходит потом. Нужно иметь простой и удобный менеджмент прав в CI системе, не отбирая при этом у разработчиков доступ к серверу. Проще говоря, нет текстового конфига — некуда лезть шаловливыми руками.
  3. В качестве деплоймент-системы использовался… Тоже CCNET, но на клиентской стороне в связке с git. Процесс билда и доставки кода выглядел примерно так (см. картинку).
  4. Экспериментально выяснилось, что поддерживать конкретно эту систему непросто. На это уходило много времени, что само по себе непозволительная роскошь. 
  5. Билд-конфигурации хранились на общем сервере с другими проектами, а так как этот проект достаточно разросся — было принято решение выделить под него отдельные сервисы (билд и деплоймент системы) и расположить это все на серверах заказчика.
  6. Отсутствие наглядности и централизованности. Какие версии модулей задеплоены на отдельно взятый сервер? Средняя частота обновлений? Какова актуальность конкретно взятого модуля? Ответы на эти и множество других вопросов нельзя было найти на поверхности.
  7. Неоптимальное использование ресурсов и устаревший процесс сборки и доставки кода.

Рисунок к пункту 3:

Обновление процесса CI/CD: подготовка и планирование — IT-МИР. ПОМОЩЬ В IT-МИРЕ 2020

На этапе планирования было принято решение использовать Teamcity в качестве билд системы, и Octopus в качестве деплоймент системы. «Железная» инфраструктура заказчика осталась неизменной: отдельные dedicated сервера под dev, test, staging и production, а также реселлерские сервера (в основном production окружения).

Заказчику был предоставлен Proof of Concept на примере одного из модулей, прописан план действий, проведены подготовительные работы (установка, настройка, и т.п.). Смысла описывать это, наверное, нет. Как устанавливать тимсити можно почитать на официальном сайте. Отдельно остановлюсь на некоторых сформулированных требованиях к новой системе:

  1. Простота в обслуживании со всеми вытекающими (бекапы, обновление, решение проблем, если возникают).
  2. Универсальность. В идеале, выработать общую схему, согласно которой собираются и доставляются все модули, и использовать ее в качестве шаблона.
  3. Минимизировать время на добавление новых билд-конфигураций и обслуживание. Клиенты добавляются/удаляются. Иногда возникает необходимость делать новые сетапы. Нужно, чтобы при создании нового модуля не возникало задержки с настройкой его доставки.

Примерно на этом моменте мы вспомнили о прекращении поддержки mercurial репозиториев битбакетом, и добавилось требование перенести репозиторий в git с сохранением веток и истории коммитов.

Подготовка: конвертация репозитория

Казалось бы, явно кто-то решил эту задачу до нас и вместо нас. Нужно просто найти готовое работающее решение. Fast export оказался не таким уж и fast. К тому же, не сработал. К сожалению, логов и скринов ошибок не осталось. Факт в том, что он не смог. Bitbucket не предоставляет собственного конвертора (а мог бы). Еще парочка нагугленных методов, и тоже мимо. Решил написать свои скрипты, все равно это не единственный mercurial репозиторий, в будущем пригодятся. Тут будут представлены ранние наработки (потому что они до сих пор остались в таком же виде). Логика работы выглядит примерно так:

  1. За основу берем расширение mercurial hggit. 
  2. Получаем список всех веток mercurial репозитория. 
  3. Преобразовываем имена веток (спасибо mercurial и разработчикам за пробелы в именах веток, отдельное спасибо за умляуты и прочие символы которые добавляют радости в жизнь).
  4. Делаем закладки (hg bookmark) и пушим в промежуточный репозиторий. Зачем? Потому что bitbucket, и потому что нельзя создать закладку с таким же именем как и название ветки (например, staging). 
  5. В новом (уже git) репозитории удаляем постфикс из названия ветки и мигрируем hgignore в gitignore. 
  6. Пушим в основной репозиторий.

Листинг команд согласно пунктам:

  1. $ cd /path/to/hg/repo
    $ cat << EOF >> ./.hg/hgrc
    [extensions]
    hggit=
    EOF
    
  2. $ hg branches > ../branches
    
  3. #!/usr/bin/env bash
    hgBranchList="./branches"
    sed -i 's/:.*//g' ${hgBranchList}
    symbolsToDelete=$(awk '{print $NF}' FS=" " ${hgBranchList} > sym_to_del.tmp)
     
    i=0
    while read hgBranch; do
      hgBranches[$i]=${hgBranch}
      i=$((i+1))
    done <${hgBranchList}
     
    i=0
    while read str; do
      strToDel[$i]=${str}
      i=$((i+1))
    done < ./sym_to_del.tmp
     
    for i in ${!strToDel[@]}
    do
      echo ${hgBranches[$i]} | sed "s/${strToDel[$i]}//" >> result.tmp
    done
     
    sed -i 's/[ t]*$//' result.tmp
    sed 's/^/"/g' result.tmp > branches_hg
    sed -i 's/$/"/g' branches_hg
    sed 's/ /-/g' result.tmp > branches_git
    sed -i 's/-/-///g' branches_git
    sed -i 's/-////g' branches_git
    sed -i 's//-///g' branches_git
    sed -i 's/---/-/g' branches_git
    sed -i 's/--/-/g' branches_git
    rm sym_to_del.tmp
    rm result.tmp
    
  4. #!/usr/bin/env bash
    gitBranchList="./branches_git"
    hgBranchList="./branches_hg"
    hgRepo="/repos/reponame"
     
    i=0
    while read hgBranch; do
      hgBranches[$i]=${hgBranch}
      i=$((i+1))
    done <${hgBranchList}
     
    i=0
    while read gitBranch; do
      gitBranches[$i]=${gitBranch}
      i=$((i+1))
    done <${gitBranchList}
     
    cd ${hgRepo}
    for i in ${!gitBranches[@]}
    do
      hg bookmark -r "${hgBranches[$i]}" "${gitBranches[$i]}-cnv"
    done
     
    hg push git+ssh://git@bitbucket.org:username/reponame-temp.git
    echo "Done."
    
  5. #!/bin/bash
    # clone repo, run git branch -a, delete remotes/origin words to leave only branch names, delete -cnv postfix, delete default branch string because we can't delete it
    repo="/repos/repo"
    gitBranchList="./branches_git"
    defaultBranch="default-cnv"
    while read gitBranch; do   gitBranches[$i]=${gitBranch};   i=$((i+1)); done < $gitBranchList
    cd $repo
    for i in ${!gitBranches[@]}; do git checkout ${gitBranches[$i]}-cnv; done
    git checkout $defaultBranch
    for i in ${!gitBranches[@]}; do
        git branch -m ${gitBranches[$i]}-cnv ${gitBranches[$i]}
        git push origin :${gitBranches[$i]}-cnv ${gitBranches[$i]}
        git push origin -u ${gitBranches[$i]}
    done
    
  6. #!/bin/bash
    # clone repo, run git branch -a, delete remotes/origin words to leave only branch names, delete -cnv postfix, delete default branch string because we can't delete it
    repo="/repos/repo"
    gitBranchList="./branches_git"
    defaultBranch="default"
    while read gitBranch; do   gitBranches[$i]=${gitBranch};   i=$((i+1)); done < $gitBranchList
    cd $repo
    for i in ${!gitBranches[@]}; do
        git checkout ${gitBranches[$i]}
        sed -i '1d' .hgignore
        mv .hgignore .gitignore
        git add .
        git commit -m "Migrated ignore file"
    done
    

Попробую объяснить смысл некоторых действий, а именно использование промежуточного репозитория. Изначально в репозитории после конвертации имена веток содержат постфикс «-cnv». Это связано с особенностью работы hg bookmark. Надо удалить этот постфикс, а также сделать gitignore файлы вместо hgignore. Все это дописывает историю, а следовательно увеличивает размер репозитория (причем необоснованно). В качестве еще одного примера могу привести следующее: попробуйте создать репозиторий, и запушить в него вместе с кодом бинарник на 300М первым коммитом. Потом добавить его в gitignore, и пушить без него. Он останется в истории. А теперь попробуйте удалить его из истории (git filter-branch). При определенном числе коммитов результирующий размер репозитория не уменьшится, а увеличится. Это решается оптимизацией, но на bitbucket ее инициировать нельзя. Она проводится только при импорте репозитория. Поэтому, все черновые операции проводятся с промежуточным, а после делается импорт в новый. Размер промежуточного репозитория в финале был 1.15G, а размер результирующего 350M. 

Заключение

Весь процесс миграции был разделен на несколько этапов, а именно:

  • подготовка (демка заказчику на примере одного билда, установка программного обеспечения, конвертация репозитория, обновление существующих конфигов ccnet);
  • настройка CI/CD для dev окружения и его тесты (параллельно остается рабочей старая система);
  • настройка CI/CD для оставшихся окружений (параллельно с существующим «добром») и тесты;
  • миграция dev, staging и test;
  • миграция production и переброс доменов со старых инстансов IIS на новые.

В данный момент завершается описание успешно выполненного подготовительного этапа. Какого-то грандиозного успеха тут достигнуто не было, разве что, существующая система не сломалась, и мигрирован mercurial репозиторий в git. Однако, без описания данных процессов будет отсутствовать контекст для более технических частей. В следующей части будет описан процесс настройки CI системы на базе teamcity.

Специально для сайта ITWORLD.UZ. Новость взята с сайта Хабр