![](https://habrastorage.org/webt/59/dd/21/59dd21a43ef2a189905765.jpeg)
Сегодня я хочу рассказать о системе деплоя Octopus Deploy. На данный момент на Хабре есть всего одна вводная статья на эту тему, поэтому в своем материале я хочу расширить описание системы, подробнее рассказать о таких важных понятиях как «жизненные циклы» (lifecycles) и «каналы» (channels), а также о том, как мы внедрили и используем Octopus в своей работе.
Кто мы
Мы — отдел собственной разработки Tinkoff.ru. Команда, в которой я работаю, занимается разработкой облачной платформы виртуальных рабочих мест WebOffice. В настоящий момент на бэке у нас Asp.Net MVC-приложение и REST API на базе ServiceStack, на фронте — SPA на Angular. Помимо этого имеются SOAP-сервисы для интеграции с другими банковскими системами, джобы для выполнения периодических задач и некоторое количество микросервисов на .Net Core 2.0.
Проблемы старого процесса деплоя
Когда я пришел в Тинькофф, деплой системы проводился с помощью TeamCity: использовался MSDeploy и n-ное количество powershell-скриптов различной сложности. Билд конфигурации, отвечающий за сборку проекта, и деплой были сгруппированы по имевшимся на тот момент контурам: QA и Prod.
Старый процесс сборки и деплоя на каждый контур можно условно представить следующим образом: собираем проект для заданного окружения (применяем соответствующие трансформации xml конфигов) > собираем полученные артефакты в несколько zip-файлов (по одному на каждое отдельно развертываемое приложение + скрипты миграции БД + скрипты деплоя) > деплоим на заданные для данного окружения сервера.
Так как в работе мы используем по несколько серверов для отказоустойчивости и распределения нагрузки, то процесс деплоя занимал приличное количество времени, потому что шёл последовательно по приложениям и серверам (писать на PowerShell параллельные асинхронные задачи довольно болезненно). С добавлением дополнительных контуров QA2 и Preprod количество билд-конфигураций росло, управление настройками деплоев через разрозненные параметры усложнялось.
Цель
Мы хотели унифицировать процесс деплоя на различные контуры, по возможности ускорить его, а также стандартизировать процесс доставки релизов в зависимости от их типа в принятой у нас системе версионирования: мажорные релизы должны проходить контуры QA > Pre > Prod, минорные — QA2 > Pre > Prod, хотфиксы — Pre > Prod.
Во всех этих хотелках нам помог Octopus Deploy. Вот основные плюсы, которые мы получили, внедрив его в наш процесс доставки релизов:
- единый процесс деплоя всего приложения для всех контуров;
- единое место хранения переменных для инфраструктурных настроек приложения;
- параллельный деплой на несколько серверов;
- сведение большого разнообразия билд-конфигураций в TeamCity до одной;
- нормальный (правильный) жизненный цикл сборки (когда одни и те же бинарники деплоятся сперва на QA, потом на Preprod и потом на Prod; в варианте с TeamCity для каждого окружения была настроена отдельная билд-конфигурация);
- упрощение кастомных PowerShell-скриптов за счёт выкидывания remoting-обвязок;
- наглядность состояния окружений, процесса деплоя, возможность гибкой настройки конретных деплоев;
- гибкое разграничение прав доступа на управление релизами.
Установка и настройка
Установка самой системы проста и прямолинейна: из внешних зависимостей необходим только MS SQL Server для хранения базы данных. Плюс к этому на каждый сервер, на который вы собираетесь деплоить, необходимо установить агент Octopus Tentacle.
Его установка отлично автоматизируется, что убирает лишнюю ручную работу и позволяет подготавливать новые сервера к деплою. Для этого мы используем Ansible, и после установки самого приложения его настройка выполняется вот таким вот powershell-скриптом:
configure_tentacle.ps1
$tentacle = 'C:\Program Files\Octopus Deploy\Tentacle\Tentacle.exe'
& $tentacle create-instance --instance "Tentacle" --config "C:\Octopus\Tentacle.config" --console
& $tentacle import-certificate --instance "Tentacle" -f "C:\ansible_temp\tentacle_cert.txt" --console
& $tentacle configure --instance "Tentacle" --reset-trust --console
& $tentacle configure --instance "Tentacle" --home "C:\Octopus" --app "C:\Octopus\Applications" --port "10933" --console
& $tentacle configure --instance "Tentacle" --trust "{{ octopus_thumbprint }}" --console
& netsh advfirewall firewall add rule "name=Octopus Deploy Tentacle" dir=in action=allow protocol=TCP localport=10933
& $tentacle register-with --instance "Tentacle" --server "{{ octopus_server }}" --apiKey="{{ octopus_api_key }}" --role "{{ octopus_role }}" --environment "{{ octopus_env }}" --comms-style TentaclePassive --console
& $tentacle service --instance "Tentacle" --install --start --console
В результате, после создания нового сервера, он автоматически регистрируется в октопусе и на него сразу же можно деплоить. Очень удобно.
Жизненные циклы
Одной из ключевых сущностей в Octopus Deploy являются жизненные циклы (lifecycles). Они используются как для автоматического промотирования релиза между окружениями, так и для ограничения окружений, на которые может быть развернуто приложение до прохождения соответствующего тестирования.
Например, для мажорных версий жизненный цикл системы у нас определен как QA > Pre > Prod. Это означает, что мы не сможем задеплоить версию в продакшен до того, как она побывает на тестовом и препрод контурах. С другой стороны, жизненный цикл патч-версий короче: Pre > Prod, т.к. они тестируются сразу на препроде.
![](https://habrastorage.org/webt/59/d3/b6/59d3b66a4245a438650839.png)
Каналы
С помощью каналов (channels) в Octopus можно независимо развертывать несколько версий одного проекта. Помимо правил, ограничивающих версии пакетов, для каждого канала можно указать используемый для промотирования релизов жизненный цикл, настроить процесс развертывания (т.к. у каждого шага имеется настройка, для каких каналов он должен быть выполнен), а также определить значения переменных. Каждый создаваемый в Octopus Deploy релиз обязательно относится к какому-либо каналу.
В нашем проекте у нас есть каналы для мажорных, минорных, патч-версий, а также специализированные каналы для прямого вывода на бой хотфиксов и развертывания экземпляра системы для интеграционного тестирования.
Основная прелесть заключается в возможности автоматического создания релизов в нужных каналах. Для этого в свойствах канала мы указываем диапазон версий пакетов (а также можем использовать тэги SemVer), которые нужно использовать для создания релиза в этом канале. Octopus автоматически выберет пакеты, имеющие самую последнюю версию.
Предположим, что в данный момент ведется тестирование версий V10.1 и V11.0. После сборки проекта из ветки V10.1 TeamCity зальет собранные пакеты (версия пакетов будет 10.1.0.xxxx) в Octopus и вызовет создание релиза в канале «минорных» релизов. При этом в релиз попадут именно пакеты версии 10.1.0.xxxx, несмотря на то, что в Octopus уже есть более новые пакеты с версией 11.0.0.yyyy, т.к. в Version Range у канала «минорных» релизов указан диапазон [10.1.0.00000, 10.1.0.99999).
![](https://habrastorage.org/webt/59/d3/b6/59d3b6fd7f261768490711.png)
Таким образом, различные версии приложения отлично сосуществуют друг с другом в каналах и следуют своему собственному процессу развертывания на различные окружения.
Вот пример того, как это выглядит на панели управления проектом:
![](https://habrastorage.org/webt/59/dd/f1/59ddf1b1e9b44209636967.png)
Важно! Если вы добавляете новые шаги в процесс деплоя, добавьте новые пакеты в правила каналов. Если для какого-либо пакета в канале не задано ограничение по версии, Octopus будет использовать самую последнюю версию пакета. Может случиться так, что вы задеплоите приложение версии 10.1, но один пакет будет, например, версии 11.0, если он уже есть в Octopus.
Создание NuGet пакетов
Создавать пакеты для деплоя во время сборки проектов можно с помощью инструмента Octopack. Он устанавливается в необходимые проекты как NuGet-пакет, и если вас устраивают настройки по умолчанию, больше делать ничего не нужно.
Octopack на основе типа проекта (консольное приложение, веб-приложение, windows service) сам выбирает файлы, которые потребуются при деплое. Если же необходима более тонкая настройка, какие файлы должны попасть в пакет, можно создать кастомный файл .nuspec.
Интеграция с TeamCity
Но самое интересное начинается при автоматизации процессов создания (и деплоя) релизов после сборки проекта на TeamCity. Вот как этот процесс выглядит у нас:
- разработчик после ревью мерджит свою задачу в версионную ветку;
- TeamCity собирает проект, определяет канал, в который должна попасть сборка и вызывает создание релиза в требуемом канале в Octopus Deploy;
- тестировщик по готовности нажимает кнопку «Deploy» в панели управления Octopus и разворачивает сборку в соответствующем этому каналу тестовом окружении.
Часть задач по подготовке NuGet-пакетов, их заливке в Octopus и созданию релиза очень легко сделать, установив плагин Octopus Deploy для TeamCity. Для некоторых других операций (например, автоматического обновления Version Range у канала) мы написали небольшие powershell-скрипты.
Guided Failures
Если в процессе деплоя на одном из шагов происходит ошибка, весь деплой останавливается и помечается упавшим. Такое поведение не всегда актуально: иногда мы готовы смириться с проблемами на некоторых шагах деплоя. Для этих случаев существует режим под названием Guided Failures.
Если он включен, и во время деплоя происходит ошибка, Octopus приостанавливает все операции и отображает специальное окно. В нем можно указать результаты расследования и решить, нужно ли продолжать процесс деплоя или нет. В истории деплоя останется пометка о том, кто, когда и какое решение принял.
Скрипты деплоя в пакетах
Иногда перед деплоем приложений и сервисов мы хотим выполнить дополнительные действия. Например, убедиться, что приложение сможет получить доступ к порту при запуске от имени сервисной учетной записи. Это можно сделать, используя дополнительные установочные скрипты, которые могут написаны как в веб-интерфейсе Octopus, так и быть частью пакета разворачиваемого приложения.
В скриптах можно использовать переменные октопуса, которые будут проброшены в соответствии с правилами именования переменных в PowerShell (из названий переменных будут удалены точки и другие специальные символы, таким образом переменная октопуса MyApp.ConnectionString будет доступна в скрипте как $MyAppConnectionString).
![](https://habrastorage.org/webt/59/dd/f4/59ddf4688efeb578550866.png)
Пример пре-деплой скрипта, который удаляет и добавляет резервирование порта для одного из наших сервисов
Retention policy
Чтобы однажды не оказаться в ситуации с полностью заполненным жестким диском, в Octopus предусмотрен механизм для удаления пакетов, которые использовались в старых релизах и больше не нужны.
Политика хранения пакетов описывается на уровне жизненного цикла, но ее можно переопредить для отдельных его фаз. Так, например, для всех фаз деплоя приложения на тестовые окружения мы храним только одну последнюю версию пакета.
![](https://habrastorage.org/webt/59/dd/f4/59ddf49845a92798819747.png)
Для боевого окружения мы переопределяем эту настройку и храним пакеты для пяти последних релизов (на случай, если вдруг придется откатываться). Также можно указать, для каких релизов хранить пакеты на самих машинах. В случае отката можно провести деплой быстрее, не тратя время на повторное скачивание пакетов с сервера Octopus.
![](https://habrastorage.org/webt/59/dd/f4/59ddf4985e951069256202.png)
Дополнительные ништяки
В этом разделе хочу поделиться некоторыми powershell-скриптами, которые мы используем у себя в системе. Вы можете использовать их как есть или как пример для реализации собственных идей.
Генерация release notes
# Inspired by http://blogs.lessthandot.com/index.php/uncategorized/access-git-commits-during-a-teamcity-build-using-powershell/
function Get-CommitsFromGitLog([string] $StartCommit, [string] $EndCommit){
$taskRegex = "(WOF-\d+)"
$Cmd = "& ""%vcsroot.agentGitPath%"" log --pretty=format:""%an: %s<br/>"" --encoding=cp866 --no-merges $StartCommit...$EndCommit"
$Result = (Invoke-Expression $Cmd) -replace $taskRegex,'<a href="https://jira.tcsbank.ru/browse/$1">$1</a>'
return $Result
}
function Get-TeamCityLastSuccessfulRun(
[string] $TeamCityUrl,
[string] $TeamCityBuildTypeId,
[string] $TeamCityUsername,
[string] $TeamCityPassword,
[string] $TeamCityBranchName)
{
$Credentials = "$($TeamCityUsername):$($TeamCityPassword)"
$AuthString = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$Credentials"))
$Url = "$($TeamCityUrl)/app/rest/buildTypes/id:$($TeamCityBuildTypeId)/builds/branch:name:$($TeamCityBranchName),status:SUCCESS"
$Content = Invoke-WebRequest "$Url" -Headers @{"Authorization" = "Basic $AuthString"} -UseBasicParsing
return $Content
}
$Run = Get-TeamCityLastSuccessfulRun
-TeamCityUrl "%teamcity.serverUrl%" `
-TeamCityBuildTypeId "%system.teamcity.buildType.id%" `
-TeamCityUsername "%system.teamcity.auth.userId%" `
-TeamCityPassword "%system.teamcity.auth.password%" `
-TeamCityBranchName "%teamcity.build.branch%"
$LatestCommitFromRun = (Select-Xml -Content "$Run" -Xpath "/build/revisions/revision/@version").Node.Value
$CommitsSinceLastSuccess = Get-CommitsFromGitLog -StartCommit "$LatestCommitFromRun" `
-EndCommit "%build.vcs.number%"
$CommitsSinceLastSuccess | Out-File releasenotes.txt -Force -Encoding utf8
Обновление настроек каналов (API)
function UpdateChannel ([string]$OctopusServer, [string]$ApiKey, [string]$ChannelId, [string]$MinVersion, [string]$MaxVersion)
{
$url = "$OctopusServer/api/{0}/{1}"
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("X-Octopus-ApiKey", $ApiKey)
$channel = Invoke-RestMethod -Method Get -Uri ($url -f "channels", $ChannelId) -Headers $headers -ContentType 'application/json;charset=utf-8'
$channel.Rules[0].VersionRange = "[{0},{1})" -f $MinVersion, $MaxVersion
$utf8encodedString = [System.Text.Encoding]::UTF8.GetBytes(($channel | ConvertTo-Json -Depth 3))
Invoke-RestMethod -Method Put -Uri ($url -f "channels", $ChannelId) -Headers $headers -Body $utf8encodedString
}
$minVersion = ("%CalculatedBuildBranch%").Trim("V") + ".00000"
$maxVersion = ("%CalculatedBuildBranch%").Trim("V") + ".99999"
if("%TargetChannel%" -eq "%PatchChannelName%") {
UpdateChannel %OctopusServer% %OctopusApiKey% %PatchChannelId% $minVersion $maxVersion
}
if("%TargetChannel%" -eq "%MinorChannelName%") {
UpdateChannel %OctopusServer% %OctopusApiKey% %MinorChannelId% $minVersion $maxVersion
}
if("%TargetChannel%" -eq "%MajorChannelName%") {
UpdateChannel %OctopusServer% %OctopusApiKey% %MajorChannelId% $minVersion $maxVersion
}
Заключение
Мы очень рады, что год назад начали использовать Octopus Deploy, и после непродолжительного периода тестовой эксплуатации полностью перешли на него для развертывания системы на всех наших окружениях.
Проект активно развивается, минорные релизы выходят чуть ли не каждую неделю. В последних версиях Octopus Deploy научился деплоить .net core и mono-приложения на linux-машины, разворачивать docker-контейнеры, а в последнем релизе 3.17 добавили первоклассную поддержку деплоя Java приложений на сервера приложений Tomcat, RedHat JBOSS и Wildfly.
В следующей статье я расскажу про multi-tenant deployments, которые мы используем для организации процесса развертывания фича бранчей на отдельные виртуалки, автоматически поднимаемые в локальном облаке OpenStack, для проведения изолированного тестирования отдельных задач.
Комментарии (2)
Bounz Автор
19.10.2017 09:10Да, в нашей команде мы пока деплоим только в Windows окружения, но вот наши соседи деплоят .Net Core приложения в Linux, и так как они изначально строили свою систему на основе микросервисов, то сейчас они деплоят через октопус более 30 сервисов. В общем виде это выглядит как пуш пакета на сервер + bash-скрипт для его распаковки и перенастройки сервиса.
Насчет CI — да, Octopus его не имеет, это система для деплоев, не для сборки и запуска юнит/интеграционных тестов и заменить им всё не получится. У нас вместо Jenkins TeamCity, соотвественно CI часть проходит на нём, а вопросами деплоя занимается Octopus.
logalize
Спасибо! Довольно интересно. На первый взгляд вроде все понятно.
Однако есть пара вопросов сходу:
— конкретно вы используете сейчас эту связку только для Win окружений? Там все понятно, powershell, powershell и снова powershell.
Да, я видел, что уже есть поддержка nix, но как это сейчас работает? И работает-ли у вас на конкретных проектах?
А связку nix + Jenkins + Nexus + Ansible, а еще поверх Octopus уже пробовали? Как я понял Octopus удобен в плане оркестрации ЖЦ, но собственного CI у него нет? Можно-ли тот же Jenkins им заменить?
Учитывя, что у Jenkins того же есть огромное множество плагинов для CI и AD.
Заранее извиняюсь за структуру вопросов :-)