Le blog tech de Nicolas Steinmetz (Time Series, IoT, Web, Ops, Data)
npm
pour gérer le cycle de release de son application (génération du changelog, gestion des numéros de versions, création des tags git, etc).Pour un projet en cours de finalisation, j’ai utilisé le CMS Grav et j’ai décidé tant pour mon développement en local que pour l’environnement de production de déployer cela sous la forme de container docker et d’utiliser Traefik comme reverse-proxy et m’appuyer notamment sur son support natif et dynamique des containers docker.
J’avais initialement publié une image nsteinmetz/grav basée sur l’image officielle PHP:apache mais elle ne me convenait pas totalement :
PHP:apache
est basée sur Debian (taille plus importante qu’une image basée sur Alpine)Pour mémoire et à toutes fins utiles, le Dockerfile
- nsteinmetz/docker-grav :
FROM php:7-apache
ADD https://github.com/getgrav/grav/releases/download/1.1.8/grav-admin-v1.1.8.zip /tmp/grav-admin-v1.1.8.zip
RUN apt update && \
apt upgrade -y && \
apt install -y \
unzip \
libfreetype6-dev \
libjpeg62-turbo-dev \
libmcrypt-dev \
libpng12-dev \
pkg-config && \
unzip /tmp/grav-admin-v1.1.8.zip -d /tmp/ && \
mv /tmp/grav-admin/* /var/www/html/ && \
mv /tmp/grav-admin/.htaccess /var/www/html/ && \
chown www-data:www-data -R /var/www/html && \
docker-php-ext-install -j$(nproc) mcrypt && \
docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && \
docker-php-ext-install -j$(nproc) gd &&\
docker-php-ext-install -j$(nproc) zip &&\
a2enmod rewrite && \
rm -rf /var/lib/apt/lists/*
COPY php.conf /etc/apache2/conf-enabled/
COPY server-signature.conf /etc/apache2/conf-enabled/
Avec php.conf
:
php_admin_flag display_errors off
php_admin_flag expose_php off
php_admin_value post_max_size "40M"
php_admin_value upload_max_filesize "40M"
et server-signature.conf
:
ServerSignature Off
ServerTokens Prod
Les améliorations apportées :
docker-compose
pour avoir plus de flexibilité au moment de démarrer les containers en fonction des projets utilisant Grav,Cela donne :
├── app
│ ├── Dockerfile
│ ├── security.conf
│ └── uploads.conf
└── web
│ ├── Dockerfile
│ └── php-fpm.conf
├── docker-compose.yml
Petites précisions à ce niveau :
app
correspond au container php-fpmsecurity.conf
contient des options de sécurité PHP (ne pas exposer la version de PHP, ne pas afficher les erreurs, etc)uploads.conf
contient des options liés aux uploads PHP (taille max de fichiers, etc)web
est le container nginxphp-fpm.conf
est la configuration de mon virtualhost nginxOn a app/Dockerfile
:
FROM php:7-fpm-alpine
ADD https://github.com/getgrav/grav/releases/download/1.1.8/grav-admin-v1.1.8.zip /tmp/grav-admin-v1.1.8.zip
RUN apk update &&\
apk upgrade &&\
unzip /tmp/grav-admin-v1.1.8.zip -d /tmp/ && \
mv /tmp/grav-admin/* /var/www/html/ && \
mv /tmp/grav-admin/.htaccess /var/www/html/ && \
chown www-data:www-data -R /var/www/html &&\
apk add libjpeg libjpeg-turbo libjpeg-turbo-dev libpng libpng-dev freetype freetype-dev &&\
docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ && \
docker-php-ext-install mcrypt zip gd &&\
rm -rf /var/cache/apk/* &&\
rm -rf /tmp/grav-admin-v1.1.8.zip &&\
cd /var/www/html && ./bin/gpm update --no-interaction
COPY uploads.conf /usr/local/etc/php-fpm.d/uploads.conf
COPY security.conf /usr/local/etc/php-fpm.d/security.conf
VOLUME ["/var/www/html", "/var/www/html/assets", "/var/www/html/backup", "/var/www/html/cache", "/var/www/html/images", "/var/www/html/logs", "/var/www/html/tmp"]
A noter, même s’il n’y a rien d’exceptionnel :
RUN
va mettre à jour les plugins inclus nativement dans Gravbackups
et tmp
.Ensuite, app/security.conf
:
[www]
php_admin_flag[display_errors] = off
php_admin_flag[expose_php] = off
et app/uploads.conf
:
[www]
php_admin_value[post_max_size] = "40M"
php_admin_value[upload_max_filesize] = "40M"
J’ai fait le choix de surcharger l’instance php-fpm par défaut (ie: www
) et j’utilise php_admin_*
afin d’interdire toute surcharge de ces variables par l’application.
web/Dockerfile
:
FROM nginx:stable-alpine
RUN rm /etc/nginx/conf.d/default.conf
ADD php-fpm.conf /etc/nginx/conf.d/php-fpm.conf
Je supprime le virtualhost fourni par défaut et je fournis le mien en lieu et place.
web/php-fpm.conf
:
server {
listen 80;
server_name _;
charset utf-8;
root /var/www/html/;
index index.html index.php;
# Uploads to 100M
client_max_body_size 100m;
location / {
try_files $uri $uri/ /index.php?_url=$uri;
}
## Begin - Security
# don't send the nginx version number in error pages and Server header
server_tokens off;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Frame-Options SAMEORIGIN;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' data: ; style-src 'self' 'unsafe-inline'; font-src 'self'; child-src; object-src 'none'";
# deny all direct access for these folders
location ~* /(.git|cache|bin|logs|backup|tests)/.*$ { return 403; }
# deny running scripts inside core system folders
location ~* /(system|vendor)/.*\.(txt|xml|md|html|yaml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
# deny running scripts inside user folder
location ~* /user/.*\.(txt|md|yaml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
# deny access to specific files in the root folder
location ~ /(LICENSE.txt|composer.lock|composer.json|nginx.conf|web.config|htaccess.txt|\.htaccess) { return 403; }
## End - Security
## Begin - PHP
location ~ \.php$ {
fastcgi_pass app:9000;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
}
## End - PHP
location ~* ^.+\.(ico|js|gif|jpg|jpeg|png|bmp)$ {
expires 30d;
}
}
A noter :
X-Content-Type-Options
, X-XSS-Protection
, X-Frame-Options
et Content-Security-Policy
)app:9000
, car app
est le nom de mon service.version: '2'
services:
web:
build: ./web/
depends_on:
- app
volumes_from:
- app
ports:
- "80:80"
app:
build: ./app/
volumes:
- ./user:/var/www/html/user
- ./assets:/var/www/html/assets
- ./backup:/var/www/html/backup
- ./cache:/var/www/html/cache
- ./images:/var/www/html/images
- ./logs:/var/www/html/logs
- ./tmp:/var/www/html/tmp
Pour builder puis lancer vos containers en mode daemon :
docker-compse up -d --build
Traefik est un reverse-proxy moderne et il a le bon goût de s’interfacer notamment avec l’API de Docker. On peut alors déclarer dynamiquement nos containers à traefik et celui-ci les prend en compte dynamiquement.
Pour mon poste en local, j’ai décidé de lancer Traefik de la façon suivante :
docker run -d -v /dev/null:/traefik.toml -v /var/run/docker.sock:/var/run/docker.sock -p 80:80 -p 443:443 -p 8080:8080 --name traefik traefik:camembert --web --docker --docker.domain=docker.localhost --logLevel=DEBUG
Pour les détails :
-v /dev/null:/traefik.toml
permet de démarrer Traefik sans fichier de configuration-v /var/run/docker.sock:/var/run/docker.sock
permert au container Traefik de communiquer avec Docker et son API--web
: lance l’interface web (dashboard)--docker --docker.domain=docker.localhost
: active le support de docker et fourni un domaine par défaut à tous les containers docker.--logLevel=DEBUG
: permet d’avoir des logs verbeux si problème via docker logs -f traefik
Il “suffit” d’indiquer des labels à vos services :
version: '2'
services:
web:
build: ./web/
depends_on:
- app
volumes_from:
- app
labels:
- "traefik.backend=grav-project"
- "traefik.frontend.rule=Host:project.grav"
- "traefik.port=80"
- "traefik.protocol=http"
- "traefik.frontend.entryPoints=http"
- "traefik.docker.network=nom_du_reseau"
app:
build: ./app/
volumes:
- ./user:/var/www/html/user
- ./assets:/var/www/html/assets
- ./backup:/var/www/html/backup
- ./cache:/var/www/html/cache
- ./images:/var/www/html/images
- ./logs:/var/www/html/logs
- ./tmp:/var/www/html/tmp
labels:
- "traefik.enable=false"
Explications pour le service web
:
entryPoint
indique que je vais me connecter à Traefik sur le port 80 pour me connecter ensuite à mon conteneur.docker network ls
.En gros on a :
Vous <=> Votre navigateur <=> Traefik Frontend (
Host:project.grav
(frontend.rule) + protocolehttp
(EntryPoint)) <=> Traefik Backend (grav-project
+ port80
+ protocolehttp
) <=> ContainerWeb
Explications pour le service app
:
Il ne me reste plus qu’à :
/etc/hosts
pour project.grav
http://project.grav/
Sauf que cela ne marche pas à ce stade (si vous utilisez traefik dans un container docker). En effet, lors du docker-compose up -d
, docker a créé un réseau pour votre application. Or par défaut, Traefik n’y a pas accès. Il vous faut faire:
docker network connect <nom_du_reseau> <container-traefik>
Et ce coup-ci, votre site propulsé par Grav s’affiche.
Vous pouvez retrouver les fichiers et remonter vos commentaires sur cerenit/docker-grav
Une fois prochaine; je vous parlerais d’Ansible, Docker et Traefik pour déployer vos projets aisément.
docker-compose.yml
en fichiers Manifests Kubernetes. Un pivot intéressant si on considère que les développeurs vont utiliser Docker (et au mieux Swarm) pour leurs environnements de développement et en production éventuellement Swarm et ensuite vouloir migrer vers Kubernetes pour ses fonctionnalités plus évoluées.