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) :
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 :
- Configuration du module deflate.
- Paramétrage des ETags.
- Mise en place des entêtes expires.
- Quelques entêtes et configurations minimales pour la sécurité.
- Un répertoire pour les virtual host.
- Et le support du SSL avec une configuration basée sur l’excellent outil de Mozilla.
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.
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 :
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
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.