Earthly: Si Dockerfile et Makefile avaient un enfant

Posted by ZedTuX 0n R00t on September 27, 2020

Hier j’ai découvert Earthly, un outil pour d’automatisation de construction d’image Docker, de binaires ou simplement pour faire tourner vos tests automatisés.

À quoi sert-il?

Cet outil vous permet, en un seul fichier Earthfile, de générer différent type de choses au travers de targets.

Concrètement, si dans vos projet vous avez un fichier Dockerfile pour votre développement, puis un fichier Dockerfile.ci pour construire l’image où seront exécutés vos tests, ainsi qu’un fichier Dockerfile.production pour créer l’image finale de votre projet, vous pourrez faire la même chose en déclarant 3 targets pour les 3 différents environnements.

Vos 3 fichiers ont des points communs et des différences. Les points communs sont à mettre dans un ou des targets qui serviront dans les 3 targets finaux.

Pour mieux comprendre, si ce n’est pas clair, voici un exemple de la structure de votre Earthfile où vous trouverez un target deps pour installer les dépendences du projet (qui dans cette exemple sont toujours les mêmes, que ce soit en development, sur le CI ou en production) puis les 3 targets dev, ci, et prod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
FROM ruby
WORKDIR /application

deps:
    # Installer ici les dépendences en utilisant les commandes COPY et RUN
    # Puis à la fin sauvegarder l'image
    SAVE IMAGE

prod:
    FROM +deps
    # Exécuter les commandes uniquement pour l'image de production

ci:
    FROM +deps
    # Exécuter les commandes pour lancer les tests

dev:
    FROM +deps
    # Exécuter les commandes uniquement pour l'image de production

Ceci replace vos 3 fichiers, et pas besoin de commande où vous passez tel ou tel Dockerfile!

Comment créer les images?

Après avoir installé Earthly, vous aurez la commande earth qui permet d’utiliser votre nouveau fichier Earthfile.

Il vous suffit de passer le target voulut en paramètre en ajoutant un + devant son nom. Pour lancer le target dev, vous le ferez en lancant la commande:

1
earth +dev

Un exemple concret

J’ai donc testé ce nouvel outil sur mon projet bac à sable: Brewformuas.org.

Vous trouverez le commit où j’ai remplacé mes fichiers Dockerfile par un fichier Earthfile ici.

Et voici mon fichier Earthfile pour ce projet Ruby On Rails:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
FROM ruby:2.6-slim-buster
WORKDIR /application

# `deps` installs all the system libraries required by the gems in order
# to install them all.
# Finally it creates a temporary image used as a base image later.
deps:
    RUN apt-get update \
        && apt-get install -y libpq-dev \
                              build-essential \
                              nodejs \
                              git
    SAVE IMAGE

# `gems` installs all the gems from the Gemfile.lock for a production usage.
# Finally it creates a temporary image used as a base image later.
gems:
    FROM +deps
    ENV BUNDLE_GEMFILE=/application/Gemfile
    ENV BUNDLE_JOBS=8
    ENV BUNDLE_PATH=/bundle
    COPY Gemfile* ./
    RUN gem install rubygems-update \
        && update_rubygems \
        && gem install bundler:1.16.0 \
        && bundle install --without development test
    SAVE IMAGE

# `assets` runs the Rails assets:precompile task and create an artifact from it
# used in the `prod` target.
assets:
    FROM +gems
    ENV RAILS_ENV production
    COPY . /application
    RUN bundle exec rake assets:precompile RAILS_ENV=production
    SAVE ARTIFACT public/assets assets

# `prod` builds the production image of the project which means precompiling
# the projet assets.
# Finally it creates the Docker image used in order to run the project.
prod:
    FROM +gems
    ENV RAILS_ENV production
    RUN useradd -ms /bin/bash brewformulas
    COPY . /application
    COPY +assets/assets public/assets
    RUN chown -R brewformulas:users /application
    EXPOSE 3000
    ENTRYPOINT ["bundle", "exec"]
    CMD ["bin/rails server -b 0.0.0.0"]
    SAVE IMAGE zedtux/brewformulas.org:latest

# `ci` is based on the production image, in order to keep an environmnet as
# close as possible with production when running the tests.
# Nonetheless it install additionnal gems used to run the tests.
ci:
    FROM +prod
    ENV RAILS_ENV test
    RUN bundle config --delete without
    RUN bundle install --gemfile=/application/Gemfile \
                       --jobs=8 \
                       --path=/bundle \
                       --without development
    SAVE IMAGE zedtux/brewformulas.org:latest-ci

# `dev` is based on the production image, in order to keep an environmnet as
# close as possible with produciton and then installs all the other gems for
# developing the project.
# The final image is used by the local docker-compose.yml file.
dev:
    FROM +prod
    ENV RAILS_ENV development
    RUN bundle config --delete without
    RUN bundle install --gemfile=/application/Gemfile \
                       --jobs=8 \
                       --path=/bundle
    # Allow adding/removing/updating gems
    RUN chown -R brewformulas:users /bundle
    SAVE IMAGE zedtux/brewformulas.org:latest-dev

Explications

deps

Ce target est responsable pour l’installation des paquets systèmes nécessaires à la compilation de certaines gems du projet.

J’utilises SAVE IMAGE à la fin pour créer une image de base pour les targets suivant.

gems

Ce target commence par déclarer qu’il se base sur l’image générée depuis le target deps.

Ensuite il va déclarer les variables d’environnement de bundler, copier les fichiers Gemfile et Gemfile.lock puis installer les gems dans le dossier /bundle après avoir installé bundler.

Pour finir j’utilises encore une fois SAVE IMAGE bien que j’aurai put utiliser SAVE ARTIFACT pour ne garder que le dossier /bundle. Surement une amélioration que je ferai 😉

assets

Ce target se base sur l’image générée depuis le target gems, de manière à avoir tout l’environnement nécessaire à pré-compiler les assets (CSS, JS, polices d’écritures et images).

RAILS_ENV y est défini avec la valeur production car le projet n’a pas d’autres cas où utiliser rake assets:precompile.

Puis il lance la commande bundle exec rake assets:precompile. Je viens de me rendre compte que j’ai oublié de retirer RAILS_ENV=production dans la commande RUN …

Ce target fini avec la commande SAVE ARTIFACT public/assets assets qui indique à Earthly de sauvegarder le contenu du dossier /application/public/assets dans un espace nommé assets de façon à pouvoir le ré-importer n’importe quand.

prod

Évidement, ce target permet de générer l’image finale pour le déploiement (qui ne se fait plus depuis que j’ai arrêter son hergement).

Il se base sur l’image générée depuis le target gems puis il redéfini la variable d’environnement RAILS_ENV à production, créé l’utilisateur brewformulas qui sera utilisé pour lancer le projet, il importe les fichiers du projet.

L’artifacte assets est importé après car le dossier du projet contient déjà un dossier public, et je suppose qu’importer l’artifacte assets avant la copie des fichiers du projet pourrait effacer le dossier public/assets.

Il fini sur le paramètrage EXPOSE, ENTRYPOINT et CMD, puis se termine en créant l’image, mais en donnant son nom final qui est zedtux/brewformulas.org:latest

ci

Ce target se base sur l’image de production, il redéfini la variable d’environnement RAILS_ENV, il installe les gems pour lancer les tests et fini par créer une image zedtux/brewformulas.org:latest-ci.

dev

Ce target se base sur l’image de production aussi, il redéfini la variable d’environnement RAILS_ENV.

Pour annuler l’effet de la commande bundle install --without development test qui a été utilisé dans le target gems, j’utilises la commande bundle config --delete without.

Pour finir, il install tous les gems, et change les permissions du dossier /bundle de manière à ce que je puisse continuer à ajouter/supprimer/mettre à jour des gems en développement.

Il fini sur la création de l’image docker zedtux/brewformulas.org:latest-dev afin d’avoir une image nommée. Si j’avais laissé zedtux/brewformulas.org:latest, puisque le nom est le même que dans le tatget prod, Earthly aurait gardé zedtux/brewformulas.org:latest uniquement pour l’image de production, ce qui aurait pour effet que l’image de dev ne serait accessible que par son ID:

1
2
zedtux/brewformulas.org                     <none>          efaff4f9ed29        28 hours ago        1.15GB
zedtux/brewformulas.org                     latest              9116b6c229ef        28 hours ago        740MB

Du coup, j’ai changé le fichier docker-compose.yml pour que l’attribut image de chaque services pointent vers zedtux/brewformulas.org:latest-dev.

Conclusion

Earthly est un super outil, qui facilite encore plus la gestion de projets avec Docker, et une fois son principe compris, vous ne pourrez plus vous en passer, j’en suis sûr! ☺️

En tous les cas, personnellement, je vais pousser pour utiliser cet outil dans les projets dans lesquelles je travails, c’est certain 😇.