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

4
6208

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 Pérez García
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 http://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