Le blog tech de Nicolas Steinmetz (Time Series, IoT, Web, Ops, Data)
Rendez-vous le 17 décembre prochain à la troisième édition du Paris Time Series Meetup consacré à TSL (billet introductif à TSL : TSL: a developer-friendly Time Series query language for all our metrics) et le module RedisTimeSeries qui apporte des fonctionnalités et des structures Time Seriies à Redis.
matrix
qui va permettre de faire la même action avec des configurations différentes plutôt que d’avoir un jenkinsfile pour chaque option/déclinaison du job. Le parallelisme semble supporté par défaut et un système d’inclusion/exclusion permet de mieux définir la combinaison des possibles. Dans l’exemple donné qui croise des systèmes d’exploitation et des navigateurs, cela permet par ex de ne pas lancer le job utilisant Micrsoft Edge sous Linux (même si…).Faîtes-vous plaisir et écouter le podcast Artisan Développeur - dans des formats de 10mn environ, un sujet autour de l’agilité, des tests, du TDD, de la responsabilité des développeurs, de SaFE, et de tout ce qui fait partie de notre quotidien de développeurs sont abordés. Depuis quelques épisodes, cela se fait en duo avec d’autres personnes (comme JP Lambert) ce qui rend les échanges encore plus intéressants. Vous retrouvez le podcast sur Soundcloud, Pocketcasts, etc.
git --force
est déconseillée si ce n’est proscrite, sa variante git --force-with-lease
est plus intéressante et permet d’éviter d’écraser le travail de vos camarades alors que vous pensiez juste faire un push en force sur une branche distante suite à un rebase local.Lorsque l’on déploie une même application dans plusieurs contextes via docker-compose
, il est intéressant d’utiliser le COMPOSE_PROJECT_NAME qui permet de donner un préfixe à vos réseaux et containers docker a minima.
L’inconvénient est qu’il faut ajouter à vos commandes un -p <project_name>
:
docker-compose -p instancea build --pull
docker-compose -p instancea up -d
docker-compose -p instancea logs -f
docker-compose -p instancea stop <service>
docker-compose -p instancea down
...
Ainsi, vos conteneurs seront nommés instancea_<service name>_<occurence>
et votre réseau instancea_<network name>
.
Mais il est possible d’aller plus loin avec les fichiers d’environnement .env
.
Dans votre fichier .env
à la racine de votre dossier où se trouve votre fichier docker-compose.yml
, définissez la/les variable(s) dont vous avez besoin. Ici, nous allons nous limiter à COMPOSE_PROJET_NAME
mais ne vous privez pas.
COMPOSE_PROJECT_NAME=instancea
A partir de ce moment-là, plus besoin de précier l’argument -p <project name>
, vos commandes redeviennent :
docker-compose build --pull
docker-compose up -d
docker-compose logs -f
docker-compose stop <service>
docker-compose down
...
… et pour autant, vos réseaux et containers ont le bon préfix car le fichier .env
est lu à l’exécution de la commande docker-compose
avant de parser docker-compose.yml
.
On peut aller encore plus loin en utilisant ce COMPOSE_PROJECT_NAME
dans le taggage des images d’un container par ex ou
version: '3'
services:
nginx:
build:
context: ./nginx/
image: "registry.mycompany.com/nginx:${COMPOSE_PROJECT_NAME}"
Lors de la phase de build, l’image sera tagguée avec le nom passé au projet compose. Ensuite, vous pouvez poussez sur la registry de votre entreprise puis déployer cette version sur votre cluster Swarm par ex.
A noter justement une limitation actuelle de docker stack deploy <stack name> -c docker-compose.yml
qui ne lit pas le fichier .env
en amont et donc COMPOSE_PROJECT_NAME
reste vide lors de la lecture du fichier docker-compose.yml
.
Une solution possible est par ex dans le script (simplifié) de déploiement :
cd $BUILDDIR/compose/
source .env
# Remplace la variable COMPOSE_PROJECT_NAME par sa valeur
sed -i -e "s/\${COMPOSE_PROJECT_NAME}/${COMPOSE_PROJECT_NAME}/g" docker-compose.yml
docker stack deploy ${COMPOSE_PROJECT_NAME} -c docker-compose.yml
Et voilà !
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.