Construir imágenes de Docker Multi-arch utilizando Manifest, Buildx y GitHub Actions
Tags: docker, tutorial, spanish, python
Published: 2022-08-14 04:59:00

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](https://docs.python.org/3/library/platform.html)

```py
#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

```dockerfile
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.
```sh
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.

```dockerfile
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](https://docs.docker.com/engine/reference/commandline/manifest/)

Vamos a inspeccionar el manifiesto de la imagen base que utilizamos que es “python:alpine3.10”

```sh
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
```json
{
   "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

```sh
# 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í 


![Docker Hub Con Manifest](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kbwt2dksvy671tijgpi6.png)


Ahora creemos el manifiesto que listará estas dos imágenes con diferentes tags en una sola unidad.

```sh
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
```sh
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


![hub Nuevo Manifiesto](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5cpde052at07u0ug7xu3.png)

Podemos probar las imágenes basadas en manifest desde diferentes instancias para obtener los resultados de la arquitectura mediante python

```sh
docker run jevillanueva/multiarch:manifest-latest
```
He aquí el resultado desde mi máquina personal de amd64 y desde una máquina arm64


![Ejecuciones](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bx1sp17ynw9nufe2kgoi.png)

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](https://github.com/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
```sh
docker buildx create --name mybuild
docker buildx use mybuild
```

```sh
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


![Construyendo con buildx](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6n17fbvbl3kfxsxrnmbo.png)

Del mismo modo procedemos a probar desde diferentes dispositivos con diferentes arquitecturas.


![prueba con buildx](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1ndo5e9ccdt6iecomscv.png)

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

```yaml
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](https://github.com/jevillanueva/multiarch)

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](https://jevillanueva.dev)