Varnish, NGinx et PHP-FPM sous Docker

Date: 8/10/2014 | Catégories: Blog,Open-source,Systeme,vitualisation | Tags: ,,,,,,,

Suite au précédent article d'introduction sur Docker (que je vous conseille de lire avant de dévorer cet article), je me suis penché sur le problème suivant: comment créer et maintenir une "stack web" de compétition (Varnish + Nginx + PHP-FPM) basée sur cette technologie de virtualisation par conteneurs.

Quel est l'objectif ?

Nous allons donc, dans ce billet, détailler l'installation et la mise à jour d'une infrastructure, basée sur Docker, qui pourra servir à l'hébergement d'un site personnel, d'un blog ou bien du site d'une PME:

  • la base de donnée sera portée par l'implémentation libre de MySQL, c'est à dire MariaDB (nous aborderons cette partie dans un deuxième billet)
  • pour le moteur PHP, j'utilise PHP-FPM
  • pour le serveur Web, je ne jure que par Nginx (léger, bien documenté)
  • afin pour tenir la charge (notamment si vous utilisé un moteur de blog de type WordPress, j'utilise le système de cache Varnish)LEMP (Docker)

Dans le reste de de l'article, on appellera machine hôte, la machine qui hébergera Docker (lire ce billet pour la procédure d'installation de Docker).

Cette dernière peut être un PC portable ou bien un serveur dédié. C'est un des avantages de la virtualisation par conteneurs. Une fois validé sur une machine , on est sûr que le conteneur fonctionnera de manière identique sur une autre installation de Docker. Ainsi pour la rédaction de ce billet, j'ai utiisé mon PC portable sous Ubuntu 14.04 et un (superbe) VPS mis à disposition par les amis de Web4All sous Debian 7 (merci Aurélien !).

Pour respecter la philosophie de la virtualisation par conteneur, chaque brique sera mise dans un conteneur dédié. On aura ainsi 4 conteneurs sur une machine hôte.

Chaque conteneur communiquera avec les autres selon le schéma ci-dessus.

Les données (data base DB, page statique du site et cache) seront stockées sur la machine hôte.

Création des conteneurs

Il y a deux méthodes pour choisie les images qui seront à la base de nos conteneurs.

La première, la plus noble mais la plus longue à mettre en œuvre, est d'écrire soit même les DockersFiles permettant l'installation et l'exécution des logiciels. On garde ainsi le contrôle de notre infrastructure et la possibilité de configurer finement les applications (notamment au niveau des options de compilation). Dans ce cas, et pour respecter les "best practices" de Docker, il faudra, pour chaque conteneur, repartir d'une image de base de type Debian sur laquelle on viendra installer les briques logicielles de notre LEMP.

La seconde, que j'ai choisi dans ce billet (pas parce-que je suis un gros fainéant mais par manque de temps), est de partir des images disponibles sur la registry officielle de Docker. On gagne ainsi en rapidité de mise en place.  Les composants de notre web stack étant très populaires, on trouve des images supportées et maintenues par la communauté Docker (notamment NGinx). Cependant, on constatera une trop grande diversité dans les systèmes de bases (Ubuntu, CentOS...).

Avant de commencer, nous allons créer un répertoire sur la machine hôte qui hébergera les fichiers utiles à notre infrastructure:

Le conteneur NGinx

Pierre angulaire de notre "web stack", le serveur NGinx est très présent sur la "registry" officielle de Docker (plus de 900 images disponibles au moment de l'écriture de ce billet). J'ai choisi d'utiliser l'image officielle proposé par Docker (elle est conçue à partir de cete DockerFile).

On commence par télécharger les images NGinx officielle:

Puis on vérifie que les images sont visibles sur notre machine hôte (NGinx version 1.7.5 au moment de l'écriture de cet article):

On lance le conteneur:

Notre conteneur a le doux nom de webstack_nginx_1, il redirige le port TCP/8080 de notre hôte vers le port TCP/80 du conteneur (port par défaut de NGinx) et il assigne, en lecture seule, le volume /usr/share/nginx/html au répertoire hôte $HOME/data/www (à adapter à votre configuration). Pour résumer, toutes les pages HTML disponible dans le répertoire /home/nicolargo/data/www, seront accessible via le port HTTP/8080 de votre machine locale.

On ajoute une page HTML statique de test et une autre pour le test PHP que l'on utilisera dans le chapitre suivant:

Puis on valide que le serveur NGinx est bien opérationnel:

Ou directement depuis votre navigateur Web préféré:

Sélection_245L'image NGinx utilisée redirige les fichiers de log (access.log et error.log vert la sortie standard). Il est donc possible de visualiser les accès à son serveur Web en "attachant" le terminal de notre hôte à la sortie standard du conteneur:

Note: bien penser à mettre l'option --sig-proxy=true sous peine de stopper le conteneur lors de l'appuie sur ^C. Je vous conseille  de créer un alias dans votre shell.

Le conteneur PHP-FPM

Pour le moteur PHP, il n'existe pas d'image officielle. Mon choix s'est donc porté vers l'utilisation du repository de Jprjr basée sur une installation de PHP-FPM sous ArchLinux avec pas mal d'extensions par défaut (voir la liste dans le DockerFile).

On télécharge la dernière version des images avec:

On obtient bien:

On lance le conteneur:

Le conteneur est configuré par défaut pour écouter sur le port TCP/9000.

Très bien, on doit donc avoir le serveur NGinx et le moteur PHP-FPM qui sont lancés dans deux conteneurs différents. On va vérifier cela avec la commande:

Faire communiquer les deux conteneurs (NGinx & PHP-FPM)

Tout cela est très beau mais les deux conteneurs ne se connaissent pas. Il faut, pour cela, configurer le serveur NGinx pour utiliser le moteur PHP et donc que le conteneur NGinx connaisse l'adresse IP du conteneur PHP-FPM.

Heureusement, Docker propose l'option --link permettant de répondre à ce besoin. Cette option, à utiliser au moment du lancement du conteneur, va créer dynamiquement des entrées dans le fichier host et dans des variables d'environnement du conteneur, permettant ainsi à ce dernier de connaître comment joindre ses congénères.

Par exemple:

Maintenant que l'on sait comment faire communiquer les deux conteneurs, il suffit de configurer NGinx pour rediriger les fichier .PHP vers le moteur PHP-FPM du second conteneur.

Pour cela, plusieurs solutions sont là encore possibles. La plus simple est de surcharger la configuration NGinx par défaut(nginx.conf). Sur notre hôte, nous allons créer le fichier de configuration en question:

Note:

  • "fastcgi_pass webstack_php:9000;" qui utilise donc le hostname présent dans le fichier host
  • "fastcgi_param   SCRIPT_FILENAME    /srv/http$fastcgi_script_name;" qui va permettre à PHP-FPM d'aller chercher les pages PHP dans le même volume que le serveur Nginx mais assigné au répertoire /srv/http du conteneur (c'est la configuration par défaut de notre conteneur PHP-FPM)

Revenons à notre cas. Nous souhaitons que le conteneur NGinx (nommé webstack_nginx_01) connaisse le conteneur PHP (webstack_php_01).

On commence donc par supprimer les deux conteneurs existant:

puis de les recréer avec l'option --link (pour le conteneur NGinx qui va initier la connexion TCP vers le moteur PHP-FPM), la nouvelle configuration de NGinx et les volumes pointant sur notre page HTML et PHP:

On teste:

Sélection_246

Bingo !

A ce stade, nous avons donc deux conteneurs communiquant entre eux pour nous offrir un serveur Web compatible avec le langage PHP.

Le conteneur Varnish en frontal

Si vous suivez ce blog, vous savez tout le bien que je pense de l'utilisation du cache Varnish pour absorber la montée en charge des sites Web (sinon, vous pouvez effectuer une session de rattrapage en lisant ces articles). Nous allons donc créer un nouveau conteneur qui exposera le port TCP/80 et qui mettra en cache les pages construites par le serveur NGinx (qui est en écoute sur le port TCP/8080 si vous avez bien suivi...).

Nous allons utiliser l'image Varnish mise à disposition et maintenue par le contributeur jacksoncage.

On commence par la récupérer:

Puis on vérifie qu'elle est bien disponible sur notre hôte:

Comme il est indiqué dans la documentation de l'image, il faut écrire le fichier de configuration par défaut de Varnish (le fichier default.vcl):

On demande donc à Varnish d'aller directement communiquer avec le conteneur NGinx via l'adresse IP webstack-nginx (rappelez-vous que le hostname est créé dynamiquement par Docker au démarrage du conteneur avec l'option --link) et sur le port TCP 80 (attention, ce n'est pas le port TCP/8080 exposé par Docker sur notre hôte mais celui vraiment utilisé par NGinx dans le conteneur).

A noter que cette configuration est à compléter, notamment si vous voulez héberger un blog sous WordPress (des exemples de fichiers de configuration sont disponibles ici). Attentinon, ces exemples sont pour Varnish 4.0, donc à adapter si la version du conteneur jacksoncage/varnish n'est pas en ligne.

Puis lancer le conteneur avec les options suivantes:

On redirige le port 80 du conteneur vers le port 80 de votre hôte (il ne doit bien sûr pas y avoir de serveur Web déjà en écoute sur ce port) puis on fait le lien entre le conteneur Varnish et le conteneur NGinx.

On vérifie que nos trois conteneurs sont lancés:

Et on teste notre serveur de cache Varnish:

Le "Via: 1.1 varnish" confirme que notre infra commence à avoir de la gueule :).

En aparté, on peut d'ailleurs juger de la puissance de Varnish en lançant un simple bench tout d'abord directement sur le serveur NGinx (donc en baille-passant Varnish):

Puis en faisant le même test en passant par Varnish:

Les chiffres parlent d'eux même...

Gestion des conteneurs

Un simple pull sur les images préalablement téléchargé s'occupera de leurs mises à jour:

Comme l'on reconstruit notre infrastructure à partir de ces images, il suffira ensuite d'arrêter et de relancer les conteneurs:

Note: Il est également possible de créer des images maison (avec commit + tag) et de les relancer. Cette phase peut être utile dans le cadre d'un déploiement d'une infrastructure à une autre.

Orchestration de l'infrastructure

On vient de voir que le lancement d'une infrastructure basée sur Docker peut rapidement devenir compliqué. Les conteneurs doivent être lancés dans un certain ordre, avec des options spécifiques. Il est toujours possible de scripter les commandes du chapitre précédent ou plus simplement d'utiliser un outil d'orchestration.

Vagrant vous vient en tête ? Pourtant, ce n'est pas la solution que nous allons utiliser dans ce billet.

Nous allons nous tourner vers docker-compose (anciennement Fig). C'est un logiciel en ligne de commande permettant de d'écrire son infrastructure Docker à partir d'un fichier de configuration texte au format YAML.

L'installation de Fig se fait via la commande:

Note: pour d'autre méthode d'installation, consultez la documentation sur le site officiel.

Le fichier de configuration Fig correspondant à notre infrastructure est le suivant (à éditer dans le fichier $HOME/data/webstack/docker-compose.yml):

On lance ensuite notre infrastructure en une seule et unique commande:

Tout comme avec la ligne de commande Docker, on peut voir les conteneurs en cours d'exécution:

Les arrêter (sans les supprimer):

Les relancer:

Les supprimer:

Je vous laisse découvrir les autres commandes de docker-compose sur le site officiel de la documentation.

Conclusion

Nous venons de créer les bases d'une infrastructure Web performante, facilement maintenable et évolutive. Il est ainsi facile d'y ajouter d'autres services comme une base de donnée (type MariaDB), un serveur sFTP (pour la mise à jour des pages Web) ou bien encore un outil d'analyse des logs (Varnish et NGinx).

Et de votre coté, avez-vous mis en place une infrastructure Docker pour votre serveur Web ? Si oui comment ?

Partagez votre expérience avec nous !