Accueil
Le blog tech de Nicolas Steinmetz (Time Series, IoT, Web, Ops, Data)
docker compose
(au lieu de l’original docker-compose
coté en python)COPY --chmod
reduced the size of my container image by 35% : pour réduire la taille de vos images, plutôt que de faire un ADD ...
puis un RUN chmod ...
, faites directement un ADD/COPY --chmod
. Marche aussi avec --chown
.depends_on
a une syntaxe longue qui permet de définir une condition sur l'état du service dépendant : démarré (valeur par défaut de la version courte), "sain" (en fonction du résultat d'un healthcheck) ou "terminé avec succès" (si votre service dépend du résultat d'un job ou d'une tâche).tig
pas très intuitif/pratique, GitUI pourrait vous plaire. Prévu pour le terminal, il permet de se ballader facilement dans votre historique git & co. L' outil en codé en Rust.argparse
est assez connu et peut être aussi Fire, c'est l'occasion de découvrir Click (par l'équipe derrière Flask & co et à ne pas confondre avec clikt en Kotlin), Typer (par le fondateur de FastAPI).compose
devrait devenir une sous-commande officiel de la CLI Docker ; on pourra alors faire docker compose up -d
jq
pour les données relationelles. Du SQL ou des fichiers Excel/CSV/JOSN/XML en entrée et les mêmes formats en sortie (et un peu plus).vector top
, la source internal_logs
et l'API GraphQL. Un guide de mise à jour vers la nouvelle syntaxe est disponible.compose
devrait devenir une sous-commande officiel de la CLI Docker ; on pourra alors faire docker compose up -d
jq
pour les données relationelles. Du SQL ou des fichiers Excel/CSV/JOSN/XML en entrée et les mêmes formats en sortie (et un peu plus).vector top
, la source internal_logs
et l'API GraphQL. Un guide de mise à jour vers la nouvelle syntaxe est disponible.monitor.stateChanges()
et monitor.stateChangesOnly()
.Si vous êtes en manque de news, vous pouvez aller consulter (et vous abonner) aux brèves du BigData Hebdo
libssh-dev(el)
suivant votre distribution pour pouvoir installer ansible-pylibssh
. Mes premiers essais ne notent pas une amélioration sensible des performances... à voir sur d'autres machines et dans la durée...sudoers
est présent sur le système (en général: /etc/sudoers
). Les versions 1.8.2 à 1.8.31 et 1.9.0 à 1.9.5-p1 sont impactées, il faut passer en version 1.9.5-p2.docker-compose.yml
) pour la rendre plus "cloud native" et plus générique avec une extension au provider cloud d'une part et d'autre part à des solutions comme kubernetes ou Amazone ECS par ex.DEV*
soient surprovisionnés et qu'il faille envisager des profils GP*
pour avoir des performances correctes. L'offre est du coup moins compétitive en termes de prix pour des petits clusters.jQuery.htmlFilter
pour toutes les versions inférieures à 3.5.0 ; il est vivement encouragé de mettre à jour vos sites. Pour le reste, je vous renvoie à la lecture de l'article.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-fpm security.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 nginx php-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.