Maven + Mac M1 (aarch64) + Fabric8 docker-maven-plugin

4
5534

Ya llevamos tiempo con los nuevos Mac con chip M1 (aarch64) en el mercado y la verdad es que tenemos muchísimas aplicaciones ya migradas y las que no funcionan bastante bien con Rosetta (de hecho en lagunas ocasiones funciona hasta mejor 🤯).

Sin embargo todavía hay cosas que no van finas y ese es el caso si usas: Maven + Mac M1 aarch64 + io.fabric8 docker-maven-plugin.

En este tutorial vamos a ver como solucionarlo.

Índice

  1. El problema
  2. La solución: Redireccionar la comunicación a través de socat
  3. Bola extra
  4. Conclusiones
  5. Sobre el autor

1. El problema

Cuando ejecutas Maven verás que da un error similar a:

[INFO] --- docker-maven-plugin:0.37.0:start (start) @ test-project ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  4.660 s
[INFO] Finished at: 2021-11-09T10:02:52+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal io.fabric8:docker-maven-plugin:0.37.0:start (start) on project test-project: Execution start of goal io.fabric8:docker-maven-plugin:0.37.0:start failed: An API incompatibility was encountered while executing io.fabric8:docker-maven-plugin:0.37.0:start: java.lang.UnsatisfiedLinkError: could not load FFI provider jnr.ffi.provider.jffi.Provider
[ERROR] -----------------------------------------------------
[ERROR] realm =    plugin>io.fabric8:docker-maven-plugin:0.37.0
[ERROR] strategy = org.codehaus.plexus.classworlds.strategy.SelfFirstStrategy
[ERROR] urls[0] = file:~/.m2/repository/io/fabric8/docker-maven-plugin/0.37.0/docker-maven-plugin-0.37.0.jar

...

[ERROR] : could not get native definition for type `POINTER`, original error message follows: java.lang.UnsatisfiedLinkError: Unable to execute or load jffi binary stub from `/var/folders/8r/_69wbkk57hj3x489xf8r0fh40000gn/T/`. Set `TMPDIR` or Java property `java.io.tmpdir` to a read/write path that is not mounted "noexec".
[ERROR] ~/src/test-project/jffi13802233681866717935.dylib: dlopen(~/src/test-project/jffi13802233681866717935.dylib, 0x0001): tried: '~/src/test-project/jffi13802233681866717935.dylib' (fat file, but missing compatible architecture (have 'i386,x86_64', need 'arm64e')), '/usr/lib/jffi13802233681866717935.dylib' (no such file)
[ERROR]     at com.kenai.jffi.internal.StubLoader.tempLoadError(StubLoader.java:424)
[ERROR]     at com.kenai.jffi.internal.StubLoader.loadFromJar(StubLoader.java:409)

...

Este error ocurre porque la librería de bajo nivel que usa Fabric8 para conectar con el socket del Docker daemon todavía no está adaptada a la nueva arquitectura, así que básicamente el problema es que el docker-maven-plugin de Fabric8 no es capaz de encontrar al Docker daemon.

Puedes encontrar más información de este problema en: https://github.com/fabric8io/docker-maven-plugin/issues/1257

Y también en: https://github.com/jnr/jnr-unixsocket/issues/95

Y un poco más en: https://github.com/jnr/jffi/issues/105

Así que seguramente es cuestión de tiempo que acaben dando soporte (si alguien se anima igual les puede echar una mano 😉).

Pero mientras tanto aquí podemos ver una solución (que encontraréis en inglés en los enlaces anteriores).

2. La solución: Redireccionar la comunicación a través de socat

El truco que vamos a hacer es “engañar” a docker-maven-plugin para que haga la comunicación a través de URL en lugar de socket directo. Y para ello vamos usar la utilidad de línea de comandos socat.

socat se define a sí mismo como:

Socat is a command line based utility that establishes two bidirectional byte streams and transfers data between them.

¿Qué quiere decir esto? Que con socat vamos a ser capaces de redireccionar el tráfico entre docker-maven-plugin y el Docker daemon.

Primero tendremos que instalar socat ya que no viene en el sistema operativo:

brew install socat

Ahora tendremos que definir la variable de entorno:

export DOCKER_HOST=tcp://127.0.0.1:2375

Con esto le estamos diciendo a docker-maven-plugin que en vez de usar el socket use la URL indicada en la variable DOCKER_HOST. Ojo con esta variable porque si la ponéis en los scripts .xxxrc de vuestra shell entonces todos los clientes de Docker van a intentar usar esa URL y esto igual no os interesa, ahí cada uno verá que se adapta mejor al uso que le quiere dar.

Ahora levantamos socat:

socat TCP-LISTEN:2375,range=127.0.0.1/32,reuseaddr,fork UNIX-CLIENT:/var/run/docker.sock

Con esto lo que estamos haciendo es que todo el tráfico que vaya a la URL que habíamos definido con DOCKER_HOSTsocat se va a encargar de mandarlo al puerto del Docker dameon, y viceversa (es decir socat establece una comunicación bidireccional).

Este comando lo podemos lanzar en otra pestaña de nuestro terminal o incluso en background añadiendo la clásica “&” al final, aunque a mi esta opción no me gusta mucho ya que no quiero dejar encendido siempre el socat, sino levantarlo o tirarlo en función de cuando lo necesito.

Y con esto estaría todo, ya deberíamos poder lanzar Maven para ejecutar el plugin docker-maven-plugin con normalidad 👌.

3. Bola extra

Simplemente para que tengáis una referencia rápida, os dejo aquí un ejemplo de configuración del pom.xml de Maven con el plugin docker-maven-plugin para, por ejemplo, levantar una base de datos PostgreSQL para ejecutar los tests de integración.

...
<properties>
  ...
  <docker-maven-plugin.version>0.37.0</docker-maven-plugin.version>
  <it.postgresql.image>postgres:13.3-alpine</it.postgresql.image>
  <it.postgresql.port>5432</it.postgresql.port>
  <it.postgresql.db>postgres</it.postgresql.db>
  <it.postgresql.password>indescifrable</it.postgresql.password>
  <it.postgresql.removeVolumesOnStop>true</it.postgresql.removeVolumesOnStop>
  ...
</properties>
...
<plugin>
    <!-- Run database Docker container during integration tests-->
    <groupId>io.fabric8</groupId>
    <artifactId>docker-maven-plugin</artifactId>
    <version>${docker-maven-plugin.version}</version>
    <configuration>
        <images>
            <image>
                <name>${it.postgresql.image}</name>
                <run>
                    <ports>
                        <port>${it.postgresql.port}:5432</port>
                    </ports>
                    <env>
                        <POSTGRES_DB>${it.postgresql.db}</POSTGRES_DB>
                        <POSTGRES_PASSWORD>${it.postgresql.password}</POSTGRES_PASSWORD>
                    </env>
                    <wait>
                        <log>database system is ready to accept connections</log>
                    </wait>
                </run>
            </image>
        </images>
    </configuration>
    <executions>
        <execution>
            <id>start</id>
            <phase>pre-integration-test</phase>
            <goals>
                <goal>start</goal>
            </goals>
        </execution>
        <execution>
            <id>stop</id>
            <phase>post-integration-test</phase>
            <goals>
                <goal>stop</goal>
            </goals>
            <configuration>
                <removeVolumes>${it.postgresql.removeVolumesOnStop}</removeVolumes>
            </configuration>
        </execution>
    </executions>
</plugin>
...

4. Conclusiones

No siempre tenemos el tiempo para esperar a la solución oficial, pero siempre podemos buscarnos las mañas para conseguir un workaround, mas en el mundo Unix donde las opciones son prácticamente infinitas.

5. Sobre el autor

Alejandro Pérez García (@alejandropgarci).

Ingeniero en Informática (especialidad de Ingeniería del Software) y Certified ScrumMaster.

Socio fundador de Autentia Real Business Solutions S.L. – “Soporte a Desarrollo”.

Alejandro es socio fundador de Autentia y nuestro experto en Java EE, Linux y optimización de aplicaciones empresariales. Ingeniero en Informática y Certified ScrumMaster. Seguir @alejandropgarci Si te gusta lo que ves, puedes contratarle para darte ayuda con soporte experto, impartir cursos presenciales en tu empresa o para que realicemos tus proyectos como factoría (Madrid). Puedes encontrarme en Autentia: Ofrecemos servicios de soporte a desarrollo, factoría y formación.

4 COMENTARIOS

  1. Buenas,

    disculpas sabes que tengo un problema al correr el mvn. Me podrias orientar por favor.. Actualmente estoy levante con soncat paralelo pero tiene un problema l buscar cp-base-new
    FO]
    [INFO] Image will be built as nxt/confluentinc/cp-base-new:7.0.0-ubi8
    [INFO]
    [INFO] ————————————————————————
    [INFO] Reactor Summary for common-docker 7.0.0:
    [INFO]
    [INFO] common-docker ……………………………….. SUCCESS [ 2.210 s]
    [INFO] utility-belt ………………………………… SUCCESS [ 1.858 s]
    [INFO] docker-utils ………………………………… SUCCESS [ 4.170 s]
    [INFO] cp-base-new …………………………………. FAILURE [ 14.628 s]
    [INFO] cp-jmxterm ………………………………….. SKIPPED
    [INFO] ————————————————————————
    [INFO] BUILD FAILURE
    [INFO] ————————————————————————
    [INFO] Total time: 23.152 s
    [INFO] Finished at: 2022-02-24T13:30:50-03:00
    [INFO] ————————————————————————
    [ERROR] Failed to execute goal com.spotify:dockerfile-maven-plugin:1.4.13:build (package) on project cp-base-new: Could not build image: java.util.concurrent.ExecutionException: com.spotify.docker.client.shaded.javax.ws.rs.ProcessingException: java.lang.UnsatisfiedLinkError: could not load FFI provider com.spotify.docker.client.shaded.jnr.ffi.provider.jffi.Provider: ExceptionInInitializerError: Can’t overwrite cause with java.lang.UnsatisfiedLinkError: java.lang.UnsatisfiedLinkError: Can’t load library: /var/folders/zz/zyxvpxvq6csfxvn_n0000000000000/T/jffi6854678401596354910.dylib

    • Hola Francisco,

      Pues complicado de saber, porque ni siquiera es el mismo plugin, sino que parece que el problema viene de `com.spotify.docker.client.shaded….`. Así que habría que analizar con calma lo que está pasando ahí.

      Por lo pronto asegúrate de que tienes bien definida la variable de entorno `export DOCKER_HOST=tcp://127.0.0.1:2375` en la shell donde estás ejecutando eso.

      Por otro lado si lo que quieres es simplemente levantar contenedores Docker y no construirlos, igual puedes buscar otras alternativas como esta que explico en este otro tutorial https://www.adictosaltrabajo.com/2021/11/12/como-ejecutar-contenedores-de-docker-con-maven-exec-maven-plugin/

      Saludo y suerte!

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad