Comment configurer un environnement de test d'intégration continue avec Docker et Docker Compose sur Ubuntu 16.04
Introduction
L'intégration continue (CI) fait référence à la pratique selon laquelle les développeurs intègrent le code aussi souvent que possible et chaque validation est testée avant et après avoir été fusionnée dans un référentiel partagé par une construction automatisée .
CI accélère votre processus de développement et minimise les risques de problèmes critiques en production, mais il n'est pas anodin à mettre en place ; les builds automatisés s'exécutent dans un environnement différent où l'installation des dépendances d'exécution et la configuration des services externes peuvent être différentes de celles de vos environnements local et de développement.
Docker est une plateforme de conteneurisation qui vise à simplifier les problématiques de standardisation des environnements afin de standardiser également le déploiement des applications (en savoir plus sur Docker). Pour les développeurs, Docker vous permet de simuler des environnements de production sur des machines locales en exécutant des composants d'application dans des conteneurs locaux. Ces conteneurs sont facilement automatisables à l'aide de Docker Compose, indépendamment de l'application et du système d'exploitation sous-jacent.
Ce didacticiel utilise Docker Compose pour démontrer l'automatisation des workflows CI.
Nous allons créer une application Python Dockerisée de type « Hello world » et un script de test Bash. L'application Python nécessitera l'exécution de deux conteneurs : un pour l'application elle-même et un conteneur Redis pour le stockage requis en tant que dépendance pour l'application.
Ensuite, le script de test sera Dockerisé dans son propre conteneur et l'ensemble de l'environnement de test déplacé vers un fichier docker-compose.test.yml afin que nous puissions nous assurer que nous exécutons chaque exécution de test dans un nouveau et uniforme environnement applicatif.
Cette approche montre comment vous pouvez créer un nouvel environnement de test identique pour votre application, y compris ses dépendances, à chaque fois que vous la testez.
Ainsi, nous automatisons les workflows CI indépendamment de l'application testée et de l'infrastructure sous-jacente.
Conditions préalables
Avant de commencer, vous aurez besoin de :
- Un serveur Ubuntu 16.04 avec un utilisateur non root avec des privilèges sudo. Le Configuration initiale du serveur avec Ubuntu 16.04 explique comment le configurer.
- Docker, installé en suivant les étapes 1 et 2 de Comment installer et utiliser Docker sur Ubuntu 16.04.
- Docker Compose, installé après l'étape 1 de Comment installer Docker Compose sur Ubuntu 16.04
Étape 1 - Créer l'application Python "Hello World"
Dans cette étape, nous allons créer une application Python simple comme exemple du type d'application que vous pouvez tester avec cette configuration.
Créez un nouveau répertoire pour notre application en exécutant :
cd ~ mkdir hello_world cd hello_world
Éditez un nouveau fichier app.py
avec nano :
nano app.py
Ajoutez le contenu suivant :
app.py
from flask import Flask from redis import Redis app = Flask(__name__) redis = Redis(host="redis") @app.route("/") def hello(): visits = redis.incr('counter') html = "<h3>Hello World!</h3>" \ "<b>Visits:</b> {visits}" \ "<br/>" return html.format(visits=visits) if __name__ == "__main__": app.run(host="0.0.0.0", port=80)
Lorsque vous avez terminé, enregistrez et quittez le fichier.
app.py
est une application Web basée sur Flask qui se connecte à un service de données Redis. La ligne visits = redis.incr('counter')
augmente le nombre de visites et conserve cette valeur dans Redis. Enfin, un message Hello World
avec le nombre de visites est renvoyé en HTML.
Notre application a deux dépendances, Flask
et Redis
, que vous pouvez voir dans les deux premières lignes. Ces dépendances doivent être définies avant de pouvoir exécuter l'application.
Ouvrez un nouveau fichier :
nano requirements.txt
Ajoutez le contenu :
exigences.txt
Flask Redis
Lorsque vous avez terminé, enregistrez et quittez le fichier. Maintenant que nous avons défini nos exigences, que nous mettrons en place plus tard dans le docker-compose.yml
, nous sommes prêts pour la prochaine étape.
Étape 2 - Dockerize l'application "Hello World"
Docker utilise un fichier appelé Dockerfile
pour indiquer les étapes requises pour créer une image Docker pour une application donnée. Modifier un nouveau fichier :
nano Dockerfile
Ajoutez le contenu suivant :
Fichier Docker
FROM python:2.7 WORKDIR /app ADD requirements.txt /app/requirements.txt RUN pip install -r requirements.txt ADD app.py /app/app.py EXPOSE 80 CMD ["python", "app.py"]
Analysons le sens de chaque ligne :
FROM python:2.7
: indique que notre image d'application "Hello World" est construite à partir de l'image Docker officiellepython:2.7
WORKDIR /app
: définit le répertoire de travail à l'intérieur de l'image Docker sur/app
ADD requirements.txt /app/requirements.txt
: ajoute le fichierrequirements.txt
à notre image DockerRUN pip install -r requirements.txt
: installe les dépendancespip
de l'applicationADD app.py /app/app.py
: ajoute le code source de notre application à l'image DockerEXPOSE 80
: indique que notre application est joignable au port 80 (le port web public standard)CMD ["python", "app.py"]
: la commande qui lance notre application
Enregistrez et quittez le fichier. Ce fichier Dockerfile
contient toutes les informations nécessaires pour construire le composant principal de notre application "Hello World".
La dépendance
Passons maintenant à la partie la plus sophistiquée de l'exemple. Notre application nécessite Redis en tant que service externe. C'est le type de dépendance qui pourrait être difficile à configurer de manière identique à chaque fois dans un environnement Linux traditionnel, mais avec Docker Compose, nous pouvons le configurer de manière reproductible à chaque fois.
Créons un fichier docker-compose.yml
pour commencer à utiliser Docker Compose.
Modifier un nouveau fichier :
nano docker-compose.yml
Ajoutez le contenu suivant :
docker-compose.yml
web: build: . dockerfile: Dockerfile links: - redis ports: - "80:80" redis: image: redis
Ce fichier Docker Compose indique comment faire tourner l'application "Hello World" localement dans deux conteneurs Docker.
Il définit deux conteneurs, web
et redis
.
web
utilise le répertoire courant pour le contextebuild
et construit notre application Python à partir du fichierDockerfile
que nous venons de créer. Il s'agit d'une image Docker locale que nous avons créée uniquement pour notre application Python. Il définit un lien vers le conteneurredis
afin d'avoir accès à l'IP du conteneurredis
. Il rend également le port 80 accessible au public depuis Internet en utilisant l'adresse IP publique de votre serveur Ubuntu.redis
est exécuté à partir d'une image Docker publique standard, nomméeredis
.
Lorsque vous avez terminé, enregistrez et quittez le fichier.
Étape 3 - Déployez l'application "Hello World"
Dans cette étape, nous allons déployer l'application et, à la fin, elle sera accessible sur Internet. Pour les besoins de votre flux de travail de déploiement, vous pouvez considérer qu'il s'agit d'un environnement de développement, intermédiaire ou de production, car vous pouvez déployer l'application de la même manière plusieurs fois.
Les fichiers docker-compose.yml
et Dockerfile
permettent d'automatiser le déploiement des environnements locaux en exécutant :
docker-compose -f ~/hello_world/docker-compose.yml build docker-compose -f ~/hello_world/docker-compose.yml up -d
La première ligne construit notre image d'application locale à partir du fichier Dockerfile
. La deuxième ligne exécute les conteneurs web
et redis
en mode démon (-d
), comme spécifié dans le fichier docker-compose.yml
.
Vérifiez que les conteneurs d'application ont été créés en exécutant :
docker ps
Cela devrait montrer deux conteneurs en cours d'exécution, nommés helloworld_web_1
et helloworld_redis_1
.
Vérifions que l'application est active. Nous pouvons obtenir l'IP du conteneur helloworld_web_1
en exécutant :
WEB_APP_IP=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' helloworld_web_1) echo $WEB_APP_IP
Vérifiez que l'application Web renvoie le bon message :
curl http://${WEB_APP_IP}:80
Cela devrait retourner quelque chose comme :
Production
<h3>Hello World!</h3><b>Visits:</b> 2<br/>
Le nombre de visites est incrémenté chaque fois que vous atteignez ce point de terminaison. Vous pouvez également accéder à l'application "Hello World" depuis votre navigateur en visitant l'adresse IP publique de votre serveur Ubuntu.
Comment personnaliser pour votre propre application
La clé pour configurer votre propre application est de placer votre application dans son propre conteneur Docker et d'exécuter chaque dépendance à partir de son propre conteneur. Ensuite, vous pouvez définir les relations entre les conteneurs avec Docker Compose, comme illustré dans l'exemple. Docker Compose est couvert plus en détail dans cet article Docker Compose.
Pour un autre exemple d'exécution d'une application sur plusieurs conteneurs, lisez cet article sur l'exécution de WordPress et phpMyAdmin avec Docker Compose.
Étape 4 - Créer le script de test
Nous allons maintenant créer un script de test pour notre application Python. Ce sera un simple script qui vérifie la sortie HTTP de l'application. Le script est un exemple du type de test que vous pouvez exécuter dans le cadre de votre processus de déploiement d'intégration continue.
Modifier un nouveau fichier :
nano test.sh
Ajoutez le contenu suivant :
test.sh
sleep 5 if curl web | grep -q '<b>Visits:</b> '; then echo "Tests passed!" exit 0 else echo "Tests failed!" exit 1 fi
test.sh
teste la connectivité Web de base de notre application "Hello World". Il utilise cURL pour récupérer le nombre de visites et indique si le test a été réussi ou non.
Étape 5 - Créer l'environnement de test
Afin de tester notre application, nous devons déployer un environnement de test. Et nous voulons nous assurer qu'il est identique à l'environnement d'application en direct que nous avons créé à l'Étape 3.
Tout d'abord, nous devons dockeriser notre script de test en créant un nouveau fichier Dockerfile. Modifier un nouveau fichier :
nano Dockerfile.test
Ajoutez le contenu suivant :
Dockerfile.test
FROM ubuntu:xenial RUN apt-get update && apt-get install -yq curl && apt-get clean WORKDIR /app ADD test.sh /app/test.sh CMD ["bash", "test.sh"]
Dockerfile.test
étend l'image officielle ubuntu:xenial
pour installer la dépendance curl
, ajoute tests.sh
au système de fichiers image et indique la commande CMD
qui exécute le script de test avec Bash.
Une fois nos tests dockerisés, ils peuvent être exécutés de manière reproductible et agnostique.
L'étape suivante consiste à lier notre conteneur de test à notre application "Hello World". C'est là que Docker Compose vient à nouveau à la rescousse. Modifier un nouveau fichier :
nano docker-compose.test.yml
Ajoutez le contenu suivant :
docker-compose.test.yml
sut: build: . dockerfile: Dockerfile.test links: - web web: build: . dockerfile: Dockerfile links: - redis redis: image: redis
La seconde moitié du fichier Docker Compose déploie l'application principale web
et sa dépendance redis
de la même manière que le fichier docker-compose.yml
précédent. Il s'agit de la partie du fichier qui spécifie les conteneurs web
et redis
. La seule différence est que le conteneur web
n'expose plus le port 80, donc l'application ne sera pas disponible sur l'Internet public pendant les tests. Ainsi, vous pouvez voir que nous construisons l'application et ses dépendances exactement de la même manière que dans le déploiement en direct.
Le fichier docker-compose.test.yml
définit également un conteneur sut
(du nom de system under tests) qui est responsable de l'exécution de nos tests d'intégration. Le conteneur sut
spécifie le répertoire actuel comme notre répertoire build
et spécifie notre fichier Dockerfile.test
. Il est lié au conteneur web
afin que l'adresse IP du conteneur d'application soit accessible à notre script test.sh
.
Comment personnaliser pour votre propre application
Notez que docker-compose.test.yml
peut inclure des dizaines de services externes et plusieurs conteneurs de test. Docker pourra exécuter toutes ces dépendances sur un seul hôte car chaque conteneur partage le système d'exploitation sous-jacent.
Si vous avez d'autres tests à exécuter sur votre application, vous pouvez créer des Dockerfiles supplémentaires pour eux, similaires au fichier Dockerfile.test
illustré ci-dessus.
Ensuite, vous pouvez ajouter des conteneurs supplémentaires sous le conteneur sut
dans le fichier docker-compose.test.yml
, en faisant référence aux Dockerfiles supplémentaires.
Étape 6 - Testez l'application "Hello World"
Enfin, en étendant les idées de Docker des environnements locaux aux environnements de test, nous avons un moyen automatisé de tester notre application à l'aide de Docker en exécutant :
docker-compose -f ~/hello_world/docker-compose.test.yml -p ci build
Cette commande construit les images locales nécessaires à docker-compose.test.yml
. Notez que nous utilisons -f
pour pointer vers docker-compose.test.yml
et -p
pour indiquer un nom de projet spécifique.
Lancez maintenant votre nouvel environnement de test en exécutant :
docker-compose -f ~/hello_world/docker-compose.test.yml -p ci up -d
OutputCreating ci_redis_1 Creating ci_web_1 Creating ci_sut_1
Vérifiez la sortie du conteneur sut
en exécutant :
docker logs -f ci_sut_1
Production
% Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 42 100 42 0 0 3902 0 --:--:-- --:--:-- --:--:-- 4200 Tests passed!
Et enfin, vérifiez le code de sortie du conteneur sut
pour vérifier si vos tests ont réussi :
docker wait ci_sut_1
Production
0
Après l'exécution de cette commande, la valeur de $?
sera 0
si les tests ont réussi. Sinon, nos tests d'application ont échoué.
Notez que d'autres outils CI peuvent cloner notre référentiel de code et exécuter ces quelques commandes pour vérifier si les tests réussissent avec les derniers bits de votre application sans se soucier des dépendances d'exécution ou des configurations de services externes.
C'est ça! Nous avons exécuté avec succès notre test dans un environnement fraîchement construit identique à notre environnement de production.
Conclusion
Grâce à Docker et Docker Compose, nous avons pu automatiser la construction d'une application (Dockerfile
), le déploiement d'un environnement local (docker-compose.yml
), la construction d'une image de test (Dockerfile.test
) , et exécuter des tests (d'intégration) (docker-compose.test.yml
) pour n'importe quelle application.
En particulier, les avantages de l'utilisation du fichier docker-compose.test.yml
pour les tests sont que le processus de test est :
- Automatable : la manière dont un outil exécute le
docker-compose.test.yml
est indépendante de l'application testée - Léger : des centaines de services externes peuvent être déployés sur un seul hôte, simulant des environnements de test complexes (d'intégration)
- Agnostique : évitez le verrouillage du fournisseur CI et vos tests peuvent s'exécuter dans n'importe quelle infrastructure et sur n'importe quel système d'exploitation prenant en charge Docker
- Immuable : les tests passant sur votre machine locale passeront dans votre outil CI
Ce didacticiel montre un exemple de test d'une simple application "Hello World".
Il est maintenant temps d'utiliser vos propres fichiers d'application, de Dockeriser vos propres scripts de test d'application et de créer votre propre docker-compose.test.yml
pour tester votre application dans un environnement frais et immuable.