Використання docker multi-stage для збирання windows образів

Всім привіт! Мене звуть Андрій, і я працюю DevOps інженером у компанії Exness у команді розробки. Моя основна діяльність пов'язана зі складанням, деплоєм та підтримкою додатків у docker під операційною системою Linux (далі – ОС). Нещодавно у мене постало завдання з тими ж активностями, але як цільова ОС проекту стала Windows Server і набір проектів на C++. Для мене це була перша щільна взаємодія з docker контейнерами під ОС Windows і загалом із додатками на C++. Завдяки цьому я отримав цікавий досвід і дізнався про деякі тонкощі контейнеризації додатків у Windows.

Використання docker multi-stage для збирання windows образів

У цій статті хочу розповісти, з якими труднощами мені довелося зіткнутися, як їх вдалося вирішити. Сподіваюся, це буде корисним для вирішення ваших поточних та майбутніх завдань. Приємного читання!

Чому контейнери?

У компанії є існуюча інфраструктура оркестратора контейнерів Hashicorp Nomad та пов'язаних компонентів – Consul та Vault. Тому контейнеризацію додатків було обрано як уніфікований метод доставки готового рішення. Оскільки в інфраструктурі проекту є docker-хости з версіями ОС Windows Server Core 1803 і 1809, необхідно збирати окремо версії docker-образів для 1803 і 1809. У версії 1803 важливо пам'ятати про те, що номер ревізії складального docker-хоста повинен збігатися з номером ревізії базового docker-образу та хоста, де контейнер із цього образу буде запущений. Версія 1809 року позбавлена ​​такого недоліку. Детальніше можна прочитати тут.

Чому multi-stage?

У інженерів команд розробки доступ до складальних хостів відсутній або дуже обмежений, немає можливості оперативно керувати набором компонентів для збирання програми на цих хостах, наприклад, встановити додатковий інструмент або workload для Visual Studio. Тому ми прийняли рішення - всі необхідні для складання програми компоненти встановити в складальний образ docker. При необхідності можна досить швидко змінити лише dockerfile та запустити пайплайн створення цього образу.

Від теорії до справи

У ідеальному докері multi-stage складання образу підготовка оточення для складання програми відбувається в тому ж dockerfile скрипті, що і складання самої програми. Але в нашому випадку була додана проміжна ланка, а саме, крок попереднього створення docker-образу з усім необхідним для збирання програми. Так зроблено тому, що хотілося використовувати можливість docker cache, щоб скоротити час встановлення всіх залежностей.

Давайте розберемо основні моменти скрипта dockerfile для формування цього образу.

Для створення образів різних версій ОС в dockerfile можна визначити аргумент, через який при складанні передається номер версії, і він є тегом базового образу.

Повний список тегів образів Microsoft Windows Server можна знайти тут.

ARG WINDOWS_OS_VERSION=1809
FROM mcr.microsoft.com/windows/servercore:$WINDOWS_OS_VERSION

За промовчанням команди в інструкції RUN всередині dockerfile в Windows виконуються в консолі cmd.exe. Для зручності написання скриптів та розширення функціоналу використовуваних команд перевизначимо консоль виконання команд на Powershell через інструкцію SHELL.

SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';"]

Наступним кроком встановлюємо пакетний менеджер chocolatey та необхідні пакети:

COPY chocolatey.pkg.config .
RUN Set-ExecutionPolicy Bypass -Scope Process -Force ;
    [System.Net.ServicePointManager]::SecurityProtocol = 
    [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 ;
    $env:chocolateyUseWindowsCompression = 'true' ;
    iex ((New-Object System.Net.WebClient).DownloadString( 
      'https://chocolatey.org/install.ps1')) ;
    choco install chocolatey.pkg.config -y --ignore-detected-reboot ;
    if ( @(0, 1605, 1614, 1641, 3010) -contains $LASTEXITCODE ) { 
      refreshenv; } else { exit $LASTEXITCODE; } ;
    Remove-Item 'chocolatey.pkg.config'

Щоб встановити пакети, використовуючи chocolatey, можна просто передати їх списком або встановити по одному в тому випадку, якщо необхідно передати унікальні параметри для кожного пакета. У нашій ситуації ми використовували маніфест-файл у форматі XML, в якому вказано список необхідних пакетів та їх параметрів. Його вміст виглядає так:

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="python" version="3.8.2"/>
  <package id="nuget.commandline" version="5.5.1"/>
  <package id="git" version="2.26.2"/>
</packages>

Далі ми встановлюємо середовище складання програми, а саме, MS Build Tools 2019 - це полегшена версія Visual Studio 2019, яка містить мінімально необхідний набір компонентів для компіляції коду.
Для повноцінної роботи з нашим C++ проектом нам знадобляться додаткові компоненти, а саме:

  • Workload C++ tools
  • Toolset v141
  • Windows 10 SDK (10.0.17134.0)

Встановити розширений набір інструментів в автоматичному режимі можна за допомогою конфігураційного файлу у форматі JSON. Вміст конфігураційного файлу:

Повний список доступних компонентів можна знайти на сайті документації Microsoft Visual Studio.

{
  "version": "1.0",
  "components": [
    "Microsoft.Component.MSBuild",
    "Microsoft.VisualStudio.Workload.VCTools;includeRecommended",
    "Microsoft.VisualStudio.Component.VC.v141.x86.x64",
    "Microsoft.VisualStudio.Component.Windows10SDK.17134"
  ]
}

У dockerfile виконується скрипт установки, і для зручності додається шлях до файлів, що виконуються build tools в змінну оточення PATH. Також бажано видалити непотрібні файли та директорії, щоб зменшити розмір образу.

COPY buildtools.config.json .
RUN Invoke-WebRequest 'https://aka.ms/vs/16/release/vs_BuildTools.exe' 
      -OutFile '.vs_buildtools.exe' -UseBasicParsing ;
    Start-Process -FilePath '.vs_buildtools.exe' -Wait -ArgumentList 
      '--quiet --norestart --nocache --config C:buildtools.config.json' ;
    Remove-Item '.vs_buildtools.exe' ;
    Remove-Item '.buildtools.config.json' ;
    Remove-Item -Force -Recurse 
      'C:Program Files (x86)Microsoft Visual StudioInstaller' ;
    $env:PATH = 'C:Program Files (x86)Microsoft Visual Studio2019BuildToolsMSBuildCurrentBin;' + $env:PATH; 
    [Environment]::SetEnvironmentVariable('PATH', $env:PATH, 
      [EnvironmentVariableTarget]::Machine)

На цьому етапі наш образ для компіляції C++ програми готовий, і можна приступати безпосередньо до створення docker multi-stage складання програми.

Multi-stage у дії

Як складальний образ будемо використовувати створений образ з усім інструментарієм на борту. Як і в попередньому скрипті dockerfile, додамо можливість динамічно вказувати номер версії/тега образу для зручності перевикористання коду. Важливо додати мітку as builder до складального образу в інструкції FROM.

ARG WINDOWS_OS_VERSION=1809
FROM buildtools:$WINDOWS_OS_VERSION as builder

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

COPY myapp .
RUN nuget restore myapp.sln ;
    msbuild myapp.sln /t:myapp /p:Configuration=Release

Завершальний етап створення кінцевого образу — вказівка ​​базового образу програми, де розташовуватимуться всі артефакти компіляції та конфігураційні файли. Для копіювання компілюваних файлів з проміжного складального образу треба вказати параметр --from=builder в інструкції COPY.

FROM mcr.microsoft.com/windows/servercore:$WINDOWS_OS_VERSION

COPY --from=builder C:/x64/Release/myapp/ ./
COPY ./configs ./

Тепер залишається додати необхідні залежності для роботи нашої програми та вказати команду запуску через інструкції ENTRYPOINT або CMD.

Висновок

У цій статті я розповів, як створити повноцінне середовище компіляції C++ додатків усередині контейнера під Windows та про те, як використовувати можливості docker multi-stage збірок для створення повноцінних образів нашої програми.

Джерело: habr.com

Додати коментар або відгук