Je me donne maintenant comme objectif de mettre ce "Hello World" en production. Comme je le disais dans l'article précédent, le but va être d'envoyer mes images de production sur le Container Registry de gitlab.
Pour commencer, identifiez-vous à votre registry à l'aide la commande docker docker login registry.gitlab.com
Une fois authentifié, rajoutez à la racine de votre projet un .env contenant la variable d'environnement pointant vers votre container registry.
# .env
CONTAINER_REGISTRY=registry.gitlab.com/yoann.chocteau/demo_docker
Cette variable d'environnement sera utilisée par docker-compose lors de nos builds et nos pushs.
Maintenant nous allons devoir construire nos images de production à l'aide d'un Dockerfile que nous allons mettre à la racine de notre projet.
# Je définis un alias à l'image que je vais créer, app_nginx
FROM nginx:1.19.3-alpine as app_nginx
# On lui ajoute la conf qui va lui permettre de communiquer avec PHP
ADD docker/nginx/conf.d /etc/nginx/conf.d
# Et bien sûr, on ajoute notre fichier index.php contenant notre fabuleux Hello World sur /srv/app/
COPY index.php /srv/app/
# Même principe pour app_php
FROM php:7.4.12-fpm-alpine as app_php
RUN docker-php-ext-install mysqli pdo pdo_mysql
COPY index.php /srv/app/
Une fois que mon Dockerfile est créé, je vais pouvoir mettre en place mon docker-compose-build.yaml.
version: '3.8'
services:
app:
image: ${CONTAINER_REGISTRY}/app
build:
context: ./
target: app_php
cache_from:
- ${CONTAINER_REGISTRY}/app
nginx:
image: ${CONTAINER_REGISTRY}/nginx
build:
context: ./
target: app_nginx
cache_from:
- ${CONTAINER_REGISTRY}/nginx
Ce docker-compose-build.yaml va me permettre de créer deux images pour la production, nginx et app. On notera que j'ai à la fois une clé image et une clé build. La clé build permet de définir où se trouve le Dockerfile pour créer mon image ainsi que la cible ( app_nginx / app_php ) et la clé image contient le nom de donné à mon image une fois construite.
On va pouvoir construire nos deux images de production avec la commande docker-compose -f docker-compose-build.yaml build --pull
, l'option -f permettant de spécifier à docker-compose le fichier à utiliser pour construire les images.
❯ docker-compose -f docker-compose-build.yaml build --pull
...
Successfully built c4f7135b307f
Successfully tagged registry.gitlab.com/yoann.chocteau/demo_docker/nginx:latest
Il ne nous reste plus qu'à envoyer ces images sur notre Container Registry avec la commande suivante
❯ docker-compose -f docker-compose-build.yaml push
Pushing app (registry.gitlab.com/yoann.chocteau/demo_docker/app:latest)...
The push refers to repository [registry.gitlab.com/yoann.chocteau/demo_docker/app]
...
Si tout s'est bien passé, vous devriez les voir sur votre gitlab.
Ces images là contiennent désormais mon application de production. Un simple "Hello World" pour le moment, mais ce code est dans mon image ( à la différence de ma stack de dev présentée précédemment où il s'agissait d'un montage de volume ).
Maintenant, je vais devoir faire mon docker-compose-prod.yaml qui lui aura la responsabilité de déployer mes conteneurs en production.
version: '3.8'
services:
app:
image: ${CONTAINER_REGISTRY}/app
# En cas de problème... redémarre !
restart: always
nginx:
image: ${CONTAINER_REGISTRY}/nginx
restart: always
depends_on:
- app
ports:
- 8080:80
Comme on peut le constater, il ressemble beaucoup à mon docker-compose.yml mais ne possède pas de clé build pour les services nginx et app, et pas non plus de base de donnée.
Faites un docker-compose down
si votre stack de développement est encore en cours, puis lancez la commande suivante :
❯ docker-compose -f docker-compose-prod.yaml up -d
Maintenant, si vous accédez à localhost:8080, mise à part l'erreur SQL Name does not resolve puisqu'on s'est pas encore occupé de la base de données, on peut dire que ça fonctionne. Je suis proche de voir mon Hello World ! 😃
Pourquoi la base de données n'est pas présente dans le docker-compose-prod.yaml ?
Et bien simplement parce que je veux être sûr de ne pas perdre les données de mon site. Je ne veux pas que ces données soient stockées dans un conteneur ou sur un volume docker. D'ailleurs, sur une production "sérieuse", on choisira en général d'utiliser un service de base de données infogéré.
Pour les besoins de la démo, je vais créer ma base de données mariadb sur docker mais ailleurs sur mon système pour bien garder à l'esprit que ma base de données de production ne doit pas se trouver dans mon docker-compose-prod.yaml.
J'ai donc créé un répertoire mariadb dans lequel j'ai mis le fichier docker-compose.yaml suivant
version: '3.8'
services:
db:
image: mariadb:10.5.6
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
volumes:
- ./data:/var/lib/mysql
Si je relance mon docker-compose -f docker-compose-prod.yaml up -d
, en l'état cela ne réglera pas mon erreur SQL. Le conteneur db n'est pas dans le même réseau que le conteneur app. Je vais donc devoir créer un réseau demo_docker afin que ma base de données et mon conteneur app puissent communiquer ensemble.
❯ docker network create demo_docker
fa7f4ac3fd8e27e4423f62157c98efaf209ba8a9a42bf7980ac1011baae01166
Une fois mon network créé, je vais devoir le définir dans mes fichiers docker-compose
# mariadb/docker-compose.yaml
version: '3.8'
services:
db:
image: mariadb:10.5.6
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
# J'attache ma base de données à mon réseau demo_docker
networks:
- demo_docker
volumes:
- ./data:/var/lib/mysql
networks:
# Je définis que demo_docker est un réseau externe qui se nomme demo_docker
demo_docker:
external:
name: demo_docker
Et je fais les mêmes adaptations sur mon docker-compose-prod.yaml de mon projet.
# demo_docker/docker-compose-prod.yaml
version: '3.8'
services:
app:
image: ${CONTAINER_REGISTRY}/app
restart: always
networks:
- demo_docker
nginx:
image: ${CONTAINER_REGISTRY}/nginx
restart: always
networks:
- demo_docker
depends_on:
- app
ports:
- 8080:80
networks:
demo_docker:
external:
name: demo_docker
Je redémarre les conteneurs de mon projet
❯ docker-compose -f docker-compose-prod.yaml up -d --force-recreate
ainsi que celui de mariadb
❯ docker-compose up -d --force-recreate
Et là je retombe sur cette erreur là... plutôt bon signe !
Il ne me reste plus qu'à créer ma base de données comme dans l'article précédent, et c'est bon 😁
... Par contre, j'ai un problème ! La chaine de connexion est en dur dans le fichier index.php... et ça, c'est vraiment pas très propre ! Comment faire la distinction entre ma base de dev et celle de prod ? Et bien, la solution réside dans les variables d'environnement 😁
Pour commencer, je vais modifier mon script index.php
<?php
try {
$dbh = new PDO($_SERVER['DB_DSN'], $_SERVER['DB_USER'], $_SERVER['DB_PASSWORD']);
} catch (PDOException $e) {
print "Erreur !: " . $e->getMessage() . "<br/>";
die();
}
echo "Hello World !!";
Ensuite, dans mon fichier docker-compose.yaml et dans le fichier docker-compose-prod.yaml, je vais définir ces variables d'environnements pour le conteneur app.
app:
...
environment:
- DB_DSN
- DB_USER
- DB_PASSWORD
Et pour finir, je dois juste rajouter dans mon fichier .env ces variables d'environnements.
DB_DSN=mysql:host=db;dbname=demo
DB_USER=root
DB_PASSWORD=
Et voilà 😁 Maintenant, il me reste plus qu'à déployer tout cela sur un serveur !