¡Hola a todos! Me llamo Andrey y soy ingeniero DevOps en la empresa Exness, en el equipo de desarrollo. Mi actividad principal se centra en la construcción, despliegue y soporte de aplicaciones en Docker bajo el sistema operativo Linux (en adelante, SO). No hace mucho, se me presentó un desafío similar, pero el SO objetivo del proyecto se convirtió en Windows Server y un conjunto de proyectos en C++. Para mí, fue la primera vez que interactué de manera intensa con contenedores Docker en Windows y, en general, con aplicaciones en C++. Gracias a esto, adquirí una experiencia interesante y aprendí sobre algunas peculiaridades de la contenedorización de aplicaciones en Windows.

En este artículo quiero hablar sobre las dificultades con las que me encontré y cómo logré resolverlas. Espero que esto sea útil para resolver tus tareas actuales y futuras. ¡Buena lectura!
¿Por qué contenedores?
La empresa cuenta con una infraestructura existente de orquestador de contenedores Hashicorp Nomad y componentes relacionados — Consul y Vault. Por lo tanto, la contenedorización de aplicaciones fue elegida como un método unificado para la entrega de soluciones listas. Dado que en la infraestructura del proyecto hay hosts Docker con versiones de Windows Server Core 1803 y 1809, es necesario construir por separado las versiones de imágenes Docker para 1803 y 1809. En la versión 1803, es importante recordar que el número de revisión del host Docker de construcción debe coincidir con el número de revisión de la imagen base de Docker y del host donde se ejecutará el contenedor de esta imagen. La versión 1809 carece de esta desventaja. Se puede leer más a fondo. .
¿Por qué multi-stage?
Los ingenieros del equipo de desarrollo no tienen acceso a los hosts de construcción o su acceso es muy limitado, no hay posibilidad de gestionar rápidamente un conjunto de componentes para la construcción de la aplicación en estos hosts, por ejemplo, instalar un conjunto de herramientas adicional o cargas de trabajo para Visual Studio. Por lo tanto, decidimos que todos los componentes necesarios para la construcción de la aplicación se instalarían en la imagen Docker de construcción. Si es necesario, se puede modificar rápidamente solo el dockerfile y lanzar el pipeline de creación de esta imagen.
De la teoría a la práctica
En una construcción de imagen docker multi-stage ideal, la preparación del entorno para compilar la aplicación ocurre en el mismo script dockerfile que la propia construcción de la aplicación. Sin embargo, en nuestro caso se ha añadido un eslabón intermedio, es decir, un paso de creación preliminar de la imagen docker con todo lo necesario para compilar la aplicación. Esto se hizo para aprovechar la caché de docker y así reducir el tiempo de instalación de todas las dependencias.
Analicemos los puntos principales del script dockerfile para formar esta imagen.
Para crear imágenes de diferentes versiones de SO en el dockerfile, se puede definir un argumento a través del cual se pasa el número de versión durante la compilación, y ese mismo será la etiqueta de la imagen base.
La lista completa de etiquetas de imágenes de Microsoft Windows Server se puede encontrar .
ARG WINDOWS_OS_VERSION=1809
FROM mcr.microsoft.com/windows/servercore:$WINDOWS_OS_VERSIONPor defecto, los comandos en la instrucción RUN dentro del dockerfile en Windows se ejecutan en la consola cmd.exe. Para facilitar la escritura de scripts y la expansión de las funciones de los comandos utilizados, redefiniremos la consola de ejecución de comandos a Powershell a través de la instrucción SHELL.
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';"]El siguiente paso es instalar el gestor de paquetes chocolatey y los paquetes necesarios:
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 paquetes usando chocolatey, se pueden pasar en lista o se pueden instalar uno a uno en caso de que sea necesario proporcionar parámetros únicos para cada paquete. En nuestra situación, utilizamos un archivo de manifiesto en formato XML, que especifica la lista de paquetes requeridos y sus parámetros. Su contenido es el siguiente:
A continuación, instalamos el entorno de construcción de la aplicación, específicamente, MS Build Tools 2019, que es una versión reducida de Visual Studio 2019 que contiene el conjunto mínimo de componentes necesarios para compilar código.
Para trabajar plenamente con nuestro proyecto C++, necesitaremos componentes adicionales, a saber:
- Herramientas de carga de trabajo C++
- Conjunto de herramientas v141
- SDK de Windows 10 (10.0.17134.0)
Se puede instalar un conjunto de herramientas extendido automáticamente mediante un archivo de configuración en formato JSON. Contenido del archivo de configuración:
La lista completa de componentes disponibles se puede encontrar en el sitio de documentación .
{
"version": "1.0",
"components": [
"Microsoft.Component.MSBuild",
"Microsoft.VisualStudio.Workload.VCTools;includeRecommended",
"Microsoft.VisualStudio.Component.VC.v141.x86.x64",
"Microsoft.VisualStudio.Component.Windows10SDK.17134"
]
}En el dockerfile se ejecuta un script de instalación, y para comodidad se agrega la ruta a los archivos ejecutables de las herramientas de construcción a la variable de entorno PATH. También es recomendable eliminar archivos y directorios innecesarios para reducir el tamaño de la imagen.
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)En esta etapa, nuestra imagen para compilar la aplicación C++ está lista, y se puede proceder a la creación de la construcción multi-etapa de Docker de la aplicación.
Multi-etapa en acción
Como imagen de construcción, utilizaremos la imagen creada con todas las herramientas a bordo. Al igual que en el script de dockerfile anterior, añadiremos la posibilidad de indicar dinámicamente el número de versión / etiqueta de la imagen para facilitar la reutilización del código. Es importante añadir la etiqueta como constructor a la imagen de construcción en la instrucción FROM.
ARG WINDOWS_OS_VERSION=1809
FROM buildtools:$WINDOWS_OS_VERSION as builderHa llegado el momento de compilar la aplicación. Aquí es bastante sencillo: copiar el código fuente y todo lo relacionado, y ejecutar el proceso de compilación.
COPY myapp .
RUN nuget restore myapp.sln ;
msbuild myapp.sln /t:myapp /p:Configuration=ReleaseLa etapa final de la creación de la imagen final es especificar la imagen base de la aplicación, donde se ubicarán todos los artefactos de compilación y los archivos de configuración. Para copiar los archivos compilados de la imagen de construcción intermedia, se debe especificar el parámetro --from=builder en la instrucción COPY.
FROM mcr.microsoft.com/windows/servercore:$WINDOWS_OS_VERSION
COPY --from=builder C:/x64/Release/myapp/ ./
COPY ./configs ./Ahora solo queda añadir las dependencias necesarias para el funcionamiento de nuestra aplicación y especificar el comando de inicio a través de las instrucciones ENTRYPOINT o CMD.
Conclusión
En este artículo, hablo sobre cómo crear un entorno completo de compilación para aplicaciones C++ dentro de un contenedor en Windows y cómo utilizar las capacidades de las compilaciones multi-etapa de Docker para crear imágenes completas de nuestra aplicación.
Fuente: habr.com
