Azure: GoCD и MSDeploy — деплой UmbracoCMS в Azure WebServices

Автор: | 10/14/2016
 

azure_logoОписание инфрастуктуры и процесса деплоя приложения одного немецкого производителя приятноалкогольной продукции.

Работает на UmbracoCMS, .NET, язык — C#, использует две Azure (MS) SQL базы — одну для хранения настроек, вторую — для пользовательских данных.

Для приложения имеются две различные ресурс-группы — Development и Production, при этом у Production имеется Staging swap-slotDev — тоже, но он пока не используется). Так же каждая группа имеет свой 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-euDevelopment, *-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, в котором есть два пайплайна, связанных с этим приложением:

jm_setup_2

Provision

PlatformDEVprovision позволяет пересоздать всю группу ресурсов jm-platfrom-dev-eu с тем же именем (т.е. предварительно её надо удалить).

Что не успел реализовать из-за проблем с Azure (раз, два) и недостатка времени — так создание баз с помощью CustomScript, как копий текущего Production (через вызов SQL-скрипта из шаблона resource-группы). Копирование баз — описано в посте MSSQL: T-SQL – копирование базы.

Имя группы ресурсов можно изменить, переопределив аргумент, передаваемый скрипту, который устанавливает и вызывает Azure CLI:

jm_setup_3

Сам скрипт написан на 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, в который входят три этапа:

jm_setup_4

  • 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 не используется) переменных:

jm_setup_5

Сам скрипт выглядит так:

:: 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, а процесс завершения деплоя выглядит так:

jm_setup_6