Earthly: En finir avec l'inconsistance entre les 'builds'

Posted by ZedTuX 0n R00t on October 3, 2020

Dans un post précédent je vous expliquait le fonctionnement de Earthly et comment il permettait de construire exactement de la même manière votre projet que ce soit sur votre machine ou ailleurs, comme sur votre serveur d’intégration continue.

C’est très bien, mais quand il s’agit de faire tourner vos tests, là vient la véritable difficultée car vous savez aussi bien que moi que lorsque vous lancez vos tests sur votre serveur d’intégration continue, vous n’êtes pas certain à 100% d’avoir le même comportement, et vous vous retrouvez même, parfois, à faire du code rien que pour votre CI … ce qui est évidement incorrect.

En me plongeant encore plus dans la documentation de Earthly, je me suis rendu compte que j’étais passé à coté du véritable atout de Earthly: Exécuter des containers Docker dans Earthly.

Dit comme ca, ca ne semble pas fifou mais en réalité ceci veux dire faire tourner vos tests d’intégrations dans Earthly! Qui dit “dans Earthly”, dit qu’il aura le même résultat, peu importe où Earthly tourne. Résultat, si vos tests ne passent pas sur votre CI, ils ne passeront pas en local non plus, et même mieux: Earthly vous permet de débogger en cas d’erreur!

Utiliser docker-compose dans Earthly

Votre application n’utilisera pas de services extérieur que rarement, ce qui veux dire que vous aurez donc quasiement toujours un service extérieur auquel votre application aura à se connecter, comme une base de donnée par exemple.

Vous pouvez utiliser Docker dans Earthly avec lancer manuellement vos différents containers etc … ou alors utiliser docker-compose dans Earthly afin de démarrer tous vos services, puis lancer vos tests, et c’est ce que vous allons voir.

Petit correctif, grand gain de temps

Au fil du temps, pendant que je construisais mon fichier Earthfile dans mon projet Brewformulas.org, je perdais du temps dès lors que je changeai mon fichier docker-compose.yml car j’utilisais COPY . /application dans le target assets et prod par fainéantise.

J’ai donc changé l’instruction COPY . /application par de multiples COPY pour copier les fichiers & dossiers nécessaires seulement, et donc en excluant, entres autres, mon fichier docker-compose.yml, ce qui permet au cache de ne plus être invalidé lorsque je change mon fichier docker-compose.yml.

Modification du target ci

J’ai aussi ajouté les différent COPY nécessaire pour importer les fichiers des tests (dossier features pour Cucumber et spec pour Rspec).

Puis j’ai changé la commande par défaut par rake.

Pour finir, au lieu de sauvegarder une image avec un nom, j’ai uniquement fait un SAVE IMAGE, vous verrez par la suite pourquoi 😉.

Ajout du target integration-test

Ce nouveau target va donc être responsable pour lancer les tests.

Voici une copie, puis je vous explique:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
integration-test:
    FROM docker:19.03.13-dind
    RUN apk --update --no-cache add docker-compose jq
    COPY docker-compose.yml ./
    WITH DOCKER
        DOCKER PULL postgres:9.6.2
        DOCKER PULL redis:3.2.8
        DOCKER PULL memcached:1.5.3-alpine
        DOCKER LOAD +ci zedtux/brewformulas.org:dev
        RUN docker-compose up -d \
            && POSTGRESQL_CONTAINER_IPADDR=$(docker inspect default_postgres_1 \
                                             | jq -r '.[0].NetworkSettings.Networks.default_default.IPAddress') \
            && for i in {1..30}; do nc -z $POSTGRESQL_CONTAINER_IPADDR 5432 && break; sleep 1; done; \
            docker run --network=host \
                       --env DATABASE_HOST=$POSTGRESQL_CONTAINER_IPADDR \
                       --env POSTGRESQL_USER=postgres \
                       --env RAILS_ENV=test \
                       zedtux/brewformulas.org:dev bash -c 'bundle exec rake db:setup \
                                                            && bundle exec rake' \
            && docker-compose down
    END

Comme il y est stipulé dans les limitations de Docker dans Earthy dans la documentation, une image Docker officiel est nécessaire pour que l’instruction pour utiliser Docker dans Earthly, ce target commence donc par un FROM docker:19.03.13-dind.

Ensuite, dans cette image, docker-compose y est installé, puis notre fichier docker-compose.yml y est copié.

Vient ensuite l’instruction WITH DOCKER ... END qui va démarrer un démon Docker, exécuter les commandes à l’intérieur de ce bloque, puis va tuer le démon Docker.

À l’intérieur, il y a 3 instructions DOCKER PULL pour télécharger les images nécessaires, les fameux services extérieur de notre application. Encore une fois, comme il est dit dans les limitations de Docker dans Earthy dans la documentation, il est nécessaire de les déclarer, même si Docker téléchargera automatiquement les images, car sinon ces images ne seront pas mises en cache, et risque d’être téléchargées plusieurs fois car le cache de Docker n’est pas préservé entre chaques exécutions.

L’instruction DOCKER LOAD permet de récupérer l’image sauvegardé depuis le target ci puis nous le nommons zedtux/brewformulas.org:dev car c’est le nom utilisé dans mon fichier docker-compose.yml.

Arrive ensuite docker-compose pour démarrer tous les services.

La ligne suivante va récupérer l’adresse IP du container où tourne PostgreSQL et la stocke dans la variable POSTGRESQL_CONTAINER_IPADDR. Ceci est nécessaire car autrement, si vous essayez de vous y connecter avec locahost, vous aurez une erreur “Connection refused” comme décrit dans cet article “Localhost postgres docker ‘Connection refused’ using pgAdmin”.

La ligne suivante attends que PostgreSQL démarre en tantant une trentaine de connexions sur l’adresse IP du container de PostgreSQL.

Une fois que PostgreSQL a démarré, j’utilises un docker run afin de configurer la base de données (création de la base de données, puis exécution des scripts de migration et du fichier seed.rb si nécessaire) puis lancer les tests.

Lancer les tests en local

Maintenant pour lancer les tests, il faut lancer earth +integration-test, comme vue dans mon précédent article, mais cette fois-ci, des priviléges augmentés sont nécessaires afin d’utiliser Docker dans Docker, alors il faudra passer le paramètre --allow-privileged ou -P:

1
earth -P +integration-test

Et lorsque quelque chose ne fonctionne pas?

Avant de comprendre que je devais récupérer l’adresse IP du container pour connecter Brewformulas.org à PostgreSQL, j’avais besoiun de débogger l’environnement Docker dans Earthly.

Pour le faire, il vous suffit de passer le paramètre --interactive ou -i. Ce paramètre fera que si une erreur survient dans votre environnement Earthly (dans mon cas, docker run ... se termine avec un exit code supérieur à 0), Earthly vous ouvre une session à l’intérieur et vous pourrez donc inspecter Docker (docker ps, docker inspect <...> etc …).

Lancer les tests sur Gitlab CI

Pour finir j’ai importer un fichier .gitlab-ci.yml, généré depuis l’interface de Gitlab, pour se connecter à Gitlab (afin de télécharger et téléverser des images Docker), puis je lance earth +prod afin de construire l’image de production, qui serait l’image téléversée si tout se passe bien, puis je lance earth -P +integration-test pour lancer les tests.

Si tout se passe bien, alors docker push "$CI_REGISTRY_IMAGE" sera exécuté, et donc l’image de production sauvegardée dans le registre Docker de Gitlab.

Et ca marche?

De façon assez intéressante mes tests Rspec passent en local, mais pas mes tests Cucumber, qui m’affiche 3 tests râtés sur 95, et Gitlab CI me dit exactement la même chose, donc ca fonctionne du feu de dieu!

Earthly permet donc bien de dire adieu aux problèmes de comportements différent d’une machine à l’autre et permet donc d’éviter ceci:

Lisez le blog d’Earthly. Source de l’image.