Page d'accueil Mes articles

Docker-compose pour la production ( 1 / 3 )

Passons aux choses sérieuses en préparant notre projet Hello Word pour la production. Nous allons voir ici l'intérêt de créer des images pour à la production ainsi que la notion de réseau.

Publié en November 2021.
Dernière mise à jour December 2022.

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.

Texte alternatif

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.

Texte alternatif

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 !

Texte alternatif

Il ne me reste plus qu'à créer ma base de données comme dans l'article précédent, et c'est bon 😁

Texte alternatif

... 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 !

Cet article vous a été utile ? Faites le connaître sur les réseaux sociaux Twitter twitter LinkedIn linkedin

Un commentaire ?

codeur
Ou connectez-vous avec GitHub