*

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 timeseries influxdb warp10 traefik grafana ansible elasticsearch kafka postgres python aws sécurité terraform mysql redis telegraf ovh tick cassandra chronograf cloud dashboard docker-compose git hashicorp helm timescaledb flux ptsm swarm vector kapacitor podman rancher résilience test gcp gitlab influxdata log machine-learning monitoring prometheus s3 spark timescale vscode architecture arm comptabilité confluent devops gitlab-ci iac java ksql microservice nomad postgresql raspberrypi serverless service-mesh sql angularjs api bilan cert-manager cncf consul container cérénit dns flows gke graphql ingress javascript npm opensource operator optimisation perspective pipeline rook scaleway ssh stream vault warpscript windows cli containerd csp discovery documentation elastic forecast geospatial golang hpkp influxace iot jenkins kafka-streams kibana kubedb lambda lean licence maesh maintenance mariadb microsoft mobile nginx orientdb performance quasardb redhat registry rest rethinkdb reverse-proxy sauvegarde warpstudio agile anomalie apm arima audit automatisation azure bash big-data bigdatahebdo ceph certificat challenge ci/cd cluster continous-delivery continous-integration cookie data datatask dataviz dbt deployment diff facebook fec fluxlang framework gdpr grav hsts http/3 https hypriot hébergement ia influxdays istio jq json k3s lets-encrypt linux load-balancer longhorn meetup metabase molecule mongodb nosql nvidia openebs openssh ovhcloud percona php pip questdb reaper replication rootless rpi rsyslog runc scale secrets société solr sre systemd tempo timezone tls virtualenv vitess vue.js wagtail warpfleet yarn accessibilité acme adoptopenjdk agpl akka alerte alertes alerting alibaba amazon-emr amqp anonymisation anthos apache-pulsar ara arrow artefact automation automl banque bastion beam beat bi bme680 bootstrap bounded-context branche brigade browser buildah buildkit cahier-des-charges calico cassandra-reaper cd cdc cdk centos centralisation-de-logs certificats cgroups chart check checklist chrome ci cilium circuitpython clever-cloud clickhouse cloud-init cloud-native cloud-storage cloudflare clusterip cnab cni co2 cockroachdb code codeurs-en-seine commit confluence conftest consul-connect context continous-deployment conventional-commit coreos cors covid19 cqrs crash cri cron crontab csi csrf css curl d3.js daemonset data-engineer data-pipelining data.gouv.fr databricks datacenter 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 euclidia event-sourcing faas faisabilité falco falcor feature-policy fedora feed filebeat firebase firefox fish flash flask fleet flink fluentd formation foundation frenchtech frontend fsync fullstack git-filter-repo github gitignore glacier glowroot go google google-cloud-next gpg gpu grid géospatial hacker hadoop haproxy harbor hdfs header holt-winters html html5 http hue iaac ibm immutable incident index indluxdata influxcloud infrastructure-as-code ingénierie inspec jquery jvm jwt k3d k6 k8s k9s kaniko katz kotlin kubeadm kubecon kubectl label laravel leap-second lens letsencrypt libssh linky linter liste-de-diffusion lmap loadbalancer logstash logstatsh loi loki lstm mailing-list management maturité mesh mesos message metallb micro-service minio mot-de-passe mqtt multi-cloud médecine métrique n8n network newsletter nodejs nodeport notebook notifications nrtsearch null object-storage observability observabilité opa opendata openhab openmetrics openshit openstack openweb opnsense over-engineering packaging pandas parquet partiql password persistent-volume-claim pico pipenv pivot pod portainer portworx prediction prescience production promql prophet prévision psp ptyhon publicité pubsub pulsar push pyenv pérénnité qualité quay queue quic ram rambleed raml react readme recaptcha recherche redistimeseries reindex reinvent reliability remote-execution repository responsive retention-policy revocation revue-de-code rexec rgpd rhel rkt rolespec root rpo rto rust rwd résultat safe-harbor sarima scalabilité scanner schema scp sdk search select serverless-architecture service service-account service-worker setuptools sftp sha1 shard shard-duration shard-group sharding shell shipyard sidecar singer souveraineté-numérique spectre spinnaker spécifications sqlite sri ssh-agent ssl stabilité stash statistique storage sudo superset suse sympa sysdig syslog-ng sérénité task template terracost terrascan test-unitaire tidb tiers time timecale timer timestream tinygo training transformation travail trésorerie tsfr tsl ubuntu unikernel unit ux velero vendredi victoria-metrics vie-privée virtualbox virtualisation vm vnc volume voxxeddays vpc wasm web wireguard workflow yaml yield yq yubikey