Описание инфрастуктуры и процесса деплоя приложения одного немецкого производителя приятноалкогольной продукции.
Работает на UmbracoCMS, .NET, язык – C#, использует две Azure (MS) SQL базы – одну для хранения настроек, вторую – для пользовательских данных.
Для приложения имеются две различные ресурс-группы – Development и Production, при этом у Production имеется Staging swap-slot (у Dev – тоже, но он пока не используется). Так же каждая группа имеет свой Storage Account с контейнерами для статики приложения.
Деплой интересен использованием MSBuild
и MSDeploy
.
Тем, кто читал пост Azure: почему никогда – будет проще представить себе процесс подготовки всего, описанного ниже.
Вообще – в посте много ссылок на предыдущие материалы блога, написанные под “вдохновлением” от работы с Microsoft Azure.
Содержание
Репозиторий, код
Код хранится в Github, в приватном репозитории.
Код самой CMS выглядит так:
$ ls -l JaegerPlatform.CMS total 440 drwxrwxr-x 2 setevoy setevoy 4096 жов 14 10:38 App_Browsers drwxrwxr-x 3 setevoy setevoy 4096 жов 14 10:38 App_Code drwxrwxr-x 3 setevoy setevoy 4096 жов 14 10:38 App_Data ... drwxrwxr-x 5 setevoy setevoy 4096 жов 14 10:38 Content drwxrwxr-x 2 setevoy setevoy 4096 жов 14 10:38 Controllers -rw-rw-r-- 1 setevoy setevoy 426 жов 14 10:39 crossdomain.xml drwxrwxr-x 3 setevoy setevoy 4096 жов 14 10:39 css -rw-rw-r-- 1 setevoy setevoy 163 жов 14 10:39 default.aspx ... -rw-rw-r-- 1 setevoy setevoy 166165 жов 14 10:38 JMPlatform.CMS.csproj ... drwxrwxr-x 3 setevoy setevoy 4096 жов 14 10:38 Models -rw-rw-r-- 1 setevoy setevoy 3680 жов 14 10:39 packages.config drwxrwxr-x 3 setevoy setevoy 4096 жов 14 10:38 Platform drwxrwxr-x 3 setevoy setevoy 4096 жов 14 10:38 Properties .... drwxrwxr-x 25 setevoy setevoy 4096 жов 14 10:39 umbraco drwxrwxr-x 38 setevoy setevoy 4096 жов 14 10:39 umbraco_client ... -rw-rw-r-- 1 setevoy setevoy 30203 жов 14 10:39 web.config -rw-rw-r-- 1 setevoy setevoy 1256 жов 14 10:38 Web.Debug.config -rw-rw-r-- 1 setevoy setevoy 5024 жов 14 10:38 Web.Release.config -rw-rw-r-- 1 setevoy setevoy 3690 жов 14 10:38 Web.Staging.config drwxrwxr-x 3 setevoy setevoy 4096 жов 14 10:39 xslt
Сейчас нам интересен файл web.config
:
$ head -n 5 web.config <?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="urlrewritingnet" restartOnExternalChanges="true" requirePermission="false" type="UrlRewritingNet.Configuration.UrlRewriteSection, UrlRewritingNet.UrlRewriter" /> <section name="microsoft.scripting" type="Microsoft.Scripting.Hosting.Configuration.Section, Microsoft.Scripting, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="false" />
И некоторые файлы в каталоге Properties
:
$ ls -l Properties/ total 36 -rw-rw-r-- 1 setevoy setevoy 1372 жов 14 10:38 AssemblyInfo.cs -rw-rw-r-- 1 setevoy setevoy 1535 жов 14 10:38 DEVconnections.xml -rw-rw-r-- 1 setevoy setevoy 1562 жов 14 10:38 jm-platfrom-dev-eu-connections.xml -rw-rw-r-- 1 setevoy setevoy 1580 жов 14 10:38 jm-platfrom-dev-eu-pl-app-connections.xml -rw-rw-r-- 1 setevoy setevoy 1568 жов 14 10:38 jm-platfrom-prod-eu-app-connections.xml -rw-rw-r-- 1 setevoy setevoy 1586 жов 14 10:38 jm-platfrom-prod-eu-app-staging-connections.xml -rw-rw-r-- 1 setevoy setevoy 1470 жов 14 10:38 PRODconnections.xml drwxrwxr-x 2 setevoy setevoy 4096 жов 14 10:38 PublishProfiles -rw-rw-r-- 1 setevoy setevoy 1492 жов 14 10:38 STAGEconnections.xml
В частности – файл jm-platfrom-dev-eu-connections.xml
используется для обновления Connection String в web.config
во время деплоя на одно из окружений, об этом – в deploy>>>.
Для деплоя используется только master-бранч, в который через feature-бранчи вливаются изменения от девелоперов.
Azure
В Azure приложение работает в Azure App Service, как WebApp.
Группы ресурсов
Для автоматизации развертывания новых окружений – они описаны в файле шаблона, с помощью которого можно командой из Azure CLI развернуть новую группу ресурсов.
Именно начало написания такого шаблона описано в посте Azure: ARM – ручное создание шаблона.
Сейчас имеется три группы:
$ azure group list ... data: jm-platfrom-dev-eu westeurope Succeeded null data: jm-platfrom-dev-eu-pl westeurope Succeeded null data: jm-platfrom-prod-eu westeurope Succeeded null
*-dev-eu
– Development, *-prod-eu
– Production и *-dev-eu-pl
– временная Dev группа для тестирования польской локализации.
Каждая ресурс группа включает в себя:
$ azure group show jm-platfrom-dev-eu | grep Name ... data: Name: jm-platfrom-dev-eu-sql-server // SQL сервер data: Name: jm-platfrom-dev-eu-CMS-database // SQL база самой CMS data: Name: jm-platfrom-dev-eu-CMS-database-stage // SQL база CMS для Stage-слота data: Name: jm-platfrom-dev-eu-DB-database // SQL база пользовательских данных data: Name: jm-platfrom-dev-eu-DB-database-stage // SQL база данных для Stage data: Name: arzldrnljtpx4storage // Storage Account data: Name: jm-platfrom-dev-eu-app-plan // App Service plan data: Name: jm-platfrom-dev-eu-app // WebApp с приложением data: Name: staging // Staging swap-slot для WebApp
GoCD
В роли continuous delivery сервера выступает GoCD, в котором есть два пайплайна, связанных с этим приложением:
Provision
PlatformDEVprovision позволяет пересоздать всю группу ресурсов jm-platfrom-dev-eu с тем же именем (т.е. предварительно её надо удалить).
Что не успел реализовать из-за проблем с Azure (раз, два) и недостатка времени – так создание баз с помощью CustomScript
, как копий текущего Production (через вызов SQL-скрипта из шаблона resource-группы). Копирование баз – описано в посте MSSQL: T-SQL – копирование базы.
Имя группы ресурсов можно изменить, переопределив аргумент, передаваемый скрипту, который устанавливает и вызывает Azure CLI:
Сам скрипт написан на bash
, а имя создаваемой группы передаётся первым аргументом в переменную RGROUP
:
#!/usr/bin/env bash me="$(basename "$(test -L "$0" && readlink "$0" || echo "$0")")" RGROUP=$1 TEMPLATE="jm-platform-clear-sql.json" PARAMS="jm-platform-clear-sql.parameters.json" if [ -z $1 ]; then echo -e "\n[$me] ERROR: Azure Resource Group must be specified as first argument. Exit.\n " exit 1 fi AZURE_LOGIN=$JM_AZURE_LOGIN AZURE_PASS=$JM_AZURE_PASS AZURE_SUBSCRIPTION=$JM_AZURE_SUBSCRIPTION #npm install azure-cli azure_init () { azure login -u $AZURE_LOGIN -p $AZURE_PASS || exit 1 azure account set $AZURE_SUBSCRIPTION || exit 1 azure config mode arm || exit 1 } azure_verify () { echo -e "\n[$me] Checking if $RGROUP exist..." local rgroups=$(azure group list) local regget=$RGROUP for group in $rgroups; do if [[ $group =~ $regget ]]; then echo -e "\n[$me] ERROR: Resource Group $group already exist. Please - remove it via Azure Panel or CLI and try again.\n" exit 1 fi done echo -e "\n[$me] Resource Group name validation passed.\n" } azure_provision () { azure group create -l westeurope -n $RGROUP -d "GoCDprovision-$GO_REVISION" -f $TEMPLATE -e $PARAMS || exit 1 } azure_init azure_verify azure_provision
Deploy
Для деплоя используется пайплайн JMPlatformDeploy, в который входят три этапа:
- build: с помощью
MSBuild
выполняется билд из файла проектаJMPlatform.CMS.csproj
; - Deploy-DEV:
MSDeploy
выполняет деплой на WebApp jm-platfrom-dev-eu-app из группы jm-platfrom-dev; - DeployPROD-staging:
MSDeploy
выполняет деплой на swap-слот WebApp jm-platfrom-prod-eu-app.
Build
MSBuild
вызывается скриптом build.bat
:
@echo off setlocal EnableDelayedExpansion set NUGET="C:\Opt\nuget.exe" set MSBUILD="C:\Windows\Microsoft.Net\Framework64\v4.0.30319\msbuild.exe" set SOLUTION="server\JMPlatform_Soln\JMPlatform_Soln.sln" set ME="%0" echo [%ME%] working in %CD% echo [%ME%] Running NuGet restore %NUGET% restore %SOLUTION% if %errorlevel% neq 0 exit /b %errorlevel% echo [%ME%] Running MSBuild %MSBUILD% %SOLUTION% /t:Clean,Build if %errorlevel% neq 0 exit /b %errorlevel% echo [%ME%] Done.
Deploy
А скрипт деплоя – немного интереснее, и использует 7 (на скрине 8 – ENV
не используется) переменных:
Сам скрипт выглядит так:
:: SITENAME = JMPlatform.CMS :: CONTENTPATH = JMPlatformDEV :: COMPUTERNAME = https://jmplatformdev.scm.azurewebsites.net :: SITE = JMPlatformDEV :: USERNAME = $JMPlatformDEV :: PROPERTYFILE = server\JMPlatform_Soln\JMPlatform.CMS\Properties\DEVconnections.xml :: :: together used for :: msdeploy.exe -enableRule:AppOffline -verb:sync -source:contentPath="C:\GO\pipelines\jmplatform\server\JMPlatform_Soln\JMPlatform.CMS" -dest:contentPath='JMPlatformDEV',ComputerName="https://jmplatformdev.scm.azurewebsites.net/msdeploy.axd?site=JMPlatformDEV",UserName='$JMPlatformDEV',Password='%PASSWORD%',AuthType="Basic" -setParamFile=server\JMPlatform_Soln\JMPlatform.CMS\Properties\DEVconnections.xml @echo off set MSDEPLOY="C:\Opt\MicrosoftWebDeployV3\msdeploy.exe" set ME="%0" echo [%ME%] working in %CD% echo [%ME%] Cleaning up application %MSDEPLOY% -enableRule:AppOffline -verb:delete -dest:contentPath='%CONTENTPATH%',ComputerName="%COMPUTERNAME%/msdeploy.axd?site=%SITE%",UserName='%USERNAME%',Password='%PASSWORD%',AuthType="Basic" echo [%ME%] Deploying new application %MSDEPLOY% -enableRule:AppOffline -verb:sync -source:contentPath="%cd%\server\JMPlatform_Soln\%SITENAME%" -dest:contentPath='%CONTENTPATH%',ComputerName="%COMPUTERNAME%/msdeploy.axd?site=%SITE%",UserName='%USERNAME%',Password='%PASSWORD%',AuthType="Basic" -setParamFile=%PROPERTYFILE% if %errorlevel% neq 0 ( echo ERROR during deploy. Exit. exit /b %errorlevel% ) else ( echo [%ME%] Done )
Переменные, их значения и опции тут:
SITENAME
= JMPlatform.CMS, используется в опции-source:contentPath
для указания пути к проекту;CONTENTPATH
= jm-platfrom-dev-eu-app, используется в опции-dest:contentPath
для указания имени и имени каталога приложения в Azure WebServices;COMPUTERNAME
= https://jm-platfrom-dev-eu-app.scm.azurewebsites.net используется в опции-dest:contentPath
для указания имени хоста, на котором располагается приложение (именно тут разделяется деплой Prod и его свап-слота Stage – во время деплоя передаётся имя хоста jm-platfrom-prod-eu-app-staging.scm.*);SITE
= jm-platfrom-dev-eu-app, используется в опции-dest:contentPath
для передачи имени приложения самому IIS (?);USERNAME
= username :-), тоже используется в опции-dest:contentPath
для авторизации на Web-сервисе Azure;PROPERTYFILE
= server\JaegermeisterPlatform_Soln\JaegerPlatform.CMS\Properties\jm-platfrom-dev-eu-connections.xml– см. ниже.
Опцией -setParamFile
передаётся аргумент с именем файла, в котором содержатся значения для Connection Strings, которые будут вписаны в файл web.config
:
... -setParamFile=%PROPERTYFILE% ...
Например, для Dev значение переменной PROPERTYFILE
задано как server\JMPlatform_Soln\JMPlatform.CMS\Properties\jm-platfrom-dev-eu-connections.xml:
$ cat Properties/jm-platfrom-dev-eu-connections.xml <parameters> <parameter name="umbracoDbDSN" value="server=jm-platfrom-dev-eu-sql-server.database.windows.net;database=jm-platfrom-dev-eu-CMS-database;user id=user;password=pass;trusted_connection=False"> <parameterEntry type="XMLFile" scope="web.config$" match="//configuration/connectionStrings/add[@name='umbracoDbDSN']/@connectionString"/> </parameter> ... </parameters>
Во время деплоя MSDeploy
парсит файл web.config
, ищет запись в его XML по пути //configuration/connectionStrings/*
с именем umbracoDbDSN (и другими, перечисленными в connections-файле далее) и перезаписывает значение connectionString. :
$ cat web.config | grep -B 2 umbracoDbDSN <connectionStrings> <remove name="umbracoDbDSN" /> <add name="umbracoDbDSN" connectionString="server=jmplatform.database.windows.net;database=JMPlatformCMS;user id=user;password=pass;trusted_connection=False" providerName="System.Data.SqlClient" />
Подробнее – MSDeploy: обновить параметры в web.config во время деплоя.
Скрипты запускаются GoCD билд-агентами на Windows Server 2012, а процесс завершения деплоя выглядит так: