Contenu

Dockeriser Spring Boot sans Dockerfile

Introduction

Vous êtes un développeur backend orienté Spring Boot et vous débutez un nouveau projet. Il y a de fortes chances que l’on vous demande de configurer à un moment ou un autre de déployer votre projet via Docker. Là, on a tous le même réflexe : créer un Dockerfile. Après une petite recherche sur les internets, vous devriez obtenir quelque chose comme ceci :

1
2
3
4
FROM openjdk:11-jre-slim
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

Oui, ça vient d’un exemple officiel.

L’étape suivante serait d’effectuer :

1
docker build . -t demo:0.0.1-SNAPSHOT

et

1
docker run -it -p8080:8080 demo:0.0.1-SNAPSHOT

Mais si je vous disais que l’on peut se passer de cette phase de configuration à partir de Spring Boot 2.3 ? Car honnêtement, quelle est la plus-value de refaire toujours cette configuration qui restera dans 90% des cas la même ? Je vais vous montrer par la suite une manière de s’en passer !

Un peu de magie

Prérequis

Commençons par se créer un projet Spring Boot. Nous pouvons soit passer par notre IDE favori, soit par start.spring.io. Par simplicité, on va utiliser le second, avec curl :

1
2
curl https://start.spring.io/starter.zip -d bootVersion=2.3.4 -d dependencies=web -o demo.zip
unzip demo.zip

On demande à générer un projet spring boot en version 2.3.4, nommé “demo”, avec le starter web.

On passe à l’action

Depuis Spring Boot 2.3, il est très simple de créer une image docker à partir de votre projet, sans toucher à un Dockerfile :

1
mvn spring-boot:build-image

Et là, quelques minutes plus tard, la magie opère et vous pouvez lancer votre conteneur avec la commande :

1
docker run -it -p8080:8080 demo:0.0.1-SNAPSHOT

Mais c’est quoi le truc ?

Buildpacks

Sous le capot, Spring Boot utilise buildpacks, une techno lancée par Heroku, qui va grosso modo analyser votre application et aller chercher toutes les dépendances manquantes pour la faire tourner. Sur du Spring Boot, buildpacks va aller automatiquement chercher la JRE nécessaire pour faire tourner le .jar produit. L’avantage de buildpacks est ici de ne plus avoir à se soucier des dépendances nécessaires à l’exécution du code pour se concentrer sur l’écriture de ce dernier.

Une évolution de buildpacks, sobrement intitulée buildpacks cloud natives permet de générer des conteneurs. Ces images sont au format Open Container Initiative. OCI est un standard open-source qui permet d’avoir des images le plus agnostique possible des insfrastructures, des cloud providers ou encore des outils devops. Si vous voulez en savoir un peu plus sur les liens entre OCI et Docker, je vous conseille la lecture de cet article.

Et si je veux personnaliser la chose ?

Par défaut, le nom de l’image vaut docker.io/library/${project.artifactId}:${project.version} mais il peut être personnalisé en changeant la propriété suivante dans la configuration de spring-boot-maven-plugin du pom.xml :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<project>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<version>2.3.4.RELEASE</version>
				<configuration>
					<image>
						<name>nom-image:un-tag</name>
					</image>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

Si un tag n’est pas précisé, il aura pour valeur latest.

Si vous voulez également personnaliser le processus de build de l’image, certaines variables peuvent être précisées de la façon suivante :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
...
<configuration>
	<image>
		<env>
			<HTTP_PROXY>http://proxy.example.com</HTTP_PROXY>
			<HTTPS_PROXY>https://proxy.example.com</HTTPS_PROXY>
		</env>
	</image>
</configuration>
...

Voici la liste des variables disponibles :

  • HTTP_PROXY : Préciser un proxy HTTP au builder (pour le téléchargement des runtimes)
  • HTTPS_PROXY : Préciser un proxy HTTPS au builder (pour le téléchargement des runtimes)
  • BP_JVM_VERSION : Préciser la version de Java à utiliser (si on ne souhaite pas utiliser celle du pom.xml)
  • JAVA_TOOL_OPTIONS : Les options de lancement de la JVM

Concernant la partie conteneur, on peut aussi directement injecter les variables d’environnement au docker run, comme ici le profil spring qui nous intéresse :

1
docker run -e "SPRING_PROFILES_ACTIVE=prod" -it -p8080:8080 demo:0.0.1-SNAPSHOT

🆕 Mise à jour avec Spring Boot 2.4

Spring Boot 2.4 apporte une nouveauté intéressante : il est possible de configurer une registry directement dans le pom.xml pour y envoyer l’image une fois construite. Pour cela, il suffit de renseigner les paramètres suivants dans la configuration du plugin maven :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<project>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<image>
						<name>docker.example.com/library/${project.artifactId}</name>
						<!-- Nécessaire pour la publication -->
						<publish>true</publish>
					</image>
					<docker>
						<publishRegistry>
							<!-- Les informations nécessaires pour configurer l'appel à la registry-->
							<username>user</username>
							<password>secret</password>
							<url>https://docker.example.com/v1/</url>
							<email>user@example.com</email>
						</publishRegistry>
					</docker>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

On peut également passer ces paramètres dans la commande de build de l’application :

1
mvn spring-boot:build-image -Dspring-boot.build-image.imageName=docker.example.com/library/my-app:v1 -Dspring-boot.build-image.publish=true

Intégration avec Spring Dev Tools

Configuration préliminaire

Si vous utilisez Spring Dev Tools dans votre projet, vous avez l’habitude de disposer de Hot Reload après avoir rebuildé votre application (Build > Build Projet dans le menu d’IntelliJ) )

Avec Spring Boot 2.3, il est possible de pousser la manipulation un peu plus loin et de disposer de Hot Reload, directement sur votre image docker.

La première étape consiste à… embarquer Spring Dev Tools dans votre image docker ! On va pour cela modifier notre pom.xml pour ne plus l’exclure (ce qui était le comportement par défaut) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludeDevtools>false</excludeDevtools>
                </configuration>
            </plugin>
        </plugins>
    </build>

En second lieu, il nous faut déclarer un secret dans notre configuration applicative : un petit tour par l’application.properties et c’est fait :

1
spring.devtools.remote.secret=Ceci est un secret, pas si secret ;)

Cette propriété est là pour sécuriser une connexion à distance. On doit la préciser en local, mais sa valeur n’a pas d’importance dans cet exemple.

Pour que ces changements soient pris en compte, il nous faut récréer notre image docker, puis en instancier un conteneur :

1
mvn spring-boot:build-image

et

1
docker run -it -p8080:8080 demo:0.0.1-SNAPSHOT

Lancement

Il s’agit maintenant de faire communiquer notre IDE avec le conteneur docker qui tourne, afin de le mettre à jour à chaque fois que l’on build notre code :

/docker-and-spring-boot-2-3/SpringBootDockerConfigurationIde.png

Il s’agit donc de définir l’exécution d’une “Application” avec comme Main class :

1
org.springframework.boot.devtools.RemoteSpringApplication

et comme Program argument : http://localhost:8080

8080 étant le port publié du conteneur ;)

On lance cette application, on fait nos requêtes sur http://localhost:8080 et bim, à chaque fois que l’on rebuild l’application, le conteneur est mis à jour avec nos changements !

Conclusion

Dans cet article on aura vu comment facilement “dockeriser” notre application Spring Boot et comment paramétrer l’image docker générée. Puis on aura vu comment intégrer ce mécanisme avec Spring Boot Developper Tools. Vous pourrez retrouver les sources liées à cet article sur notre gitlab.