Это вторая статья из цикла об обновлении CI/CD процессов. До этого момента осуществлялась подготовка к настройке новых средств, а именно: планирование, ежедневные митинги, решение разногласий, в общем, все без чего не получится грамотно построить рабочий процесс. И вот, все вопросы улажены, мы уверены что можем продолжать разработку, и наш код не канет в чёрную дыру с началом лета.
Напомню оглавление списка статей, а также краткие итоги предыдущей части.
Часть 1: что есть, почему оно не нравится, планирование, немного bash. Я бы назвал эту часть околотехнической.
Часть 2: teamcity.
Часть 3: octopus deploy.
Часть 4: за кадром. Неприятные моменты, планы на будущее, возможно FAQ. Скорее всего, тоже можно назвать околотехнической.
Предоставили заказчику proof-of-concept новой системы доставки обновлений, обозначили недостатки существующей системы, выбрали основу для новой, составили план перехода и конвертировали в git наш mercurial репозиторий. Также, в предыдущей части были описаны особенности проекта, которые окажут влияние на весь последующий процесс.
Как уже было сказано ранее, существующее решение не соответствовало современным требованиям. Изначально, скорее всего, был настроен один билд в ccnet. Тот самый, первый билд, с которого все началось (как большой взрыв, ага). Потом, видимо, человек который это все настраивал, нажал ctrl+с, ctrl+v, подправил пару строк, и получил новую конфигурацию. И за все последующее время, это было сделано достаточное количество раз, чтобы исходники на билд сервере занимали больше 60G. И это только исходники! А еще есть логи, артефакты сборки, временные файлы, и так далее. Только это заставляло задуматься. Взглянув на старую систему можно было увидеть следующее: для каждого модуля выполнялся шаг build. Собиралось по факту одно и то же ядро. И эта сборка хранилась для каждого модуля (вместе с исходниками) на сервере. Результаты сборки (одинаковые на 90%) пушились в локальные репозитории (git deployment), и тоже, естественно, занимали место. От этого можно и нужно было уходить.
Общие настройки
На данном этапе предполагается, что уже есть настроенный teamcity и octopus. На этапе установки и настройки подробно не останавливаемся. Для удобства, прописываем url и api-token октопуса в env teamcity, который, кстати, для корневого проекта выглядит в результате так:
env.apiUserName и env.apiUserPassword это доступы к api пользователю teamcity (будут нужны позже), env.buildPath и env.sourcePath — пути по умолчанию к файлам билда и исходников соответственно, env.octoApiKey и env.octoUrl — токен и url октопуса. К ним еще env.teamcityUrl добавился. Ну, и env.tt.exe — путь к утилите text transform. Эта утилита и генерирует все конфигурации.
Из сторонних плагинов установлен только Octopus Deploy integration.
Чтобы не перегружать и так огромное количество текста, подробно будут рассмотрены только те шаги, в которых есть что-то стоящее внимания. Предполагается, что всё остальное понятно исходя из представленных данных и знаний читателя. В любом случае, если у вас есть вопросы — я с радостью на них отвечу.
Разбиение на проекты, версионность, имена
В результате получилась такая схема проектов (картинкой, потому что проблема с отображением многоуровневого списка):
Версировать пакеты будем следующим образом: major.minor.patch, где major пока что 1, minor — build counter из конфигурации Build, patch — build counter для конфигурации Deploy (для конфигурации Build не нужен, поэтому всегда 0).
Примечание: тут описана старая версионность. Сейчас переделываем ее так, чтобы она основывалась на git tag.
Например, здесь 1 — major, 95 — minor, и 1 — patch.
В новом процессе для модулей будет один билд который потом будет деплоиться в каждый модуль. То-есть, исходники хранятся в единственном экземпляре для каждого окружения, результат билда тоже, так как он общий для всех модулей. Уникальные конфигурации и библиотеки пакуются в индивидуальный пакет на этапе деплоя. Это позволит эффективно использовать место на диске и сократить время доставки каждого пакета.
На этапе подготовки был отдельный короткий митинг, посвященный конвенции именования конфигурационных файлов. Раньше было (просто было), но не всегда одинаково. К тому же, в имени конфигурации не всегда содержалось достаточное количество информации. В результате все названия конфигураций имели следующий вид:
ConfigurationType.Environment.ModName.config, где ConfigurationType может быть AppSettings, Web, etc. Environment — development, test, staging, production. ModName — уникальное имя модуля.
Модули
Все проекты Modules работают по одному принципу.
В каждом окружении есть конфигурация Build, которая:
- Выполняет nuget restore (NuGet installer, nuget restore)
- Генерирует конфиги (Command Line, text transform)
- Билдит солюшен (Visual studio sln)
- Забирает файлы из env.buildPath в zip и пушит его в октопус (Octopus deploy: push packages)
- Создание релиза (без деплоя) (Octopus deploy create release)
- Сбрасывает patch версии (powershell)
Также есть конфигурации deploy, которые работают на основе шаблона и делают следующее:
- Забирает конфиги которые были сгенерированы на этапе билда (шаг 2) (Octopus deploy push packages)
- Деплоит core release (который был создан в шаге 6 билда) (Octopus deploy: deploy release)
- Деплоит individual release (который был создан в шаге 1 текущей конфигурации) (Octopus deploy: deploy release)
- Отображает в логе список всех доменов для данного модуля (шаг для удобства тестировщика и разработчика). (Powershell).
Для каждой deploy конфигурации добавлена в зависимость конфигурация build. Это дает возможность при запуске деплоя автоматически запускать билд если он не актуален, а также возможность сбрасывать patch версии.
Конфигурация Deploy_all. По факту это build chain в котором описано что сначала нужно запустить build если он не актуален, а затем параллельно задеплоить все моды.
Выглядит примерно так:
Разберем некоторые шаги чуть более подробно.
Build.General settings
Из необычного только build number format. Будет объяснено позднее.
Build.env
Интерес тут представляют:
env.coreProjectName — устанавливается для всего проекта. Нужен для идентификации проекта в octopus.
env.Environment — устанавливается для подпроекта Modules. Нужен для выбора правильных конфигов в зависимости от окружения.
env.octoChannel — канал в октопусе в который попадает пакет. Совпадает с env.Environment.
env.serviceName — имя солюшена. В основном, переменная добавлена для удобства визуализации.
env.tenantRoleTag — тег тенанта в октопусе. Подробнее в разделе octopus.
Build.4
Добавляем в архив с именем %env.serviceName%.%build.number%.zip файлы из директории с результатом билда, исключая при этом все конфигурации, и dll из папки bin/native. Последние один раз вручную добавились на веб сервера и больше их трогать никто не будет. При необходимости обновлять их тоже будут вручную (для этого планируется написать отдельный скрипт, но будем честны). Это связано с тем, что эти библиотеки «держит» пул IIS, и для успешного деплоя его необходимо останавливать, а потом запускать, что занимает некоторое время. Исключив же эти библиотеки из деплоя мы можем пропустить шаг остановки пула, что сократит время деплоя, и соответственно, даунтайма.
Build.5
Думаю, тут все понятно, за исключением того, почему поле Environment пустое. В какой env попадает пакет, октопус принимает решение исходя из pre-release tag (настраивается в каналах). Этот тег содержится в %build.number% (тот самый -staging на скрине в Build.General). Так в данном случае оказалось удобнее. Также стоит обратить внимание на чекбокс «Show deployment process». Если он не установлен — тимсити считает билд успешным как только октопус начал (но не закончил!) работу. То-есть, если чекбокс не установлен и на этапе деплоя что-то пошло не так — не заглянув в интерфейс октопуса мы об этом не узнаем.
Build.6
Ничего необычного, просто powershell script и сведения из документации teamcity.
$pair = "%env.apiUserName%:%env.apiUserPassword%"
$encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair))
$basicAuthValue = "Basic $encodedCreds"
$depUri="%env.teamcityUrl%/app/rest/buildTypes?locator=snapshotDependency:(from:(build:(id:%teamcity.build.id%)),includeInitial:false)"
# Get all dependencies of main build
$result = (Invoke-RestMethod -Headers @{'Origin'='http://localhost:8090'; "Authorization"="$basicAuthValue"} -Uri $depUri)
foreach ($i in $result.buildTypes.buildType.Count) { $buildId += $result.buildTypes.buildType.id }
$buildId = $buildId | Where-Object { $_ -ne 'Module_DeployAll' }
# Reset all child build counters to 1. This build counters are patch versions in every build. (1.build.patch)
for ($i=0; $i -lt $buildId.Count; $i++) {
$buildCounterUri = "%env.teamcityUrl%/httpAuth/app/rest/buildTypes/"+$buildId[$i]+"/settings/buildNumberCounter"
Invoke-RestMethod -Method Put -Headers @{'accept'='text/plain'; 'Origin'='http://localhost:8090'; "Authorization"="$basicAuthValue"} -Uri $buildCounterUri -ContentType "text/plain" -Body 1
}
Тут нам нужен api пользователь в тимсити (именно для него созданы env). Мы авторизуемся, забираем все конфигурации, которые зависимы от конфигурации Build, и сбрасываем их build counter в 1. То-есть по факту, для каждого деплоймента patch version это показатель того сколько раз деплоился один и тот же билд для конкретного модуля. Если там не 1 — значит что-то пошло не так с процессом деплоя или работы модуля на текущей версии, и требует внимания.
Deploy.General
Формат версии: 1.%dep.Module_Build.build.counter%.%build.counter%-staging, где
%dep.Module_Build.build.counter% — системная переменная teamcity значение которой соответствует build counter соответствующей конфигурации Build (должна быть добавлена в зависимости). По факту тут 1 — major версия, %dep.Module_Build.build.counter% — minor, а %build.counter% — patch. staging — это pre-release tag.
Deploy.Env
При добавлении нового модуля добавляется только конфигурация для его деплоя и указывается значение переменной env.modName. Все остальные переменные наследуются от родительских проектов. Переменная env.tenantModTag для октопуса формируется из env.modName.
Deploy.2
Деплой core пакета.
Release number latest — использовать последний доступный релиз для этого окружения.
Tenant tag — тег клиента/организации. Подробнее в разделе octopus.
Additional cli parameters. Тут мы указываем канал в который попадет наш пакет, а также дополнительные параметры. Об этом тоже будет сказано отдельно в разделе octopus.
Deploy.3
Аналогично Deploy.2, только деплоится индивидуальный пакет модуля.
Deploy.4
Данный шаг также будет подробно описан далее, потому что получает данные из octopus.
Конфигурации для Company website и Special module построены по тому же принципу, и основные моменты в них такие же, поэтому описывать их так же подробно не вижу смысла. Они едины, их не надо деплоить много раз, и по факту там происходит nuget restore, text transform, msbuild, octopus push, octopus create release + deploy.
Подробнее об уникальных конфигурациях. Для некоторых модов может не быть каких-то окружений (например, staging), они должны деплоиться на другие сервера, или надо вручную устанавливать идентификатор клиента (взять базовый конфиг и поменять в нем некоторые поля с помощью powershell). То-есть, работают они по тому же принципу что и основные модули. Их уникальность состоит в том, что для них добавляется/изменяются шаги, либо меняется deployment сервер и они не вписываются в шаблон, поэтому на их создание уходит чуть больше времени. Благо, таких мало.
Teamcity: Итоги
Внедрение teamcity позволило более оптимально подходить к процессу сборки приложения. Теперь она занимает гораздо меньше времени: вместо того чтобы каждый раз билдить один и тот же код, мы билдим его один раз. Также мы более оптимально используем дисковое пространство, сетевой трафик и вычислительное время. Раньше количество пакетов для деплоя (репозиториев с артефактами) равнялось количеству модулей. Каждый пакет весил примерно 100M в сжатом виде. Сейчас у нас есть количество пакетов на один больше, чем количество модулей, причём один пакет весит примерно те же 100М, а остальные около 500К. Плюс более дружественный интерфейс, удобные логи, а самое главное — сокращение времени на обслуживание билд системы и добавление конфигураций. Сейчас добавление нового модуля занимает от 15 минут до получаса.
Отдельно стоит сказать про шаблоны, так как именно шаблонизация позволяет нам экономить время на создание новых конфигураций и их дальнейшее обслуживание. Все конфигурации для взаимодействия с octopus (deploy) построены на базе одного шаблона. Во первых, это позволяет унифицировать, а следовательно упростить процесс. Во вторых, как уже и было сказано, позволяет экономить время на добавление новых конфигураций: подключили шаблон, изменили одну переменную, готово. И самое главное, шаблоны упрощают обслуживание. Все изменения сделанные в шаблоне наследуются конфигурациями на его основе. А значит, если нам нужно добавить какой-то шаг во все конфигурации сразу, нам не надо прокликивать их все, достаточно добавить этот шаг в шаблон.
Естественно, процесс настройки teamcity проходил параллельно с настройкой octopus. Просто в тексте это невозможно описать параллельно, так как велик шанс возникновения путаницы. Поэтому, описание было разделено, и в данной части описаны только шаги обеспечивающий CI. Процесс настройки octopus будет описан в следующей.