
Кому может быть полезно
Тем кто собирает что-то под windows и задумался о версионировании сборочного окружения.
Как ведь бывает, прилетает баг, нужно пересобрать старый релиз, а сборочный агент уже с другим окружением. И даже если вы каким-то образом сохраните нужное окружение, как защитить от его от самих сборок? Делать снапшоты, откатываться, или разворачивать агенты с некого золотого образа и прочее.
Версии окружения поднимаются, само окружение усложняется, требование команд к окружению растут и расходятся. Как тут всем угодить?
Исторически так сложилось, что в linux docker заехал как родной, а вот в windows вроде бы есть, но про него не слышно, на Хабре так уж точно. Поэтому если вы решили это попробовать, то эта статья для вас.
Немного теории
У Microsoft базовый docker image под windows версионируются по билду ядра. Если опустить подробности, у вас должно совпадать ядро хоста и ядро докер образа. Либо версия ядра хоста должна быть выше докер образа, - и тогда вы сможете запустить контейнер с изоляцией hyper-v.
Изоляция процессов обычно предпочтительней, нет лишней прослойки в виде виртуальной машины, но ядро хоста и ядро базового докер образа должно совпадать.
Подготовка сборочного агента
Волевым решением выбираете версию windows и привязываете к ней базовый docker image. В нашем примере это будет
Windows Server 2022 Standard 21H2 20348.2849
и
mcr.microsoft.com/windows/server:10.0.20348.2966-amd64
Разворачиваем OS и устанавливаем на ней docker скриптом https://learn.microsoft.com/ru-ru/virtualization/windowscontainers/quick-start/set-up-environment?tabs=dockerce#windows-server-1
Нас интересует Docker CE, это самый классический вариант, с понятными командами docker build и docker run.
Устанавливается скриптом, но можно отдельно выкачать бинарь и зарегать службу вручную. Вся служба умещается в один dockerd.exe и docker.exe которые копируется в system32.
К нему идут виндовые фичи, если устанавливаете вручную, то их стоит установить.
HypervisorPlatform
VirtualMachinePlatform
Containers
Первый запуск службы docker, создаст каталог C:\ProgramData\docker, который будет содержать настройки, слои и кэш, что нас не устраивает.
Поэтому правим "C:\ProgramData\docker\config\daemon.json".
Но перед этим создаем отдельный диск под докер, пусть будет E:\
Запускам powershell от имени админа, подставляем в скрипт url вашего локального докер реестра и выполняем команды:
Stop-Service docker -ErrorAction stop $json = @{ "data-root" = "E:\docker" "allow-nondistributable-artifacts" = @("harbor.domen.local") "exec-opts" = @("isolation=process") "group" = "docker" "storage-opts" = @("size=200GB") } |ConvertTo-Json -Depth 3 $json |out-file -FilePath "C:\ProgramData\Docker\config\daemon.json" -Encoding utf8 -Force New-LocalGroup -name "docker" Start-Service docker -ErrorAction stop
"data-root" = "E:\docker" - там где мы будем все хранить.
allow-nondistributable-artifacts - чтобы можно было пушить проприетарные слои мелкомягких к себе в реестр.
"exec-opts" = "isolation=process" - изоляция процессов
"group" = "docker" - группа пользователей, которая может запускать докер
storage-opts - выделяется по дефолту 30gb или около того при запуске контейнера, не всегда помещается при сборке.
Тут есть одна неочевидная вещь которая мне в будущем сильно пригодилась. Чистить от мусора очень долго, но отформатировать диск дело пары секунд. То есть мы останавливаем службу докер, форматируем диск и снова запускаем.
Сборка образа
В целом, все тоже самое и тот же синтаксис, за исключением, что вам придется оперировать CMD оболочкой или PowerShell. Пошик мне привычнее поэтому буду работать в нем.
#классический Dockerfile из примера microsoft # escape=` # Use the latest Windows Server Core 2022 image. FROM mcr.microsoft.com/windows/servercore:ltsc2022 # Restore the default Windows shell for correct batch processing. SHELL ["cmd", "/S", "/C"] RUN ` # Download the Build Tools bootstrapper. curl -SL --output vs_buildtools.exe https://aka.ms/vs/17/release/vs_buildtools.exe ` ` # Install Build Tools with the Microsoft.VisualStudio.Workload.AzureBuildTools workload, excluding workloads and components with known issues. && (start /w vs_buildtools.exe --quiet --wait --norestart --nocache ` --installPath "%ProgramFiles(x86)%\Microsoft Visual Studio\2022\BuildTools" ` --add Microsoft.VisualStudio.Workload.AzureBuildTools ` --remove Microsoft.VisualStudio.Component.Windows10SDK.10240 ` --remove Microsoft.VisualStudio.Component.Windows10SDK.10586 ` --remove Microsoft.VisualStudio.Component.Windows10SDK.14393 ` --remove Microsoft.VisualStudio.Component.Windows81SDK ` || IF "%ERRORLEVEL%"=="3010" EXIT 0) ` ` # Cleanup && del /q vs_buildtools.exe # Define the entry point for the docker container. # This entry point starts the developer command prompt and launches the PowerShell shell. ENTRYPOINT ["C:\\Program Files (x86)\\Microsoft Visual Studio\\2022\\BuildTools\\Common7\\Tools\\VsDevCmd.bat", "&&", "powershell.exe", "-NoLogo", "-ExecutionPolicy", "Bypass"]
Мне он не подошел по многим причинам.
Нужен интернет
https://aka.ms/vs/17/release/vs_buildtools.exe — самый большой обман и я позже объясню почему
mcr.microsoft.com/windows/servercore:ltsc2022 — часто не может сжать слой при билде если брать его как базовый. Не знаю точно с чем связано, но просто server хоть и толще но он всегда собирается.
По поводу https://aka.ms/vs/17/release/vs_buildtools.exe, внезапно VS 2022 тоже версионируется внутри продукта. У версий есть свой жизненный цикл.
Идем сюда https://learn.microsoft.com/ru-ru/visualstudio/releases/2022/release-history и видим:

У нас оказывается есть LTSC версии, есть промежуточные и сроки поддержки.
Тут https://aka.ms/vs/17/release/vs_buildtools.exe мы берем текущую версию и 50/50 вы попадаете на нечетную версию которая не LTSC. И ладно было дело только в этом, вы можете даже не знать об этом и просто собираетесь на том, что загрузили.
Понимание приходит потом, когда новая сборка образа перестает собирать ваш код, так как версия компилятора выше и требует пересобрать зависимости. А у вас ствол уже весь собран на промежуточной версии и все завязаны на эту версию.

В общем выбираете нужную вам версию и скачиваете инструменты для сборки -vs_buildtools.exe.
Как из него сделать установщик по сети опишу в конце статьи чтобы не мешать всё в кучу.
Итак нам нужен Dockerfile
Скрытый текст
# escape=` # Powered by Say_TT_PLZ ARG base_image=mcr.microsoft.com/windows/server:10.0.20348.2966-amd64 FROM $base_image SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] WORKDIR c:\build COPY response.json c:/build RUN curl.exe -SL --output vs_buildtools.exe http://vs-cache.local/vs/buildtools2022_17_12/vs_setup.exe; ` new-item -ItemType Directory -Path C:\build\CA; ` curl.exe -SL --output C:\build\CA\manifestRootCertificate.cer http://vs-cache.local/vs/buildtools2022_17.12/certificates/manifestRootCertificate.cer; ` curl.exe -SL --output C:\build\CA\vs_installer_opc.RootCertificate.cer http://vs-cache.local/vs/buildtools2022_17.12/certificates/vs_installer_opc.RootCertificate.cer; ` curl.exe -SL --output C:\build\CA\manifestCounterSignRootCertificate.cer http://vs-cache.local/vs/buildtools2022_17.12/certificates/manifestCounterSignRootCertificate.cer; ` curl.exe -SL --output C:\build\CA\Microsoft_Windows_Code_Signing_PCA_2024.crt http://vs-cache.local/vs/buildtools2022_17.12/certificates/Microsoft_Windows_Code_Signing_PCA_2024.crt; ` Get-ChildItem C:\build\CA | Import-Certificate -CertStoreLocation Cert:\\currentuser\\CA; ` Get-ChildItem C:\build\CA | Import-Certificate -CertStoreLocation Cert:\\LocalMachine\\CA; ` Get-ChildItem C:\build\CA | Import-Certificate -CertStoreLocation Cert:\\LocalMachine\\Root; ` Start-Process -Wait -NoNewWindow -FilePath C:\build\vs_buildtools.exe -ArgumentList '--in c:\build\response.json --layoutUri http://vs-cache.local/vs/buildtools2022_17.12/ --wait --installPath C:\BuildTools\'; ` Remove-Item -Path "$env:temp\*" -Recurse -Force; ` if (Test-Path "c:\temp"){Remove-Item -Path "c:\temp\*" -Recurse -Force}; ` if (Test-Path 'C:\ProgramData\Package Cache') {Remove-Item -Path 'C:\ProgramData\Package Cache' -Recurse -Force}; ` GCI 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\*.*' -Force | ? {$_.name -ne 'vswhere.exe'} | Remove-Item -Force; ` GCI 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\*' -Force | ? {$_.Mode -eq 'd-----'} | Remove-Item -Force -Recurse; ` GCI c:\build | Remove-Item -Force; #далее вы можете установить дополнительные пакеты и сделать настройки, в том числе установить SDK WDK, но одно тянет другое и сильно перегружает статью.
Docker при чтении съедает двойные кавычки, наверное их можно как-то экранировать, но либо обходитесь без них, либо используйте одинарные, либо оборачивайте в скрипты.
Если вы планируете устанавливать WDK, у вас из коробки не будет устанавливаться vsix пакеты которые студия автоматом ставит при установке WDK. Так что ручками, я на коленке написал скрипт который это делает.
Скрытый текст
if (!(test-path "C:\Temp\wdk")){new-item -ItemType Directory -Path "C:\Temp\wdk"} $temp_folder = "c:\temp\wdk\new" if (Test-Path "C:\Program Files (x86)\Windows Kits\10\Vsix\VS2022"){$Vsix_2022 = $(GCI "C:\Program Files (x86)\Windows Kits\10\Vsix\VS2022")[0].fullname + "\WDK.vsix"} else{$Vsix_2022 = $false} $vsix_2017 = @( @{ wdk_path = "C:\Program Files (x86)\Windows Kits\10\Vsix\WDK.vsix" build_tools_path = "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools" }, @{ wdk_path = "C:\Program Files (x86)\Windows Kits\10\Vsix\WDK.vsix" build_tools_path = "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools" } ) $vsix_msbuild = @( @{ wdk_path = "C:\Program Files (x86)\Windows Kits\10\Vsix\VS2019\WDK.vsix" build_tools_path = "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools" }, @{ wdk_path = $Vsix_2022 build_tools_path = "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools" } ) foreach ($vsix in $vsix_2017){ if ((test-path $vsix.wdk_path) -and (test-path $vsix.build_tools_path)){ if (!(test-path $temp_folder)){new-item -ItemType Directory -Path $temp_folder} copy-item -path $vsix.wdk_path -Destination "$temp_folder\WDK.zip" Expand-Archive -Path "$temp_folder\WDK.zip" -DestinationPath "$temp_folder" $source_path = "$temp_folder" + '\$VCTargets' $source_path_true = "$temp_folder" + '\VCTargets' Rename-Item -Path $source_path -NewName $source_path_true $destination_path = $vsix.build_tools_path + "\Common7\IDE\VC\VCTargets" copy-item -path "$source_path_true\*" -Destination $destination_path -Recurse -Force Remove-Item "$temp_folder" -Recurse -Force } } foreach ($vsix in $vsix_msbuild){ if ((test-path $vsix.wdk_path) -and (test-path $vsix.build_tools_path)){ if (!(test-path $temp_folder)){new-item -ItemType Directory -Path $temp_folder} copy-item -path $vsix.wdk_path -Destination "$temp_folder\WDK.zip" Expand-Archive -Path "$temp_folder\WDK.zip" -DestinationPath "$temp_folder" $source_path = "$temp_folder" + '\$MSBuild' $source_path_true = "$temp_folder" + '\MSBuild' Rename-Item -Path $source_path -NewName $source_path_true $destination_path = $vsix.build_tools_path + "\MSBuild" copy-item -path "$source_path_true\*" -Destination $destination_path -Recurse -Force Remove-Item "$temp_folder" -Recurse -Force } }
Только build_tools_path - у вас должен быть актуальный, в скрипте он ссылается на оригинальный путь вроде C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools что требует кавычек, а в Dockerfile я использую другой путь. (Вообще, у себя я ставлю через скрипты, но в статье сильно все упростил)
Сертификат который внезапно требуется, но не идет с поставкой
https://www.microsoft.com/pkiops/certs/Microsoft Windows Code Signing PCA 2024.crt
Сервер для раздачи файлов студии
Нужно ознакомится с
Если кратко, то поднять видновый сервер с IIS и настроить его для раздачи файлов по сети.
Чтобы облегчить вам труд, выставите параметры
MIME TYPES
.vsix | application/octet-stream |
.ps1 | application/postscript |
.opc | application/octet-stream |
.nupkg | application/octet-stream |
.msu | application/octet-stream |
.cer | application/octet-stream |
Далее сделайте offline инсталятор с помощью скачанного vs_buildtools.exe в самом начале статьи и перечислением всех нужных вам компонентов включая рекомендованные.
Опубликуйте полученный каталог с помощью IIS.
response.json
То, что скачаете все нужные файлы студии не значит, что вы их все установите. Тем более нужно описывать в виде кода, что вы устанавливаете. И тут нам пригодится response.json.
Скрытый текст
{ "installChannelUri": ".\\ChannelManifest.json", "channelUri": "http://vs-cache.local/vs/buildtools2022_17_12/ChannelManifest.json", "installCatalogUri": ".\\Catalog.json", "channelId": "VisualStudio.17.Release", "productId": "Microsoft.VisualStudio.Product.BuildTools", "quiet": true, "passive": false, "norestart": true, "noUpdateInstaller": true, "nocache": true, "includeRecommended": false, "add": [ "Microsoft.VisualStudio.Workload.ManagedDesktopBuildTools", "Microsoft.NetCore.Component.SDK", "Microsoft.VisualStudio.Workload.MSBuildTools", "Microsoft.VisualStudio.Workload.VCTools", "Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Llvm.Clang", "Microsoft.Component.VC.Runtime.UCRTSDK", "Microsoft.VisualStudio.Component.TestTools.BuildTools", "Microsoft.VisualStudio.Component.VC.ASAN", "Microsoft.VisualStudio.Component.VC.CMake.Project", "Microsoft.VisualStudio.Component.VC.Llvm.Clang", "Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset", "Microsoft.VisualStudio.Component.VC.ATL", "Microsoft.VisualStudio.Component.VC.ATL.Spectre", "Microsoft.VisualStudio.Component.VC.ATLMFC", "Microsoft.VisualStudio.Component.VC.ATLMFC.Spectre", "Microsoft.VisualStudio.Component.VC.CLI.Support", "Microsoft.VisualStudio.Component.VC.MFC", "Microsoft.VisualStudio.Component.VC.MFC.Spectre", "Microsoft.VisualStudio.Component.VC.x86.x64", "Microsoft.VisualStudio.Component.VC.x86.x64.Spectre" ], "addProductLang": [ "en-US" ] }
По сути все тоже самое что вы передаете в виде параметров, но в более удобном формате.
Так вы наглядно видите какие компоненты вы установили и чего вам может не хватать.
Ложка дегтя
Вы обязательно столкнетесь с ошибкой
hcsshim::ImportLayer failed in Win32: The system cannot find the path specified. (0x3)
Полезете в гугл и наткнетесь на ишак https://github.com/microsoft/hcsshim/issues/835
Используйте mcr.microsoft.com/windows/server вместо mcr.microsoft.com/windows/servercore
Не устанавливайте cygwin, и если устанавливаете, то перепаковывайте его. Жесткие ссылки которые он создает при установке не дают сжать слой.
Иногда ошибка появляется если какой-то из слоев сломан, помогает docker zap или самое простое это форматнуть диск на котором вы храните data-root. Всегда помогает и это очень быстро.
Если ставите build tools, всегда указывайте путь установки --installPath, даже если это путь по умолчанию. Без этой опции почти наверняка получите ошибку.
Ну и да, докер образ будет жирным, так что не пытайтесь установить всё, что может понадобится, ставьте только нужное.
Я не разобрал в статье как безопасно передавать креды, ответ никак. Ну почти, я для этого создаю временную smb шару на хосте и маплю её в докер при сборке и оттуда уже беру нужные зашифрованные креды. Вы можете придумать свой велосипед. В теории вы можете установить на винде отдельно containerd и buildkit, - они есть под винду и собирать на них, но я не осилил. В том числе потому, что buildkit смотрит в манифест, даже если тянет его с локального реестра, а там пути ведут в mcr.microsoft.com, а интернета нет и он получает отлуп.
Но через тернии к версионированию, может windows и утрачивает свои позиции, но я много страдал и возможно благодаря этой статье вам придется делать это меньше. Хоть она и получилась сумбурной.
