Docker pour ma stack LAMP

J’avais déjà décrit ma précédente stack LAMP sous Docker, mais, à nouveau serveur, nouvelle architecture !

Tout d’abord posons le décor : un serveur Scaleway VC1M avec dessus, ce blog WordPress et un GitLab (que je ne décrirais pas). On s’attend donc à une stack avec un serveur HTTP, un daemon PHP-FPM et une base de données.

Pour illustrer le tout rien ne vaut un schéma (on commence par la version simplifiée) :

Schéma d'architecture Docker simplifié
Schéma d’architecture simplifié de la stack LAMP sous Docker.

Pour ce qui est de docker, je suis parti de la version officiellement disponible dans CentOS 7 avec comme système de stockage aufs.

docker info
Containers: 6
 Running: 6
 Paused: 0
 Stopped: 0
Images: 76
Server Version: 1.13.1
Storage Driver: aufs
 Root Dir: /var/lib/docker/aufs
 Backing Filesystem: extfs
 Dirs: 83
 Dirperm1 Supported: true
[..]
Kernel Version: 4.4.114-mainline-rev1
Operating System: CentOS Linux 7 (Core)
OSType: linux
Architecture: x86_64
Number of Docker Hooks: 3
CPUs: 4
Total Memory: 3.857 GiB
[...]

2 services (HTTPd & PHP-FPM) donc 2 containers

Pour faire un serveur HTTP/PHP, certains font un gros container contenant les 2 services. Personnellement, je préfère respecter la bonne pratique qui consiste à avoir 1 container pour 1 service. Comme d’habitude, j’ai fait le choix de ne pas mettre WordPress dans le container PHP + le container HTTPd. C’est une solution de facilité mais qui s’explique :

  • Pas besoin de mettre à jour 2 containers pour mettre à jour WordPress.
  • Possibilité de mettre à jour WordPress depuis Deployer.
  • Plus simple.

Du coup j’ai un volume partagé entre les 2 containers et que je peux trifouiller depuis mon serveur.

Serveur HTTP

Pour le serveur HTTP, et bien que je maitrise également nginx, je reste fidèle à Apache. De mon point de vue, il n’y a rien de plus puissant et une fois configuré on a quelque chose de tout aussi performant (je sens que je viens de lâcher un gros Troll…).

En ce qui concerne le container Docker, je dérive de la dernière version officielle disponible sur le store, mais en version Alpine, c’est plus léger et plus joueur que la version Debian.

À ceci j’embarque quelques optimisations telles que :

Sources de mon images Apache HTTP disponibles sur GitHub.

Serveur PHP-FPM

Là encore, je pars de la version officielle du store mais sous Alpine. Mon image a de particulier que lorsqu’on la build via docker-compose, on peut lui passer des paramètres. Ça me permet d’avoir la même image en dev et en prod mais avec XDebug seulement sur la prod.

  php:
    container_name: php
    image: llaumgui/php:7.2-fpm
    build:
      context: build/php-fpm/7.2/
      args:
        DOCKER_PHP_ENABLE_APCU: 'on'
        DOCKER_PHP_ENABLE_COMPOSER: 'on'
        DOCKER_PHP_ENABLE_LDAP: 'off'
        DOCKER_PHP_ENABLE_MEMCACHED: 'off'
        DOCKER_PHP_ENABLE_MONGODB: 'off'
        DOCKER_PHP_ENABLE_MYSQL: 'on'
        DOCKER_PHP_ENABLE_POSTGRESQL: 'off'
        DOCKER_PHP_ENABLE_REDIS: 'on'
        DOCKER_PHP_ENABLE_SYMFONY: 'off'
        DOCKER_PHP_ENABLE_XDEBUG: 'off'
	DOCKER_USER_UID: 1000
	DOCKER_USER_GID:1000
[..]

À noter que lorsque je déploie mon application, j’ai besoin de lancer des tâches PHP. Or, je n’ai pas PHP sur mon serveur mais dans mon container. Pour ceci j’utilise un simple script shell qui va vérifier que je suis bien dans le bon répertoire et lancer php-cli via le container. Pour éviter des problèmes de droits, le php-cli s’exécute sous un ID identique à mon utilisateur de déploiement (passé en paramètre dans mon docker-compose).

#!/bin/bash

PHP_PATH=/var/www
[[ ! "$PWD" =~ ${PHP_PATH} ]] && echo "Les commandes php ne sont utilisables que sous ${PHP_PATH}" && exit 1 

CONTAINER_NAME="php"
COMMAND="php $@"
PWD=$(pwd)

docker exec -u user -i ${CONTAINER_NAME} /bin/sh -c "cd ${PWD} && ${COMMAND}"

Côté réseau, le port 9000 est accessible à partir du serveur uniquement pour des purges de l’OPCode, pour ceci j’utilise Deployer qui se base sur cachetool.

Sources de mon image PHP-FPM disponibles sur GitHub.

Base de données

Pour la base de données, je suis resté simple : MariaDB 10.1 via l’image officielle.

En effet depuis qu’Oracle a racheté Sun et donc MySQL, je suis passé sous le fork officiel de l’équipe historique et supporté par la MariaDB Fondation. Là encore le serveur peut accéder au container via le port standard de MySQL et pour ceci je passe par le client mycli plus moderne que le client historique.

Les petits trucs en plus

Un serveur Redis

Pour améliorer les performances de WordPress, je mets le cache de WordPress en RAM. Pour ceci j’utilise Redis via l’image officielle. Du coup PHP peut accéder à Redis sur le port standard.

Un GitLab

Comme expliqué en préambule j’ai un GitLab perso. J’utilise l’image docker officielle qui, vu le nombre de services embarqués dans 1 même container est tout sauf respectueuse des guidelines. Pour rappel et comme évoqué précédemment c’est à cause de GitLab que je ne suis pas sur un serveur CV1S.

Et pour administrer Docker ?

Pour administrer tout ceci, je fais dans le simple, docker et docker-compose en ligne de commande, mais également ctop qui est un très bon TUI.

ctop en action

L’envoi d’email

J’ai fait le choix de ne pas avoir de container pour l’envoi des mails. En effet, que ce soit mon serveur ou mes containers, tout ce petit monde à besoin d’envoyer des mails. Du coup j’ai un postfix directement installé sur le serveur. Il est configuré pour permettre à Docker d’envoyer des mails et j’ai également une règle firewalld pour ouvrir le serveur SMTP au réseau docker :

firewall-cmd --permanent --zone=public --add-rich-rule='
  rule family="ipv4"
  source address="172.18.0.0/16"
  port protocol="tcp" port="25" accept'

Ensuite, j’ai mis un alias DNS vers mon serveur mail dans mes containers (vous pourrez le voir dans le paragraphe suivant).

Le fichier docker-compose.yml

Au final, l’architecture est la suivante :

Schéma d'architecture Docker
Schéma d’architecture complet de la stack LAMP sous Docker.

Et toute cette architecture est portée par une seule configuration, le docker-compose.yml qui permet d’orchestrer une architecture multi-containers. C’est Compose qui va gérer les volumes, le réseau Docker et les échanges entre les différents containers.

version: '2.1'

################################################################### All services
services:
  httpd:
    container_name: httpd
    image: llaumgui/httpd24
    build:
      context: build/httpd/2.4/
    restart: always
    volumes:
     - /etc/localtime:/etc/localtime:ro
     - /docker/volumes/www:/var/www/
     - /docker/conf/httpd/vhost.d:/usr/local/apache2/conf/vhost.d:ro
     - /docker/conf/httpd/ssl:/usr/local/apache2/ssl:ro
    ports:
     - "80:80"
     - "443:443"

  php:
    container_name: php
    image: llaumgui/php:7.2-fpm
    build:
      context: build/php-fpm/7.2/
      args:
        DOCKER_PHP_ENABLE_APCU: 'on'
        DOCKER_PHP_ENABLE_COMPOSER: 'on'
        DOCKER_PHP_ENABLE_LDAP: 'off'
        DOCKER_PHP_ENABLE_MEMCACHED: 'off'
        DOCKER_PHP_ENABLE_MONGODB: 'off'
        DOCKER_PHP_ENABLE_MYSQL: 'on'
        DOCKER_PHP_ENABLE_POSTGRESQL: 'off'
        DOCKER_PHP_ENABLE_REDIS: 'on'
        DOCKER_PHP_ENABLE_SYMFONY: 'off'
        DOCKER_PHP_ENABLE_XDEBUG: 'off'
        DOCKER_USER_UID: 1001
        DOCKER_USER_GID: 1001
    restart: always
    volumes:
     - /etc/localtime:/etc/localtime:ro
     - /docker/volumes/www:/var/www/
    expose:
     - 9000
    ports:
     - "127.0.0.1:9000:9000"
    depends_on:
     - httpd
     - mariadb
     - redis
    links:
     - mariadb:database
    extra_hosts:
     - "mailserver:172.18.0.1"

  mariadb:
    container_name: mariadb
    image: mariadb:10.1
    restart: always
    env_file:
     - /docker/conf/mariadb.env
    volumes:
     - /etc/localtime:/etc/localtime:ro
     - /docker/volumes/mariadb:/var/lib/mysql
     - /docker/volumes/mysqldump:/mysqldump
    expose:
     - 3306
    ports:
     - "127.0.0.1:3306:3306"

  redis:
    container_name: redis
    image: redis:4-alpine
    restart: always
    volumes:
     - /etc/localtime:/etc/localtime:ro
    expose:
     - 6379

Commentaires

Breizh

De Breizh le 25 avril 2018

Quel est l'intérêt de tout mettre dans Docker pour ce cas précis ? Si je remplace « container » par « service » dans ton image, et j'obtiens exactement la même chose, à priori.

Guillaume Kulakowski

De Guillaume Kulakowski le 25 avril 2018

Tout a fait... Mais si tu veux les dernières versions des middleware, soit tu compiles toi même, soit tu te limite à la version dispo sur ta distro, soit tu passes par des backport.
Là, une migration de version de PHP est indolore car je déploie une container PHP en version supérieur à côté et je switch.
Après il y a aussi l'isolation, n'oublions pas que le concept de Docker et de container n'est pas nouveau, sur BSD on appelait ça Jail (il y a plus de 10ans !).
Pour finir, la force de Docker est le côté shipable, ma configuration est dans le container, c'est auto-porteur.

Breizh

De Breizh le 25 avril 2018

Effectivement, pour la version, côté pro c’est du compilé (avec génération de paquet debian qu’on déploit sur les serveurs, et fait de telle façon que plusieurs versions de PHP peuvent cohabiter), et côté perso, ma distro propose les dernières versions (Arch Linux powa).

Pour la migration de version ben… pour le pro on change un chiffre dans la conf, pour le perso je n’ai pas ce problème (pas de site dynamique actuellement).

Pour le côté container, j’ai toujours été sceptique. À partir du moment où tu as une version maintenue, tu ne risques que la 0-day, et Docker n’en est pas à l’abri.

Quant au shipable, pas convaincu. La conf c’est juste quelques fichiers ou dossier à copier / déployer.

Donc je vois les avantages et intérêts, merci de ta réponse. Après je n’en ai pas besoin, donc je vais rester sur ma config perso ^^

Flunch

De Flunch le 25 avril 2018

Je me demandais en terme de maintenance et de patchs de sécurité comment ça se passe ?

Avec une installation classique (debian ou autre) on a en général juste à faire un « apt-get upgrade » qui peut même s’automatiser pour les seuls patchs de sécurité.

Comment ça se passe quand chaque service est dans son conteneur ?

Guillaume Kulakowski

De Guillaume Kulakowski le 25 avril 2018

En terme de maintenance c'est plus chiant, on va pas se mentir. Tu dois reconstruire tes images mais au final c'est pas pire que de tout compiler.

Après le concept de package auto-porteur est très à la mode, alors oui, avec de l'ansible tu peux reconstruire une infrastructure... On va dire que c'est des méthodes complémentaires qui offrent un large choix pour monter une archi. Personnellement j'ai adopté docker à un moment où je voulais la dernière version de PHP sur ma CentOS est que les backports SCL ne me les offrait pas encore.

Étienne BERSAC

De Étienne BERSAC le 25 avril 2018

Salut,

J’ai aussi opté pour Docker pour mon serveur. J’ai comme ça put avoir en concurrence différente version de Python, Node et PHP (y compris 5.6!!) sans m’arracher les cheveux. Et là mise-à-jour est tellement facile.

Résultat, j’ai opté pour CoreOS pour faire tourner ça. Je ne m’occupe plus du système, mais que des app.

Guillaume Kulakowski

De Guillaume Kulakowski le 25 avril 2018

Un autre avantage comme tu viens d'évoquer (PHP 5.6 étant presque EOL), c'est le maintien et l'isolation de stack legacy.

Biloute

De Biloute le 25 avril 2018

Salut,

Merci pour cet article, cependant j’aurais quelques remarques.

Ça fait plusieurs fois que tu mentionnes gitlab au sein de docker avec des améliorations pour la mémoire, cependant il n’apparait dans les fichiers docker-compose. Serait-il possible de nous partager ces modifications et recommandations ?

Tu ne parles à aucun moment des sauvegardes, pourrais-tu détailler un peu plus cette partie ?

Bonne journée,

Guillaume Kulakowski

De Guillaume Kulakowski le 25 avril 2018

Ça fait plusieurs fois que tu mentionnes gitlab au sein de docker avec des améliorations pour la mémoire, cependant il n’apparait dans les fichiers docker-compose. Serait-il possible de nous partager ces modifications et recommandations ?

C'est pas dans docker-compose, c'est dans la conf de GitLab. En gros le container GitLab embarque un NGinx, un PostgreSQL, un Redis et encore d'autres services.

J'avais évoqué le sujet ici. En gros j'ai fait le ménage et j'ai appliqué les recommandations du post sur StackOverflow. Si tu veux, j'essaierais de faire un diff de ma configuration pour voir ce que j'ai modifié ?

Tu ne parles à aucun moment des sauvegardes, pourrais-tu détailler un peu plus cette partie ?

Comme tu peux voir, j'utilise les volumes de docker, du coup je fais un gros backup de /docker avec backup-manager.

Biloute

De Biloute le 25 avril 2018

Si tu veux, j’essaierais de faire un diff de ma configuration pour voir ce que j’ai modifié ?

Ça m’intéresserais et je pense que d’autres aussi. Il y a une raison particulière pour utiliser le fichier de configuration lorsqu’il est possible d’utiliser la variable d’environnement GITLAB_OMNIBUS_CONFIG (cf: https://docs.gitlab.com/omnibus/docker/#pre-configure-docker-container) ?

Comme tu peux voir, j’utilise les volumes de docker, du coup je fais un gros backup de /docker avec backup-manager.

Est-il nécessaire d’arrêter tous les différents containers avant de sauvegarder ou backup-manager effectue son backup à la volée ?

Guillaume Kulakowski

De Guillaume Kulakowski le 25 avril 2018

Je vais me penchez sur cette variable, mais comme au final le volume de configuration est persisté dans leur exemple, je ne vois pas trop l'avantage... Faut voir les limitations.

Pour les backups, j'ai pas un volumes de données qui nécessite de couper les services. Après, un snapshot LVM de la partition est aussi possible.

Denis

De Denis le 25 avril 2018

Quel est l'intérêt, Guillaume, de Redis par rapport au module Apache socache avec memcache ? Je ne l'ai jamais mis en place.

Et autre question à 10 sous : c'est quoi l'intérêt de Docker par rapport à LXC sur lequel Docker s'appuie par ailleurs ? J'avais tâté, il y a quelques temps, du docker. Il faudrait peut-être que je m'y remette.

Guillaume Kulakowski

De Guillaume Kulakowski le 25 avril 2018

Disons que Redis VS Memcached c'est un peu comme Apache VS Ngnix... Après l'un est persistant et pas l'autre...
Pour LXC, c'est bien, c'est juste l’ancêtre de Docker...

Denis

De Denis le 25 avril 2018

Pour la persistence, on peut utiliser mod_file_cache.

Je vais jeter un coup d'oeil à nouveau à Docker.

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. Apprenez comment les données de vos commentaires sont utilisées.