Grav, Docker-Compose et Traefik


07/12/2016 docker docker-compose traefik grav

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.

Premiers pas…

J’avais initialement publié une image nsteinmetz/grav basée sur l’image officielle PHP:apache mais elle ne me convenait pas totalement :

  • Utilisation d’apache2 alors que j’ai basculé depuis longtemps sur nginx
  • L’image PHP:apache est basée sur Debian (taille plus importante qu’une image basée sur Alpine)
  • Pas d’utilisation des volumes (je n’ai pas pris le temps de les déclarer)

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

Améliorations

Les améliorations apportées :

  • Passage à docker-compose pour avoir plus de flexibilité au moment de démarrer les containers en fonction des projets utilisant Grav,
  • Passage à des images basées sur alpine (plus légères)
  • Passage à une architecture PHP-FPM + Nginx

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 nginx

Revue du container “app” (php-fpm)

On 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 :

  • Initialisation du container en récupérerant la version de Grav, gestion des dépendances et des permissions
  • La dernière ligne de RUN va mettre à jour les plugins inclus nativement dans Grav
  • Les volumes déclarés permettent de sortir du container toutes les zones non stateless : logs, cache, contenus, etc sur la base de la documentation officielle de Grav ; j’ai ajouté néanmoins les répertoire backups 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.

Revue du container “web” (nginx)

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 :

  • Le fichier est inspiré de la configuration officielle nginx donnée pour Grav
  • Ajout de directives de sécurité (X-Content-Type-Options, X-XSS-Protection, X-Frame-Options et Content-Security-Policy)
  • Interdiction d’accès à des répertoires
  • Déclaration de php-fpm où le point d’attention est juste d’indiquer app:9000, car app est le nom de mon service.
  • Mise en cache des images

Et un docker-compose.yml pour enrober le tout

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 en reverse-proxy

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.

Démarrer Traefik

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
  • J’expose les ports 80, 443 et 8080. Le dernier permettra d’accéder au dashboard fourni nativement par Traefik
  • --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

Rendre son application “traefik-aware”

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 :

  • je lui indique un nom de backend (peu importe le nom) qui sert juste à Traefik pour “nommer” votre container
  • l’url (de frontend) sur laquelle Traefik devra réagir pour vous interfacer avec votre container (ici en tapant http://project.grav/ dans mon navigateur, je dois arriver sur mon container)
  • le protocol et le port indiquent que je me connecte sur le port 80 de mon container avec le protocole http
  • l'entryPoint indique que je vais me connecter à Traefik sur le port 80 pour me connecter ensuite à mon conteneur.
  • le nom du réseau que vous a créé docker lors de l’initialisation du projet. Pour le récupérer, faire un docker network ls.

En gros on a :

Vous <=> Votre navigateur <=> Traefik Frontend (Host:project.grav (frontend.rule) + protocole http (EntryPoint)) <=> Traefik Backend (grav-project + port 80 + protocole http) <=> Container Web

Explications pour le service app :

  • Je ne souhaite pas exposer ce service dans Traefik, je désactive alors ce service (par défault, il y a une auto-découverte de tous les services).

Pour finir

Il ne me reste plus qu’à :

  • Faire une entrée DNS ou éditer /etc/hosts pour project.grav
  • Ouvrir votre navigateur sur 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.

Web, Ops & Data - Semaine 44


02/11/2016 ansible docker nomad fleet kubernetes swarm elasticsearch wagtail grav

Elasticsearch

  • Elastic Stack 5.0.0 Released : La version 5.0 de l’Elastic Stack (Elasticsearch, Kibana, Beats, Logtstash) vient de sortir. Concernant Elasticsearch, on notre l’arrive d’une ingest node pour mieux traiter vos données, des améliorations de performances et plein d’autres choses dont on a parlé ici précédemment.
  • Les leviers d’Elasticsearch pour le traitement des spécificités linguistiques : le billet présente les différentes capacités d’Elasticsearch pour analyser plus ou moins finement un texte en fonction de vos besoins. Assez bien documenté pour voir les comportements des différents filtres d’Elasticsearch.

Conteneurs

CMS

  • Wagtail 1.7: Elasticsearch 2, Smaller Images, CloudFront : Tout est dans le titre ou presque : support d’Elasticsearch 2, une plus grande flexibilité sur le degré de compression des images et le support du CDN Amazon CloudFront.
  • Grav 1.1.8 : le CMS Grav continue à améliorer sa version 1.1.x ; terminant un premier projet avec Grav, je suis à la fois séduit par son potentiel et sa flexibilité que frustré par quelques défauts de jeunesse (?). Cela fera l’objet d’un futur billet…

DevOps

Syndication

Restez informé(s) de notre actualité en vous abonnant au flux du blog (Atom)

Nuage de tags

kubernetes docker influxdb timeseries traefik ansible grafana kafka elasticsearch postgres warp10 python aws sécurité mysql redis terraform tick cassandra cloud helm ovh git swarm telegraf timescaledb docker-compose ptsm rancher résilience test chronograf flux gitlab log machine-learning prometheus architecture arm confluent dashboard devops iac ksql microservice monitoring s3 serverless spark angularjs api bilan cert-manager cncf container cérénit dns gcp graphql hashicorp ingress java javascript opensource operator optimisation perspective raspberrypi service-mesh sql ssh stream vscode warpscript windows csp documentation elastic flows gke hpkp influxace jenkins kafka-streams kapacitor kibana kubedb lambda lean licence maesh maintenance mariadb microsoft mobile nginx npm orientdb performance pipeline podman redhat rest rethinkdb reverse-proxy rook sauvegarde scaleway agile apm automatisation azure bash big-data bigdatahebdo ceph certificat ci/cd cli cluster containerd continous-delivery continous-integration cookie deployment diff fluxlang forecast framework gdpr gitlab-ci grav hsts http/3 https hypriot hébergement influxdata influxdays iot istio jq json k3s lets-encrypt linux load-balancer longhorn meetup molecule mongodb nosql nvidia openebs openssh ovhcloud percona php pip postgresql reaper registry replication rootless rpi rsyslog runc scale secrets société solr sre systemd timezone tls vault vector virtualenv vitess vue.js wagtail warpfleet yarn accessibilité acme akka alerte alibaba amazon-emr amqp anomalie anonymisation anthos apache-pulsar ara arima arrow audit bastion beam beat bounded-context branche brigade browser buildkit cahier-des-charges calico cassandra-reaper cd cdc cdk centos centralisation-de-logs certificats cgroups chart checklist chrome ci cilium cloud-init cloud-native cloud-storage clusterip cnab cni cockroachdb code codeurs-en-seine commit confluence conftest consul context continous-deployment conventional-commit coreos cors covid19 cqrs crash cri cron crontab csi csrf css curl d3.js daemonset data data-engineer data-pipelining data.gouv.fr datacenter dataviz date date-scientist ddd debezium debian delta deprek8 desktop devoxx dig distributed-systems dive docker-app docker-hub docker-registry docker-swarm dockershim documentdb dog dokcer données-personnelles draft drop-in duration déploiement développement-du-site e-commerce ebs ec2 edge elassandra electron elk engineering entreprise ergonomie etcd event-sourcing faas facebook faisabilité falcor feature-policy fedora feed filebeat firebase firefox fish flash flask fleet flink fluentd formation foundation frontend fsync fullstack github gitignore glacier glowroot google google-cloud-next gpg gpu grid géospatial hacker hadoop haproxy harbor hdfs header html html5 http hue ia iaac ibm immutable incident index influxcloud infrastructure-as-code ingénierie inspec jquery jwt k3d k8s k9s kotlin kubeadm kubecon kubectl label laravel letsencrypt libssh linky liste-de-diffusion loadbalancer logstash logstatsh loi mailing-list management maturité mesh mesos message metallb micro-service mot-de-passe mqtt multi-cloud médecine métrique network newsletter nodeport nomad null object-storage observability observabilité opa opendata openhab openmetrics openshit openstack openweb over-engineering packaging pandas parquet partiql password persistent-volume-claim pipenv pod portainer portworx prediction prescience production promql ptyhon publicité pubsub pulsar push pyenv pérénnité qualité quasardb quay questdb queue quic ram rambleed raml react recaptcha recherche redistimeseries reindex reinvent reliability responsive revocation revue-de-code rgpd rhel rkt rolespec root rpo rto rust rwd safe-harbor scalabilité scanner schema scp sdk search select serverless-architecture service service-account service-worker setuptools sftp sha1 sharding shell shipyard sidecar souveraineté-numérique spinnaker spécifications sri ssh-agent ssl stabilité stash statistique storage sudo superset suse sympa syslog-ng sérénité terracost terrascan test-unitaire tidb tiers timer timescale timestream training travail tsl ubuntu unikernel unit ux vendredi victoria-metrics vie-privée virtualbox virtualisation vm vnc volume voxxeddays vpc warpstudio web yaml yq yubikey