В 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 процессы:
- В качестве билд-системы использовался CruiseControl.NET. Работать с ним — сомнительное удовольствие в принципе. XML конфиги на несколько экранов, куча зависимостей и линковок конфигов друг на друга, отсутствие современных решений современных проблем.
- У некоторых разработчиков (в основном лидов) на проекте должен быть доступ к билд серверам, и они любят иногда менять конфиги ccnet которые менять не следует. Догадайтесь, что происходит потом. Нужно иметь простой и удобный менеджмент прав в CI системе, не отбирая при этом у разработчиков доступ к серверу. Проще говоря, нет текстового конфига — некуда лезть шаловливыми руками.
- В качестве деплоймент-системы использовался… Тоже CCNET, но на клиентской стороне в связке с git. Процесс билда и доставки кода выглядел примерно так (см. картинку).
- Экспериментально выяснилось, что поддерживать конкретно эту систему непросто. На это уходило много времени, что само по себе непозволительная роскошь.
- Билд-конфигурации хранились на общем сервере с другими проектами, а так как этот проект достаточно разросся — было принято решение выделить под него отдельные сервисы (билд и деплоймент системы) и расположить это все на серверах заказчика.
- Отсутствие наглядности и централизованности. Какие версии модулей задеплоены на отдельно взятый сервер? Средняя частота обновлений? Какова актуальность конкретно взятого модуля? Ответы на эти и множество других вопросов нельзя было найти на поверхности.
- Неоптимальное использование ресурсов и устаревший процесс сборки и доставки кода.
Рисунок к пункту 3:
На этапе планирования было принято решение использовать Teamcity в качестве билд системы, и Octopus в качестве деплоймент системы. «Железная» инфраструктура заказчика осталась неизменной: отдельные dedicated сервера под dev, test, staging и production, а также реселлерские сервера (в основном production окружения).
Заказчику был предоставлен Proof of Concept на примере одного из модулей, прописан план действий, проведены подготовительные работы (установка, настройка, и т.п.). Смысла описывать это, наверное, нет. Как устанавливать тимсити можно почитать на официальном сайте. Отдельно остановлюсь на некоторых сформулированных требованиях к новой системе:
- Простота в обслуживании со всеми вытекающими (бекапы, обновление, решение проблем, если возникают).
- Универсальность. В идеале, выработать общую схему, согласно которой собираются и доставляются все модули, и использовать ее в качестве шаблона.
- Минимизировать время на добавление новых билд-конфигураций и обслуживание. Клиенты добавляются/удаляются. Иногда возникает необходимость делать новые сетапы. Нужно, чтобы при создании нового модуля не возникало задержки с настройкой его доставки.
Примерно на этом моменте мы вспомнили о прекращении поддержки mercurial репозиториев битбакетом, и добавилось требование перенести репозиторий в git с сохранением веток и истории коммитов.
Подготовка: конвертация репозитория
Казалось бы, явно кто-то решил эту задачу до нас и вместо нас. Нужно просто найти готовое работающее решение. Fast export оказался не таким уж и fast. К тому же, не сработал. К сожалению, логов и скринов ошибок не осталось. Факт в том, что он не смог. Bitbucket не предоставляет собственного конвертора (а мог бы). Еще парочка нагугленных методов, и тоже мимо. Решил написать свои скрипты, все равно это не единственный mercurial репозиторий, в будущем пригодятся. Тут будут представлены ранние наработки (потому что они до сих пор остались в таком же виде). Логика работы выглядит примерно так:
- За основу берем расширение mercurial hggit.
- Получаем список всех веток mercurial репозитория.
- Преобразовываем имена веток (спасибо mercurial и разработчикам за пробелы в именах веток, отдельное спасибо за умляуты и прочие символы которые добавляют радости в жизнь).
- Делаем закладки (hg bookmark) и пушим в промежуточный репозиторий. Зачем? Потому что bitbucket, и потому что нельзя создать закладку с таким же именем как и название ветки (например, staging).
- В новом (уже git) репозитории удаляем постфикс из названия ветки и мигрируем hgignore в gitignore.
- Пушим в основной репозиторий.
Листинг команд согласно пунктам:
$ cd /path/to/hg/repo $ cat << EOF >> ./.hg/hgrc [extensions] hggit= EOF
$ hg branches > ../branches
#!/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
#!/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."
#!/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
#!/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.