Crear Imágenes Multi-Arquitectura en Docker con buildx
PROMO DigitalOcean
Antes de comenzar, quería contarles que hay una promoción en DigitalOcean donde te dan un crédito de USD 100.00 durante 60 días para que puedas probar los servicios que este Proveedor Cloud ofrece. Lo único que tienes que hacer es suscribirte a DigitalOcean con el siguiente botón:
O a través del siguiente enlace: https://bit.ly/digitalocean-itsm
Introducción
En este artículo vamos a usar buildx para crear imágenes de Docker para distintas arquitecturas de CPU (mientras la imagen base lo permita) usando qemu y publicándolas como una sola imagen a DockerHub.
Proyecto de Ejemplo
Imaginemos que tenemos un proyecto que se ejecuta en distintos servidores con distintas arquitecturas de CPU (arm64, ppc, etc) y queremos pasarlo a Docker. Queremos que esta imagen resultante se ejecute en las siguientes arquitecturas:
- linux/amd64
- linux/arm64/v8
- linux/arm/v7
- linux/arm/v6
- linux/ppc64le
- linux/s390x
La construcción de esta imagen la podemos realizar en paralelo para cada arquitectura de CPU, pero solo publicando la imagen con todas estas arquitecturas en una sola.
Aqui tenemos una aplicación de ejemplo que es un servidor que solo muestra un hello world en nodejs:
|
|
La guardamos como app.js y procedemos a escribir su Dockerfile:
|
|
Bien sencillo, imagen base node:16-alpine, instalamos tini con el gestor de paquetes de alpine, creamos una carpeta en la imagen /app y copiamos el archivo que va a levantar el servidor app.js e inicializamos que cada vez que se entre al contenedor el comando a ejecutar será "node", "/app/app.js"
, y para finalizar exponemos el puerto 3000.
Configurando Nuestro Sistema
Los comandos a continuación dan por sentado que estás utilizando Ubuntu, para cualquier otra distro te aconsejo instalar los paquetes correspondientes.
QEMU
Instalamos los paquetes necesarios de QEMU con el siguiente comando:
|
|
Para verificar las arquitecturas que se han instalado (por ejemplo, ARM 64 Bits (aarch64)) lo podremos hacer con el comando:
|
|
Deberia mostrar la siguiente salida:
|
|
Verificamos la versión del ejecutavle qemu-aarch64-static:
|
|
Y la siguiente salida:
|
|
Verificamos la version de update-binfmts:
|
|
Alternativa: Instalación de las Herramientas por una Imagen Docker
Alternativamente, podemos instalar las herramientas de QEMU para generar las imágenes a través de un contenedor Docker, que contiene todos los simuladores de QEMU y su propio script para configurarlas en el kernel del host. Los simuladores de QEMU permanecerán registrados y se pueden utilizar siempre y cuando el host se encuentre encendido, por lo que en caso de reiniciar el sistema, tendremos que levantar manualmente el contenedor (a menos que le apliquemos un Docker Compose).
Iniciamos entonces el contenedor con el siguiente comando:
|
|
Construyendo Nuestra Imagen Multi-Arch
Vamos a configurar ahora buildx para poder construir nuestras imagenes. Si
Primero, creamos nuestra instancia de buildx:
|
|
El nombre es indistinto, podemos colocarle cualquier nombre despues del argumento --name
. Seguido de esto, vamos a invocarla con el argumento use
:
|
|
Verificamos nuestra instancia de buildx creada con el comando:
|
|
Nos debería dar la siguiente salida:
|
|
Observa la linea que dice Platforms. En esta linea podemos ver las múltiples arquitecturas no nativas que se han instalado a través de QEMU. Si solo muestra las arquitecturas para linux/amd64 y linux/386, es que aún no se han instalado los paquetes correspondientes. En este último caso, ejecuta el comando docker buildx rm
y vuelve a crearlo.
También podemos visualizar la información de la instancia de buildx mybuilder
con el comando:
|
|
Ya tenemos todo listo, construyamos la imagen ahora si:
|
|
Esto nos creará la imagen para 3 arquitecturas distintas: amd64, arm64 e i386. Podemos simplificar un poco exportando variables de entorno para facilitar la construcción:
Para arm64:
|
|
Para amd64:
|
|
Para i386:
|
|
Como construímos estas imágenes por separado ahora las podemos juntar y enviarlas a nuestro DockerHub. Voy
Preparamos el manifiesto:
|
|
Compilamos y enviamos nuestras imágenes como una sola:
|
|
Configurando CI/CD
Que gracia tendría perder tiempo con todos estos comandos si podemos directamente automatizar todo el proceso mediante CI/CD y que se construyan y desplieguen nuestras imágenes cada cierto tiempo. En este caso lo voy a hacer con GitLab pero pueden escoger cualquier otra herramienta (Jenkins, GitHub Actions, etc)
Tenemos que tener dos variables configuradas en nuestro repositorio, yendo al repositorio creado, Configuracion -> CI/CD, desplegamos la opción Variables:
- DOCKERHUB_USERNAME: nuestro nombre de usuario de DockerHub
- DOCKERHUB_TOKEN: nuestra contraseña en DockerHub
Dentro del directorio donde veníamos trabajando y tenemos nuestro Dockerfile y el archivo de nuestra aplicación app.js
, vamos a crear otro llamado .gitlab-ci.yml
con el siguiente contenido:
|
|
Todas las tareas tienen casi la misma estructura.
Hay dos etapas: build y package. En build vamos a construir nuestras imágenes por separado y en package las vamos a empaquetar en una sola imagen.
La clave:
|
|
Le indicamos a GitLab que use un servicio docker in docker, así nos permite utilizar comandos del cliente docker en la instancia de docker, ya que la imagen no necesariamente sería un instancia de docker pura y por ello necesitamos que GitLab nos proporcione ese servicio (suena confuso, pero así sería)
Las claves:
|
|
Le indicamos en forma de variable la plataforma en la cual va a generar la imagen. En cada build hay una arquitectura distinta (amd64, arm64 y arm/v7) como veníamos trabajando por linea de comandos. La segunda es la imagen que vamos a utilizar que ya viene con el buildx habilitado por defecto.
|
|
Con la clave before_script le decimos a GitLab que antes de ejecutar las tareas principales, haga, digamos, un calentamiento, o tareas que normalmente serian repetitivas (como autenticación), le pasamos el usuario y clave al comando docker login
y de esta manera iniciamos sesión en DockerHub.
|
|
Ya en la clave script estaríamos derechamente construyendo la imagen y enviándola directamente a DockerHub.
En nuestra segunda etapa, seria el package. No hay ningún secreto ni complejidad, nos autenticamos y lo que hacemos es crear el manifiesto con cada una de las imágenes generadas y compilar el set de imágenes en una sola para enviarla a DockerHub con la etiqueta latest
Comprobando
Por último, comprobemos el CI/CD y si la imagen se generó correctamente. Para ello vamos en GitLab al menu CI/CD -> Pipelines:
Observamos que se ha ejecutado con éxito, por lo que vamos a DockerHub y comprobemos que nuestra imagen está allí:
Si hacemos una rápida comprobación por linea de comandos:
|
|
Veremos una salida similar que valida que el servicio se encuentra ejecutándose en el puerto 3000:
|
|
Todos los archivos de ejemplo se encontrarán en el siguiente repositorio: https://gitlab.com/enmanuelmoreira/docker-multiarch-ejemplo/
Hasta aquí el artículo, espero les haya gustado, ¡hasta la próxima!
Referencias
- Documentación de buildx: https://docs.docker.com/buildx/working-with-buildx/
Artículos sobre Docker
- Como Instalar Docker en Linux
- Como Instalar Portainer: El Mejor Gestor Gráfico de Docker en Linux
- Conceptos y Comandos Básicos en Docker
- Construyendo Imágenes Personalizadas en Docker con Dockerfile
- Desplegando Aplicaciones con Docker Compose
- Como Configurar un Registro Privado de Docker en Linux
- SupervisorD: Gestionando Procesos en Docker
- Buenas Prácticas al Escribir Dockerfiles
- Crear Imágenes Multi-Arquitectura en Docker con buildx
Apoya este Proyecto
Si te pareció útil este artículo y el proyecto en general, considera brindarme un café :)
Buy me a coffee