Tutoriel Symfony et Docker grâce à docker-compose

Tutoriel Symfony et Docker grâce à docker-compose

Depuis plusieurs mois, voir maintenant depuis un an, je suis devenu un fan INCONDITIONNEL de Docker. Dans un premier temps, j’ai souhaité utiliser Docker dans le but d’exécuter Skype sans casser mon système d’exploitation (sacré Microsoft…). Ce n’était alors qu’une solution à l’essai que je maitrisais mal. Entre temps, j’ai investit beaucoup de temps sur cette techno et écrit ces quelques articles pour le compte de Wanadev.

Bien qu’ayant prévu un quatrième article sur l’utilisation appliquée des containers avec ce que nous appelons docker-compose, voilà un article qui a pour objectif de faire une brève description de docker-compose et de vous donner une configuration simple pour utiliser Docker avec votre projet Symfony.

ATTENTION ! Cet article donne quelques explications légères sur Docker, il ne s’agit en aucun cas d’un tutoriel expliquant Docker. Référez-vous aux 3 articles ci-dessus.

docker-compose a fait évolué la syntaxe de son format avec une version 2. Un article retraçant les évolutions ainsi que comment intégrer ces amméliorations (gestion des volumes et des réseaux) avec une application Symfony est désormais disponible.

Docker oui, mais docker-compose pour aller plus vite !

Docker est une techno super, mais dans un projet on utilise rarement une unique techo.

Exemple, pour un projet Symfony vous aurez besoin d’un frontale de type Nginx ou Apache, d’un engine (certains appellent ça aussi un runner ou un worker) de type PHP-FPM (PHP-NG pour PHP >= 7) ou HHVM, ainsi que d’une base de données comme MySQL, PostgreSQL ou autres…

L’objectif de Docker est d’isoler vos applications mais cela doit aussi vous permettre de séparer vos outils. Ceci est une des recommandations de Docker mais sachez que vous gagnerez à séparer vos outils. Pourquoi ?

  • Dans de nombreux cas, j’ai remarqué que les personnes distinguaient mal le rôle de certains services. Exemple, si vous utilisez Apache, vous distinguerez peut-être mal à quel moment Apache fait office de frontale et à quel moment de runner.
  • Cela doit vous permettre de facilement changer de technologie. Exemple, vous pourrez facilement dire que vous voulez remplacer PHP-FPM par HHVM ou mettre à jour la version (avec Docker, ces changements s’apparentent à la méthode).
  • Vous pouvez également augmenter le scale, soit le nombre de containers d’une même image qui fonctionnent en parallèle. L’objectif ? Clusteriser vos containers (voir Swarm).
  • Plus simple, cela vous permettra d’utiliser directement les images présentes sur le Docker Hub (privilégiez les images officiels dès que possible), sans avoir à construire des images complexes. La maintenabilité n’en sera que plus facile.
    Dans notre cas, nous travaillerons avec Nginx, FPM, MySQL. Toutes ces images vont devoir se lier entre elle pour pouvoir communiquer ensemble. Docker-compose est là pour vous simplifier la vie. Initialement, appelez Fig par son développeur initiale, il a été renommé et intégrer par les équipes de Docker pour en devenir un produit central. À partir d’un simple fichier YAML, vos configurations seront facilement mise en place, lancé… et isolée par projet.

Un peu de configuration

Je vous conseille de créer un fichier docker-compose.yml.dist dans votre projet qui sera versionné. Celui-ci doit contenir des instructions universelles à votre projet. Pensez à ignorer le fichier docker-compose.yml pour que chacun puisse facilement configurer les ports (selon les projets…), les volumes et autres options exotiques.

Je vous recommande de placer les fichiers docker-compose.yml.dist et docker-compose.yml à la racine du projet.

Configuration de la base de données

Dans votre fichier, nous allons commencer par configurer la base de données. C’est le dernier maillon de notre chaine (commencer par lui permet de simplement aborder notre configuration).

Un petit exemple et on détaille tout ça !

1
2
3
4
5
6
7
8
9
db:
image: mysql:5.7
ports:
- "3306:3306"
environment:
- "MYSQL_ROOT_PASSWORD=your_root_password"
- "MYSQL_USER=your_user"
- "MYSQL_PASSWORD=your_user_password"
- "MYSQL_DATABASE=your_database_name"

Voici la première partie de votre configuration. db correspond au nom du container qui sera donné. Le container sera créé à partir de l’image officielle de mysql en version 5.7. Nous allons ensuite forwarder le port 3306 de notre host (notre machine - 3306:…) vers le port 3306 du container (…:3306).

Dans la rubrique environment, les variables settées sont très facile à comprendre. Dans le cas de cette image, ces variables sont obligatoires. Elles sont bien évidemment détaillées sur la page dédiée à cette image. L’image MySQL est disponible à cet endroit.

Une fois lancé, en l’état, en contactant le port 3306 de votre machine (localhost:3306) vous accèderez à votre base de données.

Configuration de l’engine

L’engine sera PHP-FPM, soit le processus qui va exécuter le code source. Pour son fonctionnement, PHP requiert le code source, il faudra donc partager le code source du projet, ainsi qu’un lien avec la base de données (déclarée au-dessus).

1
2
3
4
5
6
7
8
engine:
    build: ./docker/engine/
    volumes:
        - ".:/home/docker:rw"
        - "./docker/engine/php.ini:/usr/local/etc/php/conf.d/custom.ini:ro"
    links:
        - "db:db"
    working_dir: "/home/docker"

Décrivons la configuration. engine est le nom de votre container.

Contrairement à notre bdd, nous n’utilisons pas directement une image du docker hub car Symfony requiert l’installation d’extensions PHP qui ne sont pas présentent dans les images officiels de PHP-FPM par défaut. Avec build, nous allons donc construire notre image en partant de l’image officielle. À la racine de votre projet, créez un dossier docker dans lequel vous pourrez placer le fichier de configuration propres à votre image. Dans le dossier docker/engine/, placez-y le fichier Dockerfile avec les instructions de construction suivantes.

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
FROM php:5.6-fpm
MAINTAINER Baptiste Donaux <baptiste.donaux@gmail.com>
ENV PHP_APCU_VERSION 4.0.11
ENV PHP_XDEBUG_VERSION 2.4.0
RUN apt-get update \
    && apt-get install -y \
        libicu-dev \
        zlib1g-dev \
&& docker-php-source extract \
    && curl -L -o /tmp/apcu-$PHP_APCU_VERSION.tgz https://pecl.php.net/get/apcu-$PHP_APCU_VERSION.tgz \
    && curl -L -o /tmp/xdebug-$PHP_XDEBUG_VERSION.tgz http://xdebug.org/files/xdebug-$PHP_XDEBUG_VERSION.tgz \
    && tar xfz /tmp/apcu-$PHP_APCU_VERSION.tgz \
    && tar xfz /tmp/xdebug-$PHP_XDEBUG_VERSION.tgz \
    && rm -r \
/tmp/apcu-$PHP_APCU_VERSION.tgz \
/tmp/xdebug-$PHP_XDEBUG_VERSION.tgz \
    && mv apcu-$PHP_APCU_VERSION /usr/src/php/ext/apcu \
    && mv xdebug-$PHP_XDEBUG_VERSION /usr/src/php/ext/xdebug \
&& sed -i '$ a apcu' /usr/src/php-available-exts \
&& sed -i '$ a xdebug' /usr/src/php-available-exts \
    && docker-php-ext-install \
        apcu \
        intl \
        mbstring \
        mysqli \
        xdebug \
        zip \
&& docker-php-source delete \
    && php -r "readfile('https://getcomposer.org/installer');" | php -- --install-dir=/usr/local/bin --filename=composer \
    && chmod +x /usr/local/bin/composer

Cette image est d’ailleurs destinée à fonctionner dans un environnement de dev dans la mesure où nous avons installé xdebug.

Dans volumes, la première ligne va monter en écriture le dossier du projet dans /home/docker. Le dossier sera monté en écriture car le _runner _aura besoin d’écrire le cache entre autre. (rw signifie read-write). La seconde ligne permettra d’ajouter un fichier de configuration spécifique.

Dans links vous retrouverez le lien à réaliser avec le container de la base de données.

Dans working_dir, le chemin de votre container. Très pratique pour arriver immédiatement dans votre projet.

Voici le fichier docker/engine/php.ini permettant de spécifier une configuration. Cela vous permettra par exemple de définir le nombre de loop maximum de xdebug ou tout autre paramètre.

Fichiers de configuration pour Docker compose et Symfony

Configuration du frontale

Le dernier maillon de notre application est le frontale. Dans notre cas, c’est Nginx qui va recevoir les requêtes, retourner les fichiers statiques ainsi que retourner les résultats de PHP.

1
2
3
4
5
6
7
8
9
front:
image: nginx
    ports:
        - "80:80"
    links:
        - "engine:engine"
volumes:
- ".:/home/docker:ro"
        - "./docker/front/default.conf:/etc/nginx/conf.d/default.conf:ro"

front est le nom du container, nginx l’image à utiliser. Le container permettant d’exécuter du PHP sera lié. Le port 80 de notre machine sera branché sur le port 80 du container. Le dossier courant (celui de projet) sera monté en lecture uniquement dans /home/docker. ro pour read-only (nginx n’a pas besoin d’écrire sur le disque).

La deuxième ligne va permettre de définir la configuration de Nginx. Transmettre la requête à PHP-FPM, définir le cache sur les ressources statiques…

Placez donc ce fichier dans docker/front/default.conf.

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
server {
    listen 80;
    root   /home/docker/web;
    rewrite ^/app_dev\.php/?(.*)$ /$1 permanent;
    location / {
        try_files $uri @rewriteapp;
    }
    gzip on;
    gzip_buffers 4 32k;
    gzip_min_length 1100;
    gzip_types text/plain application/x-javascript text/xml text/css;
    gzip_vary on;
    location ~ ^/(app|app_dev|config)\.php(/|$) {
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_pass engine:9000;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
    }
    location ~* \.(jpg|jpeg|gif|css|png|js|ico|html|eof|woff|ttf)$ {
        if (-f $request_filename) {
            expires 30d;
            access_log off;
        }
        try_files $uri @rewriteapp;
    }
    location @rewriteapp {
        rewrite ^(.*)$ /app_dev.php/$1 last;
    }
}

Tout comme pour l’engine, cette configuration est faite pour un environnement de dev.

Pourquoi versionner le .dist

Il est de fortes chances que votre machine possède déjà un proccessus sur le port 80 et sur le 3306. Par conséquent le dist ne sera pas réellement utilisable en l’état. En initialisant votre projet, vous n’aurez qu’à copier-cotre le fichier dist (docker-compose.yml.dist) vers un fichier docker-compose.yml. Là vous changerez les ports. Exemple :

1
2
3
4
5
6
7
8
9
front:
ports:
- "81:80"
db:
ports:
- "3307:3306"

De cette manière, vous pourrez contacter localhost:81 pour accéder un projet, et vous pourrez de fil en aiguille lancer plusieurs projets sur plusieurs ports sans conflits.

Symfony et la configuration du parameters.yml

Bien évidemment, Symfony ne va pas fonctionner tout seul. Voici la configuration à appliquer pour les éléments principaux.

1
2
3
4
5
6
7
parameters:
    database_driver: mysqli
    database_host: db
    database_port: 3306
    database_name: your_database_name
    database_user: your_user
database_password: your_user_password

Le driver de la base de données importe peu, c’est de la configuration classique. database_name, database_user et database_password peuvent facilement être retrouvés avec la configuration de MySQL dans docker-compose.yml.

Le plus important dans cette configuration est le host de la bdd et son port. db la valeur de database_host, est le « nom de domaine » ou vhost correspondant à l’IP du container de votre base de données. Par conséquent, en saisissant db, vous contactez directement l’IP du container MySQL. C’est pour cela que le port est et restera 3306, peu importe le port utilisé sur votre machine.

Conclusion

Docker est un formidable outils pour exécuter un projet comme Symfony dans un environnement unique. L’utiliser c’est l’adopter ! N’hésitez pas à me poser vos questions !

Retrouver tous les fichiers de configuration pour Docker compose et Symfony