Utilizzo di docker multi-stage per costruire immagini Windows

Ciao a tutti! Mi chiamo Andrey e lavoro come ingegnere DevOps presso Exness nel team di sviluppo. La mia attività principale riguarda la creazione, il deployment e il supporto delle applicazioni in Docker su sistemi operativi Linux (di seguito - SO). Recentemente ho ricevuto un compito simile, ma come SO target del progetto è stato scelto Windows Server e un insieme di progetti in C++. Per me è stata la prima interazione stretta con i container Docker su Windows e in generale con le applicazioni in C++. Grazie a questo ho acquisito esperienze interessanti e ho appreso alcune sottigliezze nella containerizzazione delle applicazioni su Windows.

Utilizzo di docker multi-stage per costruire immagini Windows

In questo articolo voglio raccontare quali difficoltà ho dovuto affrontare e come sono riuscito a risolverle. Spero che risulti utile per affrontare le vostre attuali e future sfide. Buona lettura!

Perché i container?

In azienda esiste un'infrastruttura esistente di orchestratori di container Hashicorp Nomad e componenti correlati — Consul e Vault. Pertanto, la containerizzazione delle applicazioni è stata scelta come metodo unificato per la consegna della soluzione pronta. Poiché nell'infrastruttura del progetto ci sono host Docker con versioni di Windows Server Core 1803 e 1809, è necessario costruire separatamente le versioni delle immagini Docker per 1803 e 1809. Nella versione 1803 è importante ricordare che il numero di revisione dell'host di build Docker deve corrispondere al numero di revisione dell'immagine Docker di base e all'host dove il container di quest'immagine verrà avviato. La versione 1809 è esente da questo inconveniente. Maggiori dettagli possono essere letti qui.

Perché multi-stage?

Gli ingegneri del team di sviluppo non hanno accesso agli host di build o hanno accesso molto limitato, non c'è la possibilità di gestire rapidamente l'insieme dei componenti per il build dell'applicazione su questi host, ad esempio, installando un toolset aggiuntivo o un workload per Visual Studio. Pertanto, abbiamo deciso che tutti i componenti necessari per la creazione dell'applicazione venissero installati nell'immagine Docker di build. Se necessario, è possibile modificare rapidamente solo il Dockerfile e avviare il pipeline di creazione di quest'immagine.

Dalla teoria alla pratica

In un'ideale build multi-stage di Docker, la preparazione dell'ambiente per la compilazione dell'applicazione avviene nello stesso script del dockerfile in cui si compila l'applicazione stessa. Tuttavia, nel nostro caso è stato aggiunto un passaggio intermedio, ovvero un passo preliminare per creare un'immagine Docker con tutto il necessario per la costruzione dell'applicazione. Questo è stato fatto perché volevamo sfruttare la possibilità della cache di Docker, per ridurre il tempo di installazione di tutte le dipendenze.

Analizziamo i punti principali dello script del dockerfile per la formazione di questa immagine.

Per creare immagini di diverse versioni del sistema operativo nel dockerfile, è possibile definire un argomento attraverso il quale viene passato il numero di versione durante la costruzione, che è anche il tag dell'immagine di base.

Un elenco completo dei tag delle immagini di Microsoft Windows Server può essere trovato qui.

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

Per impostazione predefinita, i comandi nella direttiva comandi RUN all'interno del dockerfile su Windows vengono eseguiti nella console cmd.exe. Per facilitare la scrittura degli script e ampliare la funzionalità dei comandi utilizzati, ridefiniremo la console di esecuzione dei comandi in Powershell tramite la direttiva SHELL.

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

Il passo successivo è installare il gestore di pacchetti chocolatey e i pacchetti necessari:

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'

Per installare i pacchetti utilizzando chocolatey, è possibile semplicemente passarli in elenco o installarli uno alla volta nel caso sia necessario fornire parametri unici per ciascun pacchetto. Nella nostra situazione abbiamo utilizzato un file manifest in formato XML, in cui è indicato l'elenco dei pacchetti necessari e i loro parametri. Il suo contenuto appare così:

Successivamente installiamo l'ambiente di build dell'applicazione, cioè MS Build Tools 2019 — una versione alleggerita di Visual Studio 2019, contenente il minimo necessario di componenti per compilare il codice.
Per un utilizzo completo del nostro progetto C++, avremo bisogno di componenti aggiuntivi, in particolare:

  • Strumenti per C++
  • Toolset v141
  • SDK di Windows 10 (10.0.17134.0)

È possibile installare un set avanzato di strumenti in modo automatico utilizzando un file di configurazione in formato JSON. Contenuto del file di configurazione:

L'elenco completo dei componenti disponibili può essere trovato sul sito di documentazione 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"
  ]
}

Nel dockerfile viene eseguito uno script di installazione, e per comodità viene aggiunto il percorso ai file eseguibili degli strumenti di build nella variabile d'ambiente PATH. È anche consigliabile rimuovere file e directory non necessari per ridurre le dimensioni dell'immagine.

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)

A questo punto, la nostra immagine per compilare l'applicazione C++ è pronta e possiamo procedere alla creazione della build multi-stage del docker.

Multi-stage in azione

Come immagine di build utilizzeremo l'immagine creata con tutti gli strumenti a bordo. Come nel precedente script del dockerfile, aggiungiamo la possibilità di specificare dinamicamente il numero di versione/tag dell'immagine per facilitare il riutilizzo del codice. È importante aggiungere l'etichetta as builder all'immagine di build nell'istruzione DA.

ARG WINDOWS_OS_VERSION=1809
FROM buildtools:$WINDOWS_OS_VERSION as builder

È giunto il momento di costruire l'applicazione. Qui tutto è abbastanza semplice: copiare il codice sorgente e tutto ciò che è ad esso collegato, e avviare il processo di compilazione.

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

L'ultima fase della creazione dell'immagine finale è la specifica dell'immagine di base dell'applicazione, dove risiederanno tutti gli artefatti di compilazione e i file di configurazione. Per copiare i file compilati dall'immagine di build intermedia è necessario specificare il parametro --from=builder nell'istruzione COPY.

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

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

Ora rimane da aggiungere le dipendenze necessarie per il funzionamento della nostra applicazione e specificare il comando di avvio tramite le istruzioni ENTRYPOINT o CMD.

Conclusione

In questo articolo ho spiegato come creare un ambiente di compilazione completo per applicazioni C++ all'interno di un contenitore su Windows e come utilizzare le funzionalità delle build multi-stage di Docker per creare immagini complete della nostra applicazione.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster