Cześć wszystkim! Nazywam się Andrzej i pracuję jako inżynier DevOps w firmie Exness w zespole deweloperskim. Moja główna działalność związana jest z budowaniem, wdrażaniem i wsparciem aplikacji w dockerze na systemie operacyjnym Linux (dalej — OS). Niedawno pojawiło się przede mną zadanie związane z tymi samymi aktywnościami, ale jako docelowy system OS projektu został wybrany Windows Server oraz zestaw projektów w C++. Dla mnie było to pierwsze intensywne współdziałanie z kontenerami dockerowymi na systemie Windows oraz ogólnie z aplikacjami w C++. Dzięki temu zdobyłem ciekawe doświadczenie i dowiedziałem się o pewnych subtelnościach konteneryzacji aplikacji w systemie Windows.

W tym artykule chciałbym opowiedzieć, z jakimi trudnościami się zmagałem, jak udało mi się je rozwiązać. Mam nadzieję, że okaże się to pomocne w rozwiązywaniu waszych bieżących i przyszłych zadań. Miłej lektury!
Dlaczego kontenery?
W firmie istnieje już infrastruktura orkiestratora kontenerów Hashicorp Nomad oraz powiązanych komponentów — Consul i Vault. Dlatego konteneryzacja aplikacji została wybrana jako ujednolicony sposób dostarczania gotowego rozwiązania. Ponieważ w infrastrukturze projektu znajdują się hosty dockerowe z wersjami systemu Windows Server Core 1803 i 1809, konieczne jest oddzielne budowanie wersji obrazów dockerowych dla 1803 i 1809. W wersji 1803 ważne jest, aby numer rewizji hosta dockerowego odpowiadał numerowi rewizji podstawowego obrazu dockerowego oraz hosta, na którym kontener z tego obrazu będzie uruchomiony. Wersja 1809 pozbawiona jest tej wady. Więcej można przeczytać .
Dlaczego multi-stage?
Inżynierowie zespołów deweloperskich nie mają dostępu do hostów budowlanych lub jest on mocno ograniczony, nie ma możliwości szybkiego zarządzania zestawem komponentów do budowy aplikacji na tych hostach, na przykład zainstalowania dodatkowego zestawu narzędzi lub obciążenia dla Visual Studio. Dlatego podjęliśmy decyzję — wszystkie komponenty potrzebne do budowy aplikacji zainstalujemy w obrazie budowlanym dockerowym. W razie potrzeby można dość szybko zmienić tylko dockerfile i uruchomić pipeline tworzenia tego obrazu.
Od teorii do praktyki
W idealnej wieloetapowej budowie obrazu Docker przygotowanie środowiska do kompilacji aplikacji odbywa się w tym samym skrypcie Dockerfile, co sama kompilacja aplikacji. W naszym przypadku wprowadzono jednak dodatkowy krok, a mianowicie, etap wstępnego tworzenia obrazu Docker z wszystkimi niezbędnymi elementami do kompilacji aplikacji. Zrobiono to, ponieważ chciano skorzystać z możliwości pamięci podręcznej Docker, aby skrócić czas instalacji wszystkich zależności.
Przeanalizujmy główne punkty skryptu Dockerfile, który służy do tworzenia tego obrazu.
Aby utworzyć obrazy różnych wersji systemu operacyjnego w Dockerfile, można zdefiniować argument, przez który podczas budowy przekazywana jest numer wersji, który również jest tagiem podstawowego obrazu.
Pełną listę tagów obrazów Microsoft Windows Server można znaleźć .
ARG WINDOWS_OS_VERSION=1809
FROM mcr.microsoft.com/windows/servercore:$WINDOWS_OS_VERSIONDomyślnie polecenia w instrukcji RUN wewnątrz Dockerfile w systemie Windows są wykonywane w konsoli cmd.exe. Aby ułatwić pisanie skryptów i rozszerzyć funkcjonalność używanych poleceń, przedefiniujemy konsolę wykonywania poleceń na Powershell za pomocą instrukcji SHELL.
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';"]Kolejnym krokiem jest zainstalowanie menedżera pakietów Chocolatey oraz niezbędnych pakietów:
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'Aby zainstalować pakiety przy użyciu Chocolatey, wystarczy przekazać je w formie listy lub zainstalować jeden po drugim, jeśli potrzebne są unikalne parametry dla każdego pakietu. W naszej sytuacji wykorzystaliśmy plik manifestu w formacie XML, w którym wymienione są wymagane pakiety oraz ich parametry. Jego zawartość wygląda następująco:
Następnie instalujemy środowisko do kompilacji aplikacji, a mianowicie, MS Build Tools 2019 — jest to odchudzona wersja Visual Studio 2019, która zawiera minimalny zestaw komponentów potrzebnych do kompilacji kodu.
Do pełnoprawnej pracy z naszym projektem C++ będą nam potrzebne dodatkowe komponenty, a mianowicie:
- Narzędzia C++
- Zestaw narzędzi v141
- Windows 10 SDK (10.0.17134.0)
Można zainstalować zaawansowany zestaw narzędzi w trybie automatycznym przy użyciu pliku konfiguracyjnego w formacie JSON. Zawartość pliku konfiguracyjnego:
Pełną listę dostępnych komponentów można znaleźć na stronie dokumentacji .
{
"version": "1.0",
"components": [
"Microsoft.Component.MSBuild",
"Microsoft.VisualStudio.Workload.VCTools;includeRecommended",
"Microsoft.VisualStudio.Component.VC.v141.x86.x64",
"Microsoft.VisualStudio.Component.Windows10SDK.17134"
]
}W dockerfile wykonywany jest skrypt instalacyjny, a dla wygody dodawana jest ścieżka do plików wykonywalnych narzędzi kompilacyjnych do zmiennej środowiskowej ŚCIEŻKA. Zaleca się również usunięcie zbędnych plików i katalogów, aby zmniejszyć rozmiar obrazu.
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)Na tym etapie nasz obraz do kompilacji aplikacji C++ jest gotowy i można przejść do samego tworzenia wieloetapowej budowy aplikacji.
Wieloetapowy w akcji
Jako obraz budujący użyjemy stworzonego obrazu ze wszystkimi narzędziami na pokładzie. Jak w poprzednim skrypcie dockerfile, dodamy możliwość dynamicznego określenia numeru wersji/tagu obrazu dla wygody ponownego użycia kodu. Ważne jest, aby dodać etykietę jako budowniczy do obrazu budującego w instrukcji Z FROM.
ARG WINDOWS_OS_VERSION=1809
FROM buildtools:$WINDOWS_OS_VERSION as builderNadszedł czas na budowanie aplikacji. Tutaj wszystko jest dość proste: skopiować kod źródłowy i wszystko, co z nim związane, i uruchomić proces kompilacji.
COPY myapp .
RUN nuget restore myapp.sln ;
msbuild myapp.sln /t:myapp /p:Configuration=ReleaseOstatni etap tworzenia końcowego obrazu polega na wskazaniu podstawowego obrazu aplikacji, gdzie będą przechowywane wszystkie artefakty kompilacji i pliki konfiguracyjne. Aby skopiować skompilowane pliki z pośredniego obrazu budującego, należy wskazać parametr --from=builder w instrukcji COPY.
FROM mcr.microsoft.com/windows/servercore:$WINDOWS_OS_VERSION
COPY --from=builder C:/x64/Release/myapp/ ./
COPY ./configs ./Teraz wystarczy dodać niezbędne zależności do działania naszej aplikacji i wskazać polecenie uruchamiające przez instrukcje PUNKCIE WEJŚCIA lub POLECENIE.
Podsumowanie
W tym artykule opisałem, jak stworzyć pełne środowisko kompilacji aplikacji C++ w kontenerze pod Windows oraz jak wykorzystać możliwości wieloetapowego budowania docker do tworzenia kompletnych obrazów naszej aplikacji.
Źródło: habr.com
