Usando docker multi-stage para construir imagens do Windows

Olá a todos! Meu nome é Andrey e trabalho como engenheiro DevOps na Exness na equipe de desenvolvimento. Minha principal atividade está relacionada à construção, implantação e suporte de aplicações em docker no sistema operacional Linux (doravante denominado SO). Há pouco tempo tive uma tarefa com as mesmas atividades, mas o sistema operacional alvo do projeto era o Windows Server e um conjunto de projetos C++. Para mim, esta foi a primeira interação próxima com contêineres docker no sistema operacional Windows e, em geral, com aplicativos C++. Graças a isso, tive uma experiência interessante e aprendi algumas das complexidades da conteinerização de aplicativos no Windows.

Usando docker multi-stage para construir imagens do Windows

Neste artigo quero contar quais dificuldades tive que enfrentar e como consegui resolvê-las. Espero que isso seja útil para seus desafios atuais e futuros. Gostar de ler!

Por que contêineres?

A empresa possui infraestrutura existente para o orquestrador de contêineres Hashicorp Nomad e componentes relacionados – Consul e Vault. Portanto, a conteinerização de aplicações foi escolhida como um método unificado para entregar uma solução completa. Como a infraestrutura do projeto contém hosts docker com versões 1803 e 1809 do Windows Server Core OS, é necessário construir versões separadas de imagens docker para 1803 e 1809. Na versão 1803, é importante lembrar que o número de revisão do build docker host deve corresponder ao número de revisão da imagem base do Docker e ao host onde o contêiner desta imagem será iniciado. A versão 1809 não tem essa desvantagem. Você pode ler mais aqui.

Por que multiestágio?

Os engenheiros da equipe de desenvolvimento não têm acesso ou têm acesso muito limitado para criar hosts; não há como gerenciar rapidamente o conjunto de componentes para criar um aplicativo nesses hosts, por exemplo, instalar um conjunto de ferramentas ou carga de trabalho adicional para o Visual Studio. Portanto, tomamos a decisão de instalar todos os componentes necessários para construir o aplicativo na imagem Docker de construção. Se necessário, você pode alterar rapidamente apenas o dockerfile e iniciar o pipeline para criar esta imagem.

Da teoria à ação

Em uma construção de imagem de vários estágios do Docker ideal, o ambiente para construir o aplicativo é preparado no mesmo script Dockerfile em que o próprio aplicativo é construído. Mas no nosso caso foi adicionado um link intermediário, ou seja, a etapa de criação preliminar de uma imagem docker com tudo o que é necessário para construir a aplicação. Isso foi feito porque eu queria usar o recurso docker cache para reduzir o tempo de instalação de todas as dependências.

Vejamos os pontos principais do script dockerfile para criar esta imagem.

Para criar imagens de diferentes versões de SO, você pode definir um argumento no dockerfile através do qual o número da versão é passado durante a construção, e também é a tag da imagem base.

Uma lista completa de tags de imagem do Microsoft Windows Server pode ser encontrada aqui.

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

Por padrão, os comandos nas instruções RUN dentro do dockerfile no sistema operacional Windows, eles são executados no console cmd.exe. Para a comodidade de escrever scripts e ampliar a funcionalidade dos comandos utilizados, iremos redefinir o console de execução de comandos no Powershell através da instrução SHELL.

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

A próxima etapa é instalar o gerenciador de pacotes chocolatey e os pacotes necessários:

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'

Para instalar pacotes usando chocolatey, você pode simplesmente passá-los como uma lista ou instalá-los um de cada vez se precisar passar parâmetros exclusivos para cada pacote. Em nossa situação, utilizamos um arquivo de manifesto em formato XML, que contém uma lista de pacotes necessários e seus parâmetros. Seu conteúdo é assim:

<?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>

A seguir, instalamos o ambiente de construção do aplicativo, ou seja, MS Build Tools 2019 - esta é uma versão leve do Visual Studio 2019, que contém o conjunto mínimo de componentes necessário para compilar o código.
Para funcionar totalmente com nosso projeto C++, precisaremos de componentes adicionais, a saber:

  • Ferramentas de carga de trabalho C++
  • Conjunto de ferramentas v141
  • SDK do Windows 10 (10.0.17134.0)

Você pode instalar um conjunto estendido de ferramentas automaticamente usando um arquivo de configuração no formato JSON. Conteúdo do arquivo de configuração:

Uma lista completa de componentes disponíveis pode ser encontrada no site de documentação 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"
  ]
}

O dockerfile executa o script de instalação e, por conveniência, adiciona o caminho para os arquivos executáveis ​​das ferramentas de construção à variável de ambiente PATH. Também é aconselhável remover arquivos e diretórios desnecessários para reduzir o tamanho da imagem.

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)

Neste estágio, nossa imagem para compilar o aplicativo C++ está pronta e podemos prosseguir diretamente para a criação de uma compilação docker em vários estágios do aplicativo.

Multiestágio em ação

Usaremos a imagem criada com todas as ferramentas integradas como uma imagem de construção. Como no script dockerfile anterior, adicionaremos a capacidade de especificar dinamicamente o número da versão/tag de imagem para facilitar a reutilização do código. É importante adicionar um rótulo as builder para a imagem de montagem nas instruções FROM.

ARG WINDOWS_OS_VERSION=1809
FROM buildtools:$WINDOWS_OS_VERSION as builder

Agora é hora de construir o aplicativo. Tudo aqui é bastante simples: copie o código-fonte e tudo o que está associado a ele e inicie o processo de compilação.

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

A etapa final da criação da imagem final é especificar a imagem base da aplicação, onde estarão localizados todos os artefatos de compilação e arquivos de configuração. Para copiar arquivos compilados de uma imagem de montagem intermediária, você deve especificar o parâmetro --from=builder em instruções COPY.

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

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

Agora só falta adicionar as dependências necessárias para que nossa aplicação funcione e especificar o comando de lançamento através das instruções ENTRYPOINT ou CMD.

Conclusão

Neste artigo, falei sobre como criar um ambiente de compilação completo para aplicativos C++ dentro de um contêiner no Windows e como usar os recursos de compilações de vários estágios do docker para criar imagens completas de nosso aplicativo.

Fonte: habr.com

Adicionar um comentário