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 http://adictosaltrabajo.com/2021/11/12/como-ejecutar-contenedores-de-docker-con-maven-exec-maven-plugin/
Saludo y suerte!