Skip to content

Blog


Adapter Gradle et Docker pour un développement local rapide

5 janvier 2021

|

Marvin Flores

Alors que les entreprises technologiques s'empressent de sortir leurs prochaines fonctionnalités, tout retard de productivité peut être extrêmement préjudiciable, d'où l'importance d'un processus de développement efficace. 

Les entreprises qui utilisent Kubernetes et Docker dans des environnements de production utilisent très probablement Docker pour le développement local. Docker-compose, un outil permettant de définir et d'exécuter des applications Docker multiconteneurs, garantit des processus de développement local cohérents, ce qui facilite la gestion des dépendances des applications. Docker-compose permet aux ingénieurs de choisir les versions spécifiques des dépendances à exécuter avec le conteneur d'application, au lieu de demander à chaque ingénieur d'installer manuellement les dépendances sur sa machine. Il est essentiel d'avoir des versions cohérentes des dépendances pour un cycle de développement rapide, car il y a peu de chances d'avoir des problèmes de compatibilité dus à des versions incorrectes des dépendances. 

DoorDash a été confronté à un problème similaire lorsque notre équipe a dû faire face à un temps de construction lent dans notre environnement local, ce qui a considérablement ralenti le processus de développement. Chaque fois qu'un ingénieur ajoutait un nouveau code à la base de code, le processus d'intégration prenait quatre à cinq minutes. Peu importe que nous modifiions 20 fichiers ou que nous ajoutions simplement une ligne de code pour créer un journal rapide, il fallait toujours environ cinq minutes pour terminer la compilation. Tous les ingénieurs savent qu'il est difficile de rester concentré et productif si une partie du cycle de développement interrompt leur processus de réflexion. 

Le problème de notre processus de construction

Nous utilisons Gradle pour compiler notre application, puis Docker pour l'exécuter avec ses dépendances. Cependant, en raison de l'absence d'un processus de compilation uniforme pour le développement local, nous n'avons pas été en mesure de réaliser un développement efficace.

Un des problèmes était que le Dockerfile que notre équipe utilisait était la réplique exacte du Dockerfile de production. Notre fichier Docker de production est destiné à compiler l'application à partir de zéro, car nous n'utilisons pas le cache de construction Gradle en production. En utilisant localement le fichier Docker de production, nous ne tirons pas profit du cache Gradle et nous téléchargeons également des dépendances qui ne sont pas nécessaires dans notre environnement local. La figure 1, ci-dessous, est une représentation du fichier Docker qui causait des retards :

Notre fichier docker de développement local avant optimisation
Figure 1 : Ce fichier Docker contient de nombreux composants supplémentaires destinés à la production qui ne sont pas nécessaires au développement local. Le fait de ne pas s'appuyer sur le cache de compilation de Gradle signifiait que chaque compilation nécessitait de télécharger les dépendances d'une application à partir de zéro, ce qui n'était pas idéal pour le développement local.

Un autre problème était que l'équipe utilisait la cible Makefile suivante pour exécuter notre processus de construction, dans lequel nous compilons en fait notre application en utilisant une instance locale de Gradle avant d'exécuter la construction Docker :

.PHONY: build
build:
 git submodule update --init --recursive --remote
 ./gradlew clean installDist
 docker-compose build

L'idée était d'utiliser le cache Gradle avant de construire l'image entière pour accélérer le temps de construction. Cependant, cette partie du processus est devenue un gaspillage d'efforts car nous exécutions la même commande à l'intérieur du Dockerfile, comme le montre la Figure 1 ci-dessus. En fait, notre fichier Docker contenait cette commande :

RUN ./gradlew clean installDist test ktlintCheck --no-daemon && \
    mv build/install/* /home/ && \
    rm -r build

Non seulement cette commande était similaire à celle que nous venons d'exécuter en dehors du fichier Docker, mais elle incluait également un test et une vérification du format en même temps que la construction. Le pire, c'est qu'elle n'a pas utilisé le cache de la machine locale et qu'elle a donc dû retélécharger toutes les dépendances de notre application.

Solution : Mettre à jour nos scripts de construction et notre fichier Docker. 

Nous avons résolu le problème du temps de construction en examinant notre script de construction et notre fichier Docker et en réorganisant la façon dont nous construisons notre application et ses dépendances.

Comment nous avons mis à jour notre fichier Docker pour le développement local

  • Nous avons supprimé toutes les applications tierces inutiles que nous n'utilisons pas localement. Par exemple, nous avons supprimé l'agent New Relic dont nous avons absolument besoin en production mais qui n'est pas nécessaire dans un environnement local.
  • Nous avons supprimé l'installation de Gradle. Pour utiliser correctement le cache Gradle, nous avons utilisé l'installation de Gradle sur notre machine locale et construit l'application en dehors de notre build docker.
  • Puisque nous ne compilons plus l'application à l'intérieur du fichier Docker, nous devons copier les fichiers du contexte de construction dans le répertoire approprié à l'intérieur de notre image.

Séparer les exécutions de la compilation et des tests unitaires

Dans notre Makefile, nous avons séparé nos exécutions de build et de test pour nous donner plus de flexibilité. L'équipe écrit et exécute constamment des tests unitaires via l'IDE pendant le développement, il n'est donc pas nécessaire de les réexécuter pour chaque build local. De plus, il n'est pas pratique d'exécuter tous les tests unitaires pour chaque version locale, surtout si les changements dans le code sont minimes. Bien sûr, chaque membre de l'équipe exécute toujours le build complet - qui exécute tous les tests, vérifie le format, et construit avant de livrer les changements à notre dépôt.

S'assurer que nous utilisons correctement le cache Gradle

Nous avons activé le cache Gradle en mettant org.gradle.caching=true dans notre gradle.properties. Comme nous n'avons pas de tâches complexes dans nos build.gradlePour les tâches complexes, c'est tout ce qu'il faut faire pour utiliser efficacement le cache de Gradle. Pour les tâches complexes dans build.gradleL'optimisation de ces tâches peut permettre de tirer le meilleur parti du cache Gradle.

Résultats 

Comme le montre la figure 2 ci-dessous, notre fichier Docker est plus léger que la version précédente (figure 1). En apportant ces modifications, nous avons réduit le temps de construction d'environ cinq minutes à une moyenne de 45 secondes, soit une diminution d'environ 85 %.

notre fichier docker optimisé
Figure 2. La représentation améliorée du fichier Docker est beaucoup plus optimisée et plus rapide pour l'équipe de développement. La suppression des dépendances inutiles et le déplacement du processus de compilation de l'application ont considérablement réduit le temps de construction.

Si l'on considère que nous exécutons les commandes de construction plusieurs fois par jour, cette amélioration est une grande victoire pour l'équipe, car elle nous fait gagner beaucoup de temps et permet à tout le monde de se concentrer sur les tâches à accomplir. 

Ce type de problème peut arriver à toute équipe qui débute dans la conteneurisation ou les microservices. Si vous gérez un Dockerfile, c'est toujours une bonne idée de le revisiter de temps en temps pour chercher des améliorations potentielles. Les meilleures pratiques de Dockerfile et le cache de construction de Gradle sont d'excellentes documentations pour mieux comprendre les problèmes de construction spécifiques.

A propos de l'auteur

Emplois connexes

Localisation
San Francisco, CA ; Mountain View, CA ; New York, NY ; Seattle, WA
Département
Ingénierie
Localisation
San Francisco, CA ; Sunnyvale, CA
Département
Ingénierie
Localisation
San Francisco, CA ; Sunnyvale, CA ; Seattle, WA
Département
Ingénierie
Localisation
Pune, Inde
Département
Ingénierie
Localisation
San Francisco, CA ; Seattle, WA ; Sunnyvale, CA
Département
Ingénierie