Docker et Kotlin : conteneurisation d'applications de niveau avancé
La conteneurisation est devenue une partie intégrante du développement moderne. Si vous maîtrisez déjà les principes de base de Docker et avez écrit quelques images simples en Kotlin, il est temps de passer au niveau supérieur. Dans cet article, nous aborderons des techniques avancées : la construction multi-étapes, l'optimisation de la taille de l'image finale, le travail avec les frameworks populaires (Ktor, Spring Boot) et la gestion efficace des coroutines dans un environnement conteneurisé.
1. Construction multi-étapes (Multi-stage builds)
L'un des principaux problèmes lors de la conteneurisation d'applications JVM est la taille de l'image. Une image de base JDK peut peser des centaines de mégaoctets. La construction multi-étapes permet de séparer les étapes de compilation et d'exécution, en ne laissant dans l'image finale que le JRE et le JAR compilé.
Considérons un exemple pour une application Kotlin construite avec Gradle :
# Étape de constructionFROM gradle:7.6-jdk17 AS builderWORKDIR /appCOPY build.gradle.kts settings.gradle.kts ./COPY src ./srcRUN gradle build --no-daemon -x test
# Étape d'exécutionFROM eclipse-temurin:17-jre-alpineWORKDIR /appCOPY --from=builder /app/build/libs/*.jar app.jarEXPOSE 8080CMD ["java", "-jar", "app.jar"]Pourquoi est-ce efficace ?
- Lors de l'étape de construction, le JDK complet et Gradle sont utilisés, mais ils ne sont pas inclus dans l'image finale.
- L'image finale ne contient que le JRE (Java Runtime Environment), ce qui réduit la taille de 3 à 5 fois.
- Alpine Linux réduit encore le volume à environ 150-200 Mo au lieu de 600+ Mo.
2. Optimisation des couches et mise en cache
Docker met en cache les couches, mais seulement si elles n'ont pas changé. Pour accélérer les reconstructions, séparez les dépendances et le code source :
FROM gradle:7.6-jdk17 AS builderWORKDIR /app
# Copiez d'abord uniquement les fichiers de dépendancesCOPY build.gradle.kts settings.gradle.kts ./RUN gradle dependencies --no-daemon
# Ensuite le code source (change plus souvent)COPY src ./srcRUN gradle build --no-daemon -x testCette approche garantit que la couche de dépendances ne sera reconstruite que lorsque build.gradle.kts est modifié. Sinon, Docker utilise le cache, économisant des minutes à chaque itération.
Conseil : pour les projets Kotlin avec un grand nombre de dépendances (par exemple, Spring Boot), cette technique est particulièrement pertinente — le chargement du cache Gradle peut prendre jusqu'à 2-3 minutes.
3. Conteneurisation d'applications Ktor
Ktor est un framework asynchrone léger en Kotlin, idéal pour les microservices. La conteneurisation d'une application Ktor nécessite une configuration minimale, mais il y a des nuances liées aux coroutines.
Exemple de Dockerfile pour Ktor :
FROM gradle:7.6-jdk17 AS builderWORKDIR /appCOPY build.gradle.kts settings.gradle.kts ./RUN gradle dependencies --no-daemonCOPY src ./srcRUN gradle build --no-daemon -x test
FROM eclipse-temurin:17-jre-alpineWORKDIR /appCOPY --from=builder /app/build/libs/ktor-app-*.jar app.jarEXPOSE 8080
# Important : configuration JVM pour les coroutinesCMD ["java", "-XX:+UseZGC", "-Xms256m", "-Xmx512m", "-jar", "app.jar"]Points clés :
- Utilisez ZGC (Z Garbage Collector) — il minimise les pauses, ce qui est critique pour les applications asynchrones avec un grand nombre de coroutines.
- Limitez la mémoire via
-Xmset-Xmx. Dans les conteneurs, cela aide à éviter les erreurs OOM. - Assurez-vous que le serveur Ktor ne bloque pas le thread principal — les coroutines doivent être configurées via
Dispatchers.IOou un pool personnalisé.
Exemple de configuration Ktor pour fonctionner dans un conteneur :
fun main() { embeddedServer(Netty, port = 8080, host = "0.0.0.0") { routing { get("/") {