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
- El problema
- La solución: Redireccionar la comunicación a través de socat
- Bola extra
- Conclusiones
- 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_HOST
, socat
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”.
I spent hours trying to solve this. Works great. Thanks for your help Alejandro!
El problema del socket está arreglado a partir de la versión 0.38.1 del plugin 🥳
https://github.com/fabric8io/docker-maven-plugin/releases
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!