Ejemplo de trabajo.
Este no es un tutorial de arranque de primavera. Es la respuesta actualizada a una pregunta sobre cómo ejecutar una compilación de Maven dentro de un contenedor Docker.
Pregunta publicada originalmente hace 4 años.
1. Genere una aplicación
Utilice el inicializador de primavera para generar una aplicación de demostración
https://start.spring.io/
Extraiga el archivo zip localmente
2. Crea un Dockerfile
#
# Build stage
#
FROM maven:3.6.0-jdk-11-slim AS build
COPY src /home/app/src
COPY pom.xml /home/app
RUN mvn -f /home/app/pom.xml clean package
#
# Package stage
#
FROM openjdk:11-jre-slim
COPY --from=build /home/app/target/demo-0.0.1-SNAPSHOT.jar /usr/local/lib/demo.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/usr/local/lib/demo.jar"]
Nota
- Este ejemplo utiliza una compilación de varias etapas . La primera etapa se usa para construir el código. La segunda etapa solo contiene el jar construido y un JRE para ejecutarlo (observe cómo se copia jar entre etapas).
3. Construye la imagen
docker build -t demo .
4. Ejecute la imagen
$ docker run --rm -it demo:latest
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.3.RELEASE)
2019-02-22 17:18:57.835 INFO 1 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication v0.0.1-SNAPSHOT on f4e67677c9a9 with PID 1 (/usr/local/bin/demo.jar started by root in /)
2019-02-22 17:18:57.837 INFO 1 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2019-02-22 17:18:58.294 INFO 1 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 0.711 seconds (JVM running for 1.035)
Misc
Lea la documentación del centro de Docker sobre cómo se puede optimizar la compilación de Maven para usar un repositorio local para almacenar archivos en caché.
Actualización (2019-02-07)
Esta pregunta tiene ahora 4 años y en ese momento es justo decir que la creación de aplicaciones con Docker ha experimentado un cambio significativo.
Opción 1: construcción de varias etapas
Este nuevo estilo le permite crear imágenes más livianas que no encapsulan sus herramientas de compilación y código fuente.
El ejemplo aquí nuevamente usa la imagen base oficial de Maven para ejecutar la primera etapa de la compilación usando una versión deseada de Maven. La segunda parte del archivo define cómo se ensambla el jar construido en la imagen de salida final.
FROM maven:3.5-jdk-8 AS build
COPY src /usr/src/app/src
COPY pom.xml /usr/src/app
RUN mvn -f /usr/src/app/pom.xml clean package
FROM gcr.io/distroless/java
COPY --from=build /usr/src/app/target/helloworld-1.0.0-SNAPSHOT.jar /usr/app/helloworld-1.0.0-SNAPSHOT.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/usr/app/helloworld-1.0.0-SNAPSHOT.jar"]
Nota:
- Estoy usando la imagen base sin distribución de Google , que se esfuerza por proporcionar el tiempo de ejecución suficiente para una aplicación Java.
Opción 2: foque
No he usado este enfoque, pero parece digno de investigación, ya que te permite crear imágenes sin tener que crear cosas desagradables como Dockerfiles :-)
https://github.com/GoogleContainerTools/jib
El proyecto tiene un complemento de Maven que integra el empaquetado de su código directamente en su flujo de trabajo de Maven.
Respuesta original (incluida para completar, pero escrita hace mucho tiempo)
Intenta usar las nuevas imágenes oficiales, hay una para Maven
https://registry.hub.docker.com/_/maven/
La imagen se puede usar para ejecutar Maven en el momento de la compilación para crear una aplicación compilada o, como en los siguientes ejemplos, para ejecutar una compilación de Maven dentro de un contenedor.
Ejemplo 1: Maven ejecutándose dentro de un contenedor
El siguiente comando ejecuta su compilación de Maven dentro de un contenedor:
docker run -it --rm \
-v "$(pwd)":/opt/maven \
-w /opt/maven \
maven:3.2-jdk-7 \
mvn clean install
Notas:
- Lo bueno de este enfoque es que todo el software se instala y se ejecuta dentro del contenedor. Solo necesita Docker en la máquina host.
- Ver Dockerfile para esta versión
Ejemplo 2: usar Nexus para almacenar archivos en caché
Ejecuta el contenedor Nexus
docker run -d -p 8081:8081 --name nexus sonatype/nexus
Cree un archivo "settings.xml":
<settings>
<mirrors>
<mirror>
<id>nexus</id>
<mirrorOf>*</mirrorOf>
<url>http://nexus:8081/content/groups/public/</url>
</mirror>
</mirrors>
</settings>
Ahora ejecute Maven vinculando al contenedor nexus, para que las dependencias se almacenen en caché
docker run -it --rm \
-v "$(pwd)":/opt/maven \
-w /opt/maven \
--link nexus:nexus \
maven:3.2-jdk-7 \
mvn -s settings.xml clean install
Notas:
- Una ventaja de ejecutar Nexus en segundo plano es que otros repositorios de terceros se pueden administrar a través de la URL del administrador de forma transparente para las compilaciones de Maven que se ejecutan en contenedores locales.
mavenCentral()
Reemplacé en mis dependencias gradle conmaven {url "http://nexus:8081..."
y ahora solo tengo problemas de resolución.Puede haber muchas formas ... Pero implementé siguiendo dos formas
El ejemplo dado es del proyecto maven.
1. Usando Dockerfile en el proyecto maven
Utilice la siguiente estructura de archivos:
Demo └── src | ├── main | │ ├── java | │ └── org | │ └── demo | │ └── Application.java | │ | └── test | ├──── Dockerfile ├──── pom.xml
Y actualice el Dockerfile como:
FROM java:8 EXPOSE 8080 ADD /target/demo.jar demo.jar ENTRYPOINT ["java","-jar","demo.jar"]
Navegue a la carpeta del proyecto y escriba el siguiente comando, podrá crear una imagen y ejecutar esa imagen:
$ mvn clean $ mvn install $ docker build -f Dockerfile -t springdemo . $ docker run -p 8080:8080 -t springdemo
Obtenga un video en Spring Boot con Docker
2. Uso de complementos de Maven
Agregue el complemento de maven dado en
pom.xml
<plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.4.5</version> <configuration> <imageName>springdocker</imageName> <baseImage>java</baseImage> <entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> </resource> </resources> </configuration> </plugin>
Navegue a la carpeta del proyecto y escriba el siguiente comando, podrá crear una imagen y ejecutar esa imagen:
$ mvn clean package docker:build $ docker images $ docker run -p 8080:8080 -t <image name>
En el primer ejemplo, estamos creando Dockerfile y proporcionando una imagen base y agregando jar an so, después de hacer eso, ejecutaremos el comando docker para construir una imagen con un nombre específico y luego ejecutaremos esa imagen.
Mientras que en el segundo ejemplo estamos usando el complemento maven en el que proporcionamos
baseImage
y,imageName
por lo tanto, no necesitamos crear Dockerfile aquí ... después de empaquetar el proyecto maven obtendremos la imagen de la ventana acoplable y solo necesitamos ejecutar esa imagen ...fuente
Como regla general, debe crear un JAR grueso usando Maven (un JAR que contiene tanto su código como todas las dependencias).
Luego, puede escribir un Dockerfile que coincida con sus requisitos (si puede construir un JAR pesado, solo necesitaría un sistema operativo base, como CentOS, y la JVM).
Esto es lo que uso para una aplicación Scala (que está basada en Java).
FROM centos:centos7 # Prerequisites. RUN yum -y update RUN yum -y install wget tar # Oracle Java 7 WORKDIR /opt RUN wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/7u71-b14/server-jre-7u71-linux-x64.tar.gz RUN tar xzf server-jre-7u71-linux-x64.tar.gz RUN rm -rf server-jre-7u71-linux-x64.tar.gz RUN alternatives --install /usr/bin/java java /opt/jdk1.7.0_71/bin/java 1 # App USER daemon # This copies to local fat jar inside the image ADD /local/path/to/packaged/app/appname.jar /app/appname.jar # What to run when the container starts ENTRYPOINT [ "java", "-jar", "/app/appname.jar" ] # Ports used by the app EXPOSE 5000
Esto crea una imagen basada en CentOS con Java7. Cuando se inicie, ejecutará su aplicación jar.
La mejor manera de implementarlo es a través del Docker Registry, es como un Github para imágenes de Docker.
Puedes construir una imagen como esta:
# current dir must contain the Dockerfile docker build -t username/projectname:tagname .
Luego puede empujar una imagen de esta manera:
docker push username/projectname # this pushes all tags
Una vez que la imagen está en el Registro de Docker, puede extraerla de cualquier parte del mundo y ejecutarla.
Consulte la Guía del usuario de Docker para obtener más información.
Algo a tener en cuenta :
También puede extraer su repositorio dentro de una imagen y construir el jar como parte de la ejecución del contenedor, pero no es un buen enfoque, ya que el código podría cambiar y podría terminar usando una versión diferente de la aplicación sin previo aviso.
La construcción de un frasco grueso elimina este problema.
fuente
RUN wget -O {project.build.finalname}.jar
pero quiero descargar el jar anterior de nexus.Aquí está mi contribución.
No intentaré enumerar todas las herramientas / bibliotecas / complementos que existen para aprovechar Docker con Maven. Algunas respuestas ya lo han hecho.
en lugar de, me centraré en la tipología de aplicaciones y la forma de Dockerfile.
Dockerfile
es realmente un concepto simple e importante de Docker (todas las imágenes conocidas / públicas se basan en eso) y creo que tratar de evitar comprender y usarDockerfile
s no es necesariamente la mejor manera de ingresar al mundo de Docker.Dockerizar una aplicación depende de la aplicación en sí y del objetivo a alcanzar
1) Para las aplicaciones que queremos continuar, ejecutarlas en un servidor Java instalado / independiente (Tomcat, JBoss, etc.)
El camino es más difícil y ese no es el objetivo ideal porque agrega complejidad (tenemos que administrar / mantener el servidor) y es menos escalable y menos rápido que los servidores integrados en términos de compilación / implementación / anulación de implementación.
Pero para las aplicaciones heredadas, eso puede considerarse como un primer paso.
Generalmente, la idea aquí es definir una imagen de Docker para el servidor y definir una imagen por aplicación a implementar. Para aplicaciones enormes (millones de líneas de códigos) con una gran cantidad de elementos heredados, y tan difíciles de migrar a una solución integrada de arranque completo, esa es realmente una buena mejora.
Las imágenes de la ventana acoplable para las aplicaciones producen el WAR / EAR esperado, pero no se ejecutan como contenedor y la imagen de la aplicación del servidor implementa los componentes producidos por estas imágenes como aplicaciones implementadas.
No detallaré más ese enfoque, ya que es para casos de uso menores de Docker, pero quería exponer la idea general de ese enfoque porque creo que para los desarrolladores que enfrentan estos casos complejos, es bueno saber que algunas puertas están abiertas a integrar Docker.
2) Para aplicaciones que incrustan / arrancan el servidor en sí (Spring Boot con servidor incrustado: Tomcat, Netty, Jetty ...)
Ese es el objetivo ideal con Docker . Especifiqué Spring Boot porque es un marco realmente agradable para hacer eso y también tiene un nivel muy alto de capacidad de mantenimiento, pero en teoría podríamos usar cualquier otra forma de Java para lograrlo.
Generalmente, la idea aquí es definir una imagen de Docker por aplicación para implementar.
Las imágenes de la ventana acoplable para las aplicaciones producen un JAR o un conjunto de JAR / clases / archivos de configuración y estos inician una JVM con la aplicación (comando java) cuando creamos e iniciamos un contenedor a partir de estas imágenes.
En el caso de nuevas aplicaciones o aplicaciones que no sean demasiado complejas para migrar, esa forma debe ser preferida a los servidores independientes porque es la forma estándar y la forma más eficiente de utilizar contenedores.
Detallaré ese enfoque.
Dockerizar una aplicación maven
1) sin bota de resorte
La idea es crear un frasco gordo con Maven (el complemento de ensamblaje de maven y el complemento de sombra de maven ayudan para eso) que contenga las clases compiladas de la aplicación y las dependencias de maven necesarias.
Entonces podemos identificar dos casos:
si la aplicación es una aplicación de escritorio o autónoma (que no necesita ser implementada en un servidor): podríamos especificar como
CMD/ENTRYPOINT
en laDockerfile
ejecución java de la aplicación:java -cp .:/fooPath/* -jar myJar
si la aplicación es una aplicación de servidor, por ejemplo Tomcat, la idea es la misma: obtener un frasco grande de la aplicación y ejecutar una JVM en el
CMD/ENTRYPOINT
. Pero aquí con una diferencia importante: necesitamos incluir algunas bibliotecas lógicas y específicas (org.apache.tomcat.embed
bibliotecas y algunas otras) que inician el servidor integrado cuando se inicia la aplicación principal.Tenemos una guía completa en el sitio web de heroku .
Para el primer caso (aplicación autónoma), esa es una forma directa y eficiente de usar Docker.
Para el segundo caso (aplicación de servidor), que funciona pero que no es directo, puede ser propenso a errores y no es un modelo muy extensible porque no coloca su aplicación en el marco de un marco maduro como Spring Boot que hace muchos de estas cosas para usted y también proporciona un alto nivel de extensión.
Pero eso tiene una ventaja: tiene un alto nivel de libertad porque usa directamente la API de Tomcat integrada.
2) Con bota de primavera
Por fin, aquí vamos.
Eso es simple, eficiente y muy bien documentado.
Realmente hay varios enfoques para hacer que una aplicación Maven / Spring Boot se ejecute en Docker.
Exponerlos a todos sería largo y quizás aburrido.
La mejor elección depende de sus necesidades.
Pero sea cual sea la forma, la estrategia de compilación en términos de capas de ventana acoplable parece la misma.
Queremos utilizar una compilación de varias etapas: una que se base en Maven para la resolución de dependencias y para la compilación y otra que se base en JDK o JRE para iniciar la aplicación.
Etapa de construcción (imagen de Maven):
Acerca de eso,
mvn dependency:resolve-plugins
encadenado amvn dependency:resolve
puede hacer el trabajo, pero no siempre.Por qué ? Debido a que estos complementos y la
package
ejecución para empaquetar el jar gordo pueden depender de diferentes artefactos / complementos e incluso para un mismo artefacto / complemento, estos aún pueden extraer una versión diferente. Por lo tanto, un enfoque más seguro, aunque potencialmente más lento, es resolver las dependencias ejecutando exactamente elmvn
comando utilizado para empaquetar la aplicación (que extraerá exactamente las dependencias que necesita), pero omitiendo la compilación de origen y eliminando la carpeta de destino para acelerar el procesamiento y evitar cualquier detección de cambio de capa no deseado para ese paso.Ejecutar etapa (imagen JDK o JRE):
Aquí dos ejemplos.
a) Una forma sencilla sin caché para las dependencias de maven descargadas
Dockerfile:
########Maven build stage######## FROM maven:3.6-jdk-11 as maven_build WORKDIR /app #copy pom COPY pom.xml . #resolve maven dependencies RUN mvn clean package -Dmaven.test.skip -Dmaven.main.skip -Dspring-boot.repackage.skip && rm -r target/ #copy source COPY src ./src # build the app (no dependency download here) RUN mvn clean package -Dmaven.test.skip # split the built app into multiple layers to improve layer rebuild RUN mkdir -p target/docker-packaging && cd target/docker-packaging && jar -xf ../my-app*.jar ########JRE run stage######## FROM openjdk:11.0-jre WORKDIR /app #copy built app layer by layer ARG DOCKER_PACKAGING_DIR=/app/target/docker-packaging COPY --from=maven_build ${DOCKER_PACKAGING_DIR}/BOOT-INF/lib /app/lib COPY --from=maven_build ${DOCKER_PACKAGING_DIR}/BOOT-INF/classes /app/classes COPY --from=maven_build ${DOCKER_PACKAGING_DIR}/META-INF /app/META-INF #run the app CMD java -cp .:classes:lib/* \ -Djava.security.egd=file:/dev/./urandom \ foo.bar.MySpringBootApplication
¿Inconveniente de esa solución? Cualquier cambio en pom.xml significa que vuelve a crear toda la capa que descarga y almacena las dependencias de maven. Eso generalmente no es aceptable para aplicaciones con muchas dependencias (y Spring Boot extrae muchas dependencias), en general si no usa un administrador de repositorio maven durante la construcción de la imagen.
b) Una forma más eficiente con caché para las dependencias de maven descargadas
El enfoque es aquí el mismo, pero las descargas de dependencias maven que se almacenan en caché en la caché del generador de Docker.
La operación de caché se basa en buildkit (api experimental de Docker).
Para habilitar buildkit, la variable env DOCKER_BUILDKIT = 1 debe configurarse (puede hacerlo donde quiera: .bashrc, línea de comando, archivo json del demonio docker ...).
Dockerfile:
# syntax=docker/dockerfile:experimental ########Maven build stage######## FROM maven:3.6-jdk-11 as maven_build WORKDIR /app #copy pom COPY pom.xml . #copy source COPY src ./src # build the app (no dependency download here) RUN --mount=type=cache,target=/root/.m2 mvn clean package -Dmaven.test.skip # split the built app into multiple layers to improve layer rebuild RUN mkdir -p target/docker-packaging && cd target/docker-packaging && jar -xf ../my-app*.jar ########JRE run stage######## FROM openjdk:11.0-jre WORKDIR /app #copy built app layer by layer ARG DOCKER_PACKAGING_DIR=/app/target/docker-packaging COPY --from=maven_build ${DOCKER_PACKAGING_DIR}/BOOT-INF/lib /app/lib COPY --from=maven_build ${DOCKER_PACKAGING_DIR}/BOOT-INF/classes /app/classes COPY --from=maven_build ${DOCKER_PACKAGING_DIR}/META-INF /app/META-INF #run the app CMD java -cp .:classes:lib/* \ -Djava.security.egd=file:/dev/./urandom \ foo.bar.MySpringBootApplication
fuente