Cuando trabajas en entornos de arquitectura uniforme es bastante fácil lidiar con la construcción de imágenes de docker, todos trabajando bajo la misma arquitectura “amd64” por ejemplo.
Pero qué sucede cuando trabajas con una Macbook Air con un chip M1 que es “arm64”, y tiene que generar imágenes para “amd64”, o a la inversa, trabajas con un equipo “amd64” y tienes que levantar contenedores en un cluster de Raspberry PI o servidores ARM, que están ganando cada vez más popularidad entre los proveedores de cloud.
El objetivo de hoy es enseñar desde mi perspectiva y en español a generar imágenes docker multiarquitectura o multi-arch y publicarlas en el Docker Hub.
Lo primero es tener una aplicación en mi caso utilizaré un archivo simple de Python que nos obtenga información acerca del actual sistema. Para lo cual utilizaremos la librería platform
#main.py
import platform
platform_var = platform.uname()
print ("*"*30)
print (f'Machine:\t{platform_var.machine}')
print (f'System:\t\t{platform_var.system}')
print (f'Node:\t\t{platform_var.node}')
print (f'Release:\t{platform_var.release}')
print (f'Version:\t{platform_var.version}')
print (f'Processor:\t{platform_var.processor}')
print ("*"*30)
Lo siguiente es armar nuestro Dockerfile normalmente de la siguiente manera, esta es la versión inicial, más adelante le haremos pequeñas modificaciones para poder visualizar en qué arquitectura estamos construyendo
FROM python:alpine3.10
WORKDIR /app
COPY . .
CMD [ "python", "main.py" ]
Hasta este punto no tenemos nada del otro mundo podemos generar la imagen y hacerla correr.
docker build . -t jevillanueva/multiarch:latest
docker run jevillanueva/multiarch:latest
Donde tenemos una salida similar a esta:
Machine: x86_64
System: Linux
Node: 8ca0b41e5372
Release: 4.19.128-microsoft-standard
Version: #1 SMP Tue Jun 23 12:58:10 UTC 2020
Processor:
Hasta aquí ya sabemos que funciona lo siguiente es modificar el Dockerfile donde añadiremos un par de cosas para poder visualizar lo que pasa.
FROM --platform=$TARGETPLATFORM python:alpine3.10
ARG TARGETPLATFORM
WORKDIR /app
RUN echo "construyendo para $TARGETPLATFORM" > /log
COPY . .
CMD [ "python", "main.py" ]
Añadimos la opción platform y el argumento “TARGETPLATFORM”, en este caso
Existen dos caminos para realizar el proceso multi-arch, la primera la más antigua xD, la describiremos a continuación nos servirá para entender cómo funciona.
DOCKER MANIFEST
Todas las imágenes de docker cuentan con un manifest, un manifest contiene información acerca de la imagen, sus layers o capas, el tamaño y su digest que es un id inmutable para cada imagen, para nuestro caso de multi-arch esto es vital ya que en el manifiesto se encuentra detallado para imágenes con funcionalidades idénticas pero para diferentes sistemas operativos y arquitecturas. Puede ver mas de manifest
Vamos a inspeccionar el manifiesto de la imagen base que utilizamos que es “python:alpine3.10”
docker manifest inspect python:alpine3.10
Tendremos una salida similar a esta que nos muestra el tamaño, su digest, las plataformas, el sistema operativo y sus arquitecturas
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1368,
"digest": "sha256:655edcda221823fcdb79b61095dd77e6c767bf1543505dcf078f6945497c7fcf",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1368,
"digest": "sha256:16779e4847cac747c0496fc56d0daa7c2090ea6cb2e4c9aa455672d6818d7179",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v6"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1368,
"digest": "sha256:3dee5de82f12477d1f0a8ed0836181064863ba84cd8098b9c62c65f8347c656b",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v7"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1368,
"digest": "sha256:001faf6c4b5e87a3a4b251e72bb9a9dd1a5ef93083fc3a42d54e8734dc76e1f5",
"platform": {
"architecture": "arm64",
"os": "linux",
"variant": "v8"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1368,
"digest": "sha256:42829bfc005ccdd282d82da1328c9a2511e20f09439c702311d30627c91c70ca",
"platform": {
"architecture": "386",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1368,
"digest": "sha256:52e6ffac79478822ea62492d73c7aac3eca9a1cf41e8531899744e8e4958c236",
"platform": {
"architecture": "ppc64le",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 1368,
"digest": "sha256:90f421378ca9b94b071a20e7c4b2df746d9b4bc26dcbd85ea79f0db9322d330f",
"platform": {
"architecture": "s390x",
"os": "linux"
}
}
]
}
Hasta aquí ya comprendemos a grandes rasgos qué papel importante juega manifest, ahora nos utilizaremos para crear las imágenes multi-arch así que manos a la obra, realizaremos para dos arquitecturas amd64 y arm64.
Imagenes usando manifest
Lo primero es construir las imágenes para cada arquitectura
# AMD64
docker build . -t jevillanueva/multiarch:manifest-amd64 --build-arg TARGETPLATFORM=amd64
docker push jevillanueva/multiarch:manifest-amd64
# ARM64
docker build . -t jevillanueva/multiarch:manifest-arm64 --build-arg TARGETPLATFORM=arm64
docker push jevillanueva/multiarch:manifest-arm64
Ya tenemos construidas nuestras dos imágenes para diferentes arquitecturas y publicadas en el Docker Hub puede verse algo así
Ahora creemos el manifiesto que listará estas dos imágenes con diferentes tags en una sola unidad.
docker manifest create jevillanueva/multiarch:manifest-latest --amend jevillanueva/multiarch:manifest-amd64 --amend jevillanueva/multiarch:manifest-arm64
Y Posterior a este realizamos el publicado del nuevo manifiesto
docker manifest push jevillanueva/multiarch:manifest-latest
Como se puede visualizar ahora en nuestro registry se encuentra un nuevo tag que hace referencia a las dos imágenes y sus respectivos sistemas operativos y arquitecturas
Podemos probar las imágenes basadas en manifest desde diferentes instancias para obtener los resultados de la arquitectura mediante python
docker run jevillanueva/multiarch:manifest-latest
He aquí el resultado desde mi máquina personal de amd64 y desde una máquina arm64
Hasta aquí es la forma clásica y detallada para comprender el porqué de las cosas xD ahora algo mucho más rápido actualmente existen una nueva herramienta desarrollada por Docker que podemos utilizar para estos escenarios llamada “Buildx”
DOCKER BUILDX
Docker buildx es una herramienta que nos permite acceder a las características de la herramienta de construcción moby buildkit, nos permite soportar multiples instancias asi que procedemos a crear nuestras imágenes para diferentes plataformas, creamos la instancia para buildx llamada “mybuild” y usamos esa instancia
docker buildx create --name mybuild
docker buildx use mybuild
docker buildx build . --push -t jevillanueva/multiarch:buildx-latest --platform linux/amd64,linux/arm64
Después de un proceso de descarga y compilación utilizando moby/buildkit se publica la imagen en el docker hub y se tiene un resultado similar que usando manifest
Del mismo modo procedemos a probar desde diferentes dispositivos con diferentes arquitecturas.
Como se puede ver existen varias maneras de realizar esto. Y para finalizar como un extra les dejo un modelo de para github actions para generar imágenes multiarch utilizando qemu para virtualizar las arquitecturas.
Github Actions
name: Docker Image CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
env:
IMAGE_NAME: ${{ github.repository }}
IMAGE_TAG: ${{ github.sha }}
jobs:
build:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Docker Login
env:
DOCKER_USER: ${{secrets.DOCKER_USER}}
DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}}
run: |
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
-
name: Build and push
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ env.IMAGE_NAME }}:${{env.IMAGE_TAG}},${{ env.IMAGE_NAME }}:latest
Aquí encuentran el repositorio del proyecto
Enlaces utilizados:
https://docs.docker.com/registry/spec/manifest-v2-2/
https://docs.docker.com/engine/reference/commandline/manifest/
https://docs.docker.com/build/buildx/multiplatform-images/
https://github.com/docker/setup-buildx-action#with-qemu
Ahí les dejo mi página personal jevillanueva.dev
Top comments (0)