Непрерывная интеграция с Data Analysis / BI Engine

Tags: CI, continuous integration, непрерывная интеграция, BI, SSRS, SSIS, SQL Server, TFS

(Перевод статьи Георгия Зубриенко)

Никто не умаляет ценность, которую приносит контроль над источниками кода для процессов разработки.  Однако, если речь идет о проектах, анализирующих данные, как, например, BI и Data Analysis, большинство людей избегают этого, аргументируя это тем, что это слишком сложно.  Это ведет к экспоненциальному росту сложности кода и нулевой гибкости BI системы -  создать новую с нуля, будет стоить гораздо дешевле, чем обновлять уже существующую.

В нескольких словах, вам необходим контроль источников, но не только он. Как и любая другая программа, системы BI и DA должны быть построены удаленно и выпущены в стандартных условиях (TEST – PREPRD – PRD). Качественное разбиение на версии и механизм отката с прозрачной логикой так же необходимы.

Из моего опыта, принцип KISS работает достаточно хорошо, если вы разрабатываете среду CI. В первую очередь сокращение количества использованных технологий играет ключевую роль; во-вторых, сокращение количества использованных сред, классические четыре – скорее всего будут перебором, в то время, как три – идеальное количество, даже две среды может быть достаточно, если вы установите инструменты CI правильно. Работая со стеком MS BI, логично буде использовать Team Foundation Server, как инструмент CI. Если же вы работаете с ORACLE/Postgres, то лучше перейти на TeamCIty или Jenkins.

Однако, у использования TFS есть свои подводные камни, вы можете закончить со сборкой значений из командной строки, команды удаленного сервера и так далее -  что точно не скажется хорошо, когда вы будете просматривать бесконечную сборку / релиз логов различных форматов. По большому счету, в BI проектах вы можете делать почти все только с PowerShell, MSBuild и Command Line потребует наличия стандартных инструментов Microsoft, например, sqlpackage.

Версии продукта действительно очень важны, так как это позволяет команде разработчиков представить результаты их работы и том представлении, которое понятно для всех.  Еще более важно то, что использование связей между версиями, проектом, задачами и кодом, заставляет и разработчика, и заказчика понять эффект любых возможных откатов. Не представляю, как этим можно управлять, если VC на базе Git (если знаете, пишите в комментариях); для TFS мы использовали очень простой подход: т.к. каждая TFS репозитория обладает уникальным в своем роде номером версии, если вы считаете, что каждый набор изменений зарегистрирован в качестве обновления системы, тогда вы можете завершить вот с этим для каждой сборки: 0.1.456.{инкрементированный номер или GUID}, где:

  • 0 это для основной версии, обновленной после того, как 10 000 изменений были совершены. Обычно вам нужно создавать файлы для обновления до следующей версии в механизме отката
  • 1 это младшая версия, после каждой 1000 произведенных изменений. То же назначение, что и у основной версии, но релиз гораздо чаще.
  •  456 это номер подверсии, указывающий на то как быстро меняется система с каждым проектом. Как правило откатов между подверсиями не делается.
  • {инкрементированный номер или GUID} – если вдруг вы попробуете построить одну и ту же версию несколько раз.

Забавная вещь о TFS это то, что создавать версии C# проектов легко, однако про BI проекты такого сказать нельзя из-за проектов с 3-4 разными VS шаблонами.  Хоть Microsoft и не упоминал шаблонно-независимый подход управления версиями слишком много, вы можете использовать VSO команды для назначения нового номера.  Представьте, что у нас есть скрипт Powershell, который рассчитывает все части номера версии:

...

$fullversion = [System.String]::Format("{0}.{1}.{2}.{3}.{4}", $major, $minor, $subversion, $dt, $rev);

Затем вы можете сконструировать подсказку для агента сборки VS и напечатать его в консоли агента:

$prompt = [System.String]::Format("{0}{1}", "##vso[build.updatebuildnumber]", $fullversion)

Write-Host "Setting build number to", $fullversion

Write-Verbose –Verbose $prompt # in case you are interested in debugging this

Как результат ваши версии присвоены:

Не забудьте, что в данном случае вы ничего не указываете в графе «Build Number Format» :

Чтобы включить это для всех значений сборки, просто добавьте в каждую сборку шаг Execute Powershell:

Build.SourceVersion и Build.BuildNumber это системные переменные под управлением TFS.

Когда вы закончили с версиями, построение CI системы такое же легкое, как и конструктор LEGO. Требуемые шаги для проектов SQL Server Database:

  • Первоначальная сборка проекта со всеми зависимостями. Используйте MSBuild, укажите аргументы в переменных сборки. Пример аргументов для MSBuild:

/p:SqlPublishProfilePath="$(xml.script)" /p:OutDir=$(deployment.dir)

  • Инкрементный скрипт обновления будет генерироваться на основании публикации скрипта, расположенного в репозитории. SQLPackage для спасения, еще несколько переменных, как, например, имя файла dacpac , которое должно использоваться для сравнение с существующей базой данных и соединения с целевым сервером:

/a:script /SourceFile:"$(deployment.dir)$(deployment.dacpac)" /TargetConnectionString:"Data Source=$(deployment.server);Initial Catalog=$(deployment.db);Integrated Security=true" /OutputPath:$(deployment.dir)$(deployment.script) /Profile:"$(xml.script)"

  • Первоначальный скрипт публикации, который будет выполнять сгенерированный .sql-файл. Это может быть выполнено с помощью создания дополнительного проекта с использованием инструмента стороннего разработчика для VS и добавлением этой сборки ко всем BI сборкам для того, чтобы создать CI скрипты. Для проекта базы данных, CI скрипт просто использует Invoke-SqlCmd:

Import-Module SqlPs

 

...

 

Write-Host 'Executing file... ', $file 

Write-Host 'Executing command... '

Write-Host "---------------------------------------------"

       

$SQLCommandText = @(Get-Content -Path $directorypath\$file) 

Write-Host $SQLCommandText

 

Invoke-Sqlcmd -InputFile $directorypath\$file -ServerInstance $server 

... # some debugging if you want

Наконец-таки, скопируйте все, что было сгенерировано и опубликуйте, как артефакты сборки.  Типичное выполнение сборки будет выглядеть следующим образом:

И для шаблонов проектов SSIS, и для шаблонов проектов SSAS мы не нашли лучшего решения, чем использовать devenv.com т.к. он предоставляет логи сборки и обладает нулевым временем загрузки в сравнении с devenv.exe.  В случае, если у вас другие настройки сборки, не забудьте указать корректную папку вывода, когда копируете файлы ISPAC или SSAS в папку артефакта.

Часть с релизом для проектов SQL и SSIS не сложная, но есть некоторая путаница с SSAS. Вкратце, чтобы сделать релиз SQL проекта нужно просто выполнить CI скрипт с правильными аргументами:  

-server $(deployment.server) -file $(ids.script.db)

Не забудьте, что $(deployment.server) должно соответствовать серверу, использованному в сборке ранее. Нет нужды указывать имена тестовой/промышленной базы данных, т.к. они были уже добавлены в $(ids.script.db), когда sqlpackage создал все необходимое, используя .xml publish script, который вы предоставили в SQL проекте.

Для развертывания SSIS мы использовали только обновление пакета без автоматического присваивания переменных или создания задач. Этот подход требует выполнение некоторых задач вручную, например, связывания переменных среды; Тем не менее, он делает процесс более безопасным и стабильным, поскольку SSIS является основным ядром системы. Код развертывания можно легко найти в Интернете:

$folder = $catalog.Folders[$FolderName]

 

if (!$folder)

{

    #Create a folder in SSISDB

    Write-Host "Creating Folder ..."

    $folder = New-Object "$ISNamespace.CatalogFolder" ($catalog, $FolderName, $FolderName)            

    $folder.Create()  

}

 

# Read the project file, and deploy it to the folder

Write-Host "Deploying Project ..."

[byte[]] $projectFile = [System.IO.File]::ReadAllBytes($ProjectFilePath)

$folder.DeployProject($ProjectName, $projectFile)

Здесь я не охватываю вопросы, связанные с развертыванием Python / R, так как мы использовали SSIS и поток данных и диспетчер процессов для всех задач анализа данных и интеллектуального анализа данных; в этом случае вы просто добавляете шаг копирования файла, чтобы переместить производственные версии скриптов .py / .r  в каталог сервера. Тем не менее, для любого другого подхода шаг Powershell может выполнить это достаточно хорошо, т.к. он может взаимодействовать с веб-сервисами и, таким образом, инициировать публикацию в облачных сервисах, таких как Azure ML или Anaconda.

Последней проблемой является развертывание SSAS проекта. Не знаю, ошибка это или нет, но параметры развертывания SSAS, указанные в VS, хранятся локально и никогда не используются TFS. Это означает, что при создании сборки у вас есть localhost как сервер развертывания. Возможно, это не блестящая идея, но мы просто исправили ее в соответствующем скрипте CI, который запускается с аргументами, заполненными именами серверов и целевых баз данных:

-servers "$(deployment.servers)" -ASDeploy "$(util.ssas.deployment)" -file "$(System.DefaultWorkingDirectory)/($CIPath)/$(deployment.project)" -database "$(deployment.db)"

Затем отключите мозг и используйте простые обновления файла конфигурации (так как файлы конфигурации являются частью вывода сборки). Кстати, если серверы предоставлены в виде строки с разделителями, цикл может быть запущен для обработки каждого:

$connString = "<ConnectionString>Data Source={0};Timeout=0</ConnectionString>"

$serverString = "<Server>{0}</Server>"

$dbconfig = "<Database>{0}</Database>"

 

$server_list = $servers.Split(';')

 

foreach ($server_fqn in $server_list)

{

        Write-Host (Get-Date), "Checking connection to", $server_fqn

        $server = $server_fqn.Split(':')[0]

        $port = $server_fqn.Split(':')[1]

        $result = Test-NetConnection $server -Port $port

        if ($result.PingSucceeded -And $result.TcpTestSucceeded)

        {

 

$path = [System.IO.Path]::GetDirectoryName($file)

               $name = [System.IO.Path]::GetFileNameWithoutExtension($file)

               $deployment = [System.IO.Path]::Combine($path, $name + ".deploymenttargets")

               $deployment_temp = [System.IO.Path]::Combine($path, $name + "_temp.deploymenttargets")

 

               # fill content

               $content = Get-Content $deployment

 

               foreach ($line in $content)

               {

                       if ((-Not $line.Contains("Server")) -and (-Not $line.Contains("ConnectionString")) -and (-Not $line.Contains("Database"))) 

                       {

                               $line | Out-File $deployment_temp -Append

                       }

                       elseif ($line.Contains("Server"))

                       {

                               [System.String]::Format($serverString, $server) | Out-File $deployment_temp -Append

                       }

                       elseif ($line.Contains("ConnectionString"))

                       {

                               [System.String]::Format($connString, $server) | Out-File $deployment_temp -Append

                       }

                       elseif ($line.Contains("Database"))

                       {

                               [System.String]::Format($dbconfig, $database) | Out-File $deployment_temp -Append

                       }

               }

 

               # switch files

               Remove-Item $deployment.ToString()

               Rename-Item -Path $deployment_temp -NewName ($name + ".deploymenttargets")

Последним шагом является запуск средства развертывания SSAS для обновления и обработки куба:

$pinfo = New-Object System.Diagnostics.ProcessStartInfo

               $pinfo.FileName = $ASDeploy

               $pinfo.RedirectStandardError = $true

               $pinfo.RedirectStandardOutput = $true

               $pinfo.UseShellExecute = $false

               $pinfo.Arguments = @("`"$file`" /s")

               $p = New-Object System.Diagnostics.Process

               $p.StartInfo = $pinfo

               $p.Start() | Out-Null

               $stdout = $p.StandardOutput.ReadToEnd()

               $stderr = $p.StandardError.ReadToEnd()

               $p.WaitForExit()

               Write-Host "stdout: $stdout"

               Write-Host "stderr: $stderr"

               Write-Host "exit code: " + $p.ExitCode

Так как он никогда не вылетает, просто выдает ненулевой код выхода, если что-то пойдет не так, вам нужно вручную свернуть скрипт (комментарии приветствуются о том, как сделать это менее уродливым):

if (-not ($p.ExitCode -eq 0))

               {

                       throw [System.Exception] "Generic processing failure"

               }

               if ($stdout.ToLower().Contains("error"))

               {

                       throw [System.Exception] "Unable to incrementally update and process cube. Try full processing or check project code"

               }

Именно в этом и состоит очень простая CI система, которая начинается с каждой регистрации и автоматического развертывания изменений независимо от размера. Системе не хватает тестирования, возможно, сценарий SSAS не так хорош, но по крайней мере он предоставляет вам механизм для плавного управления обновлениями системы и обеспечивает возможность доставки новых функций за один день, а не за неделю или за месяц. Вся установка выполняется в течение 2-3 дней, включая тестирование, ею очень просто управлять и отлаживать, если это необходимо.

No Comments

Add a Comment