Allô Docker

⏲️ ~11 min de lecture

Publié le par Hadrien
docker php

Oui oui, il s'agit bien d'une incrustation sur Michel Cymes

Oui oui, il s'agit bien d'une incrustation sur Michel Cymes !

Tout d'abord, commençons par un traditionnel aperçu de ce qui vous attend : le but de cet article est de vous montrer pas-à-pas comment mettre en place une architecture Docker simple, composée d'un serveur Nginx et d'un serveur PHP-FPM dans le but d'afficher un phpinfo(). Ça parait peu, mais vous verrez que ce n'est en réalité qu'un prétexte pour faire une premier grand pas dans le monde de Docker !

Étape 0 : installer Docker #

Pour Linux #

Il y a un paquet pour ça, rendez-vous simplement sur la page dédiée sur le site de Docker.

Pour OS X #

Pour le moment Docker utilise une fonctionnalité du noyau Linux pour fonctionner. Pour le faire marcher sur d'autres systèmes, il faut donc passer par une machine virtuelle.

Docker a officiellement un projet pour Mac, boot2docker, mais afin de pouvoir utiliser la puissance de Docker notamment en terme de partage de volumes, je vous recommanderais plutôt docker-osx, basé sur Vagrant. Depuis la version 1.3 de Docker, boot2docker vous permet aussi de faire du partage de volume !

Une fois installé, créez un fichier $HOME/.docker-osx/Vagrantfile_extra.rb avec ce contenu :

for port in 49000..49900
  config.vm.network "forwarded_port", guest: port, host: port
end

Ce code permettra simplement d'indiquer à Vagrant qu'on veut rediriger les ports locaux de 49000 à 49900 vers la machine virtuelle.

Pour Windows #

Hahaha LOL !

Mince, vous êtes sérieux ? Dans ce cas, boot2docker est aussi disponible pour vous. Mais sachez que vous serez très vite limité et ce tutoriel utilisera des fonctionnalités qui vous seront indisponibles.

Étape 1 : Nginx #

D'abord le plus simple, il n'y a presque rien à faire.

Il faut savoir que Docker dispose de son propre dépôt d'images officielles ou faites par des utilisateurs. On va donc simplement utiliser l'image Nginx officielle qui fait le café.

$ docker run --name demo_nginx -p 80 -d nginx

Petite explication rapide des options passées à docker run :

Si c'est la première fois que vous utilisez l'image Nginx, soyez patient, il faut d'abord que Docker la télécharge.

Une fois que c'est fait, rien besoin de plus, on a déjà un Nginx qui tourne comme on peut le voir avec la commande docker ps :

$ docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS           NAMES
34347b3ec693        nginx:1             "nginx -g 'daemon of    2 minutes ago       Up 27 seconds       443/tcp, 0.0.0.0:49168->80/tcp   demo_nginx

On voit donc que c'est le port local 49168 qui est lié au port 80. Et si on allait voir ce qu'il se passe à l'URL http://localhost:49168 ?

Oh la belle page Nginx par défaut ! Oh la belle page Nginx par défaut !

Étape 1 bis : utiliser notre propre HTML ! #

La page par défaut de Nginx, c'est cool, mais avoir notre propre HTML ce serait plus sympa. Pour faire ça on va utiliser les volumes. Les volumes sont des sortes de points de montage sur lesquels vous allez entre autre pouvoir lier votre code en local au conteneur. De cette manière on crée un point d'ancrage dynamique : si on fait une modification en local, elle sera aussitôt répercutée dans le conteneur (normal puisque c'est un point de montage).

Arrêtons et détruisons notre conteneur Nginx (ne vous en faîtes pas, tel le phœnix il va renaître de ses cendres) :

$ docker stop demo_nginx
$ docker rm demo_nginx

Maintenant, créez un fichier index.html dans votre répertoire de travail actuel (je laisse libre cours à votre imagination quant à son contenu). Ensuite on relance un conteneur Nginx :

$ docker run --name demo_nginx -v $(pwd):/usr/share/nginx/html -p 80 -d nginx

Nouvelle option : -v avec un paramètre de la forme /répertoire/local/absolu:/répertoire/dans/le/conteneur.

Ndla : ici on peut utiliser le répertoire /usr/share/nginx/html parce qu'il a été déclaré en tant que volume dans le Dockerfile de l'image Nginx

Lancez un nouveau docker ps pour voir le port lié au port 80 de Docker, et rouvrez l'URL dans votre navigateur. En principe, vous verrez votre fichier HTML !

Étape 2 : PHP #

Un peu plus complexe cette fois, il n'existe pas d'image officielle pour PHP-FPM, et bien qu'il doive certainement en exister plus d'une, on va quand même construire la notre histoire de voir comment créer une image soi-même.

Définition de l'image #

Commençons par créer le fichier docker/php/Dockerfile avec le contenu suivant :

FROM ubuntu:latest

ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update

RUN apt-get -y install php5-fpm
RUN sed -i 's/listen = \/var\/run\/php5-fpm.sock/listen = 9000/' /etc/php5/fpm/pool.d/www.conf

EXPOSE 9000

CMD ["/usr/sbin/php5-fpm", "-F"]

Examinons ce qui est fait étape par étape.

Création de l'image #

Il faut maintenant lancer la commande build sur notre Dockerfile pour pouvoir construire notre image de PHP-FPM.

$ docker build -t demo_fpm docker/php # docker/php en argument car c'est ici que se trouve notre Dockerfile

L'option -t permet de définir un nom et un tag pour notre image. Ici on nomme notre image demo_fpm, et comme on ne précise pas de tag (en principe sous la forme nom:tag), il sera automatiquement désigné comme latest

Étape 3 : Communication #

On a donc 2 conteneurs, un Nginx et un FPM, mais qui ne sont pour l'instant pas du tout au courant de l'existence l'un de l'autre.

On va commencer par supprimer une fois de plus le conteneur Nginx :

$ docker stop demo_nginx
$ docker rm demo_nginx

Lier des conteneurs #

On va commencer par lier le conteneur PHP-FPM au conteneur Nginx, afin que ce dernier puisse communiquer avec lui. Avec des serveurs physiques, ça reviendrait un peu à brancher un cable réseau de l'un à l'autre. L'option --link sert à ça.

En plus de tout ça, comme Docker est super sympa avec nous, des variables d'environnement sont créées pour nous aider ! On va donc voir comment ça se présente avec la commande env :

$ docker run -d --name demo_fpm demo_fpm
$ docker run --rm --link demo_fpm:fpm nginx env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=1aff2985ddc0
FPM_PORT=tcp://172.17.0.76:9000
FPM_PORT_9000_TCP=tcp://172.17.0.76:9000
FPM_PORT_9000_TCP_ADDR=172.17.0.76
FPM_PORT_9000_TCP_PORT=9000
FPM_PORT_9000_TCP_PROTO=tcp
FPM_NAME=/drunk_sammet/fpm
FPM_ENV_DEBIAN_FRONTEND=noninteractive
NGINX_VERSION=1.7.6-1~wheezy
HOME=/root

Explications rapides de ce qu'on vient de faire.
On a lancé un conteneur PHP-FPM. Ensuite on a lancé un conteneur Nginx auquel on a donc lié le conteneur PHP-FPM en lui donnant un alias : fpm. Pour finir au lieu de démarrer le serveur Nginx dans le conteneur Nginx, on lui a dit qu'on souhaitait exécuter une commande qu'on aura nous-même choisi : env.

On a donc obtenu toutes les variables d'environnement du conteneur Nginx. Et là, on peut constater l'apparition de variables de la forme ALIAS_DU_CONTENEUR_LIÉ_*, soit dans notre cas FPM_*. Ces variables d’environnement sont automatiquement créées par Docker afin de fournir au conteneur “hôte” des informations sur son conteneur “invité”.

Supprimez maintenant le conteneur PHP-FPM qu'on a lancé.

Préparation de Nginx #

Il faut commencer par créer une configuration Nginx pour que celui-ci puisse communiquer avec PHP-FPM. On va prendre une configuration plutôt minimaliste, docker/nginx/default.conf :

server {
    listen 80;

    server_name localhost;

    root /usr/share/nginx/html;
    index index.php;

    location / {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_index index.php;
        fastcgi_pass 127.0.0.1:9000;
    }
}

Vous noterez la ligne fastcgi_pass 127.0.0.1:9000;. C'est bien mignon, mais notre serveur PHP-FPM n'est pas sur le même serveur que Nginx, donc 127.0.0.1.
Il va donc falloir utiliser les variables d'environnement vues plus tôt. Un problème se pose alors : on ne peut pas utiliser ces variables d'environnement dans un Dockerfile, puisqu'au moment de la génération de l'image Docker, celle-ci n'est pas (et ne peut pas être) au courant qu'un jour peut-être elle sera lié à un autre conteneur.
Pour ça on va utiliser une autre fonctionnalité de Docker : un ENTRYPOINT. Un ENTRYPOINT permet de définir une commande de démarrage (bootstrap) qui sera exécutée avant la commande CMD, dans le contexte de “vie” d’un conteneur. La commande CMD sera passée en argument à la commande ENTRYPOINT, c’est donc à cette dernière qu’il incombe de s’assurer que le CMD soit ensuite exécuté.

Créons ce fameux fichier, docker/nginx/entrypoint.sh :

#!/usr/bin/env bash

sed -i "s/fastcgi_pass .*;/fastcgi_pass $FPM_PORT_9000_TCP_ADDR:$FPM_PORT_9000_TCP_PORT;/" /etc/nginx/conf.d/default.conf

exec "$@"

Assurez-vous ensuite que le fichier soit exécutable (chmod +x docker/nginx/entrypoint.sh).

Un dernier détail : la commande spécifié à ENTRYPOINT doit être une commande sur le système du conteneur qui l'exécute. Il faut donc trouver un moyen pour envoyer notre fichier sur le système du conteneur. On pourrait utiliser un volume pour ça, mais pour le coup un volume ne se prête pas vraiment à la situation.
On va donc créer un Dockerfile très minimaliste pour Nginx, docker/nginx/Dockerfile :

FROM nginx

ADD ./default.conf /etc/nginx/conf.d/default.conf

ADD ./entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

CMD ["nginx", "-g", "daemon off;"]

L'instruction ADD nous permet d'injecter un fichier local dans l’image. Contrairement à un volume, ce n’est pas dynamique. Si on change le contenu du fichier, on devra re-lancer un build pour prendre les modifications en compte.

On construit maintenant l'image :

$ docker build -t demo_nginx docker/nginx

Le bouquet final #

Commencez par créer un fichier index.php contenant un simple phpinfo().

Maintenant, on lance nos conteneurs :

$ docker run --name demo_fpm -v $(pwd):/usr/share/nginx/html -d demo_fpm
$ docker run --name demo_nginx -v $(pwd):/usr/share/nginx/html --link demo_fpm:fpm -p 80 -d demo_nginx
$ docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED              STATUS              PORTS                            NAMES
1c37e313a8e8        demo_nginx:latest   "/entrypoint.sh ngin    3 seconds ago        Up 1 seconds        443/tcp, 0.0.0.0:49172->80/tcp   demo_nginx
cedd406239a7        demo_fpm:latest     "/usr/sbin/php5-fpm     About a minute ago   Up About a minute   9000/tcp                         demo_fpm,demo_nginx/fpm

Si on se rend maintenant sur http://localhost:49172, on a bien notre phpinfo() !

Screenshot 2014-10-09 11.03.31

Conclusion/Teasing #

Ce qu'on peut retenir de tout ça, c'est que Docker, c'est pas si simple. Je suis volontairement parti sur une application simple (un bête phpinfo()) pour ne pas en plus rajouter la couche "paramétrage comme il faut" pour la faire tourner.

Cependant je suis conscient qu'on n'est pas au pays des Bisounours, et je vous prépare déjà un autre article pour faire tourner CakePHP 3 sous Docker ! Alors stay tuned ;)

Les Bisounours sont prêts pour la suite

Cet article vous a plu ? Sachez que nous recrutons !

← Accueil