Træfik : un proxy inverse pour les gouverner tous 👁 (vos conteneurs)
Pour ce premier article d'une série dédiée à la mise en place d'un serveur à l'aide de conteneurs Docker, il est naturel d'aborder la question par ce qui constitue son épine dorsale : le reverse proxy.
Introduction
Quel différence entre un bon vieux Apache / Nginx me dirait vous ? Après tout, ça fonctionne, et vous avez passé tant de tant à triturer vos configurations pour ne pas tout casser en adoptant un des derniers projet open-source à la mode. If it works, don't fix it, n'est-ce pas ?
Déjà, nous verrons dans cet article que loin de remiser les vénérables,
Træfik les complémente.
Chaque outil se concentrera désormais sur ce qu'il
maitrise le mieux : un reverse proxy automatisé et centralisé d'une part
(Træfik) et nos serveurs HTTP de l'autre (Apache, Nginx, Node + Express,
Django…).
Aussi, ce serait négliger le plus grand attrait de Træfik à mes yeux : son intégration symbiotique avec la plate-forme de conteneurs Docker.
Ses cas d'usages sont nombreux, mais d'emblée nous pouvons en identifier les principaux :
- Fédération de vos services sur un ou des domaines / sous-domaines.
- Génération automatique de vos certificats SSL au fil de l'eau.
- Équilibrage de la charge sur différents instances dans une grappe de services.
- Authentification basique.
- Gestion de middlewares.
- Règles de filtrage : ré-écritures par motifs, redirections…
- Moniteur de santé de vos services.
Nous nous intéressons surtout ici aux deux premières fonctions, puis, dans un futur article, nous explorerons les possibilités du middleware d'authentification Authelia, qui fonctionne aussi en symbiose avec Træfik.
À noter que Træfik peut traiter du traffic TCP, tel HTTP/S (ports 80 et 443),
ou encore WebSockets, mais aussi de l'UDP, etc.
Seul l'HTTP sera étudié ici, ce qui couvre la majorité des usages.
Les utilités du reverse proxy
Petit rappel graphique de l'utilité d'un tel outil dans votre pile technologique.
Complémentarité avec Nginx etc.
Même si Træfik enlève un gros poids en terme de responsabilités qui incombait autrefois au combo reverse proxy / serveur HTTP avec Nginx, il est souvent utile d'en avoir une petite instance aux côtés des piles Docker que l'on déploie.
Nginx / Apache permettent tout un tas de choses qui ne sont pas aussi bien documentées voire absentes du viseur de Træfik telles que :
- L'injection d'entêtes HTTP.
- La politique de mise en cache.
- Le service de contenu statique.
À voir dans le futur dans quel mesure il phagocytera ces spécificités,
mais pour l'heure, de l'ancienneté de Nginx, découle plus de
de ressources en ligne afin d'optimiser le déploiement de services tiers
(Nextcloud, Seafile, Odoo…).
Par exemple, au moment où j'écris ces lignes,
la version payante Træfik Enterprise dispose
d'un module « http-cache » que je n'ai pas testé.
Cela étant dit, il me semble préférable de bien séparer les responsabilités pour le moment, le temps de mieux connaître l'outil, encore jeune et pas parfaitement stable (cf. la V2 qui fait un bond).
Création et configuration de Træfik
Base du fichier de la pile Docker Compose
Tout d'abord, dans un dossier vierge, on crée un fichier docker-compose.yml
.
Ensuite, voyons qu'elle est la version
la plus récente de Træfik.
La bonne pratique est de fixer les versions afin d'éviter
les mauvaises surprises à l'avenir.
Avec ceci, on veille à ce que le service se (re)lance tout le temps par le biais du démon de Docker.
version: '3'
services:
traefik:
image: traefik:v2.4.8
restart: always
Configuration des volumes
Il faut définir un point de montage pour que nos certificats SSL
persistent malgré la destruction / reconstruction de notre pile Docker Compose.
On injecte ici une variable d'environnement, il est préférable de décoréler
le stockage (un état singulier) d'une fonction (un service amnésique).
Il sera ainsi plus facile de migrer ou d'effectuer des sauvegardes.
Aussi, parce que Træfik a besoin d'un accès privilégié au démon Docker
qui tourne sur l'hôte, nous les lions via son interface de connexion,
le docker.sock
.
C'est par ce biais que notre reverse proxy va effectuer ses tours de magie
pour nous libérer de tâches ingrates, comme étendre la portée de nos
certificats à de nouveaux domaines.
# …ReverseProxy/docker-compose.yml
version: '3'
services:
traefik:
image: traefik:v2.4.5
restart: always
volumes:
# Persistence des certificats
- ${SRV_VOL_BASE}/letsencrypt:/letsencrypt
# Lien vers l'interface de connexion de Docker
- /var/run/docker.sock:/var/run/docker.sock:ro
Configuration du réseau
En tant que point d'entrée HTTP/S, Traefik est le seul conteneur à exposer
les ports 80
et 443
vers le monde extérieur.
Même si nous n'utiliserons que le HTTPS, il est
utile de conserver le port 80 ne serait-ce que pour rediriger les requêtes
non sécurisées vers leur versions sécurisées.
Træfik en a aussi besoin pour son bon fonctionnement interne,
notament pour la génération des certificats SSL.
Nous créons aussi un réseau Docker interne qui servira de relai pour les autres services qui cohabitent avec Traefik.
version: '3'
services:
traefik:
image: traefik:v2.4.5
restart: always
volumes:
- ${SRV_VOL_BASE}/letsencrypt:/letsencrypt
- /var/run/docker.sock:/var/run/docker.sock:ro
ports:
- 443:443
# Port 80 utile pour redirections, certifications…
- 80:80
# Accès au réseau relai interne
networks:
- traefik
# Réseau relai interne
networks:
traefik:
driver: bridge
Configuration de Træfik
Nous rentrons désormais dans le cœur de notre configuration de Træfik.
Il est possible de créer un fichier séparé pour cela, traefik.toml
,
cependant je préfère centraliser tout dans le fichier compose via
l'utilisation des arguments de command
et des labels
.
Les arguments de commande
…
command:
# ------------------------------------------- Configuration Docker
- --providers.docker=true
- --providers.docker.exposedbydefault=false
# ------------------------------------------- Points d'entrée
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
# ------------------------------------------- Configuration SSL
- --certificatesresolvers.ovh.acme.dnschallenge=true
- --certificatesresolvers.ovh.acme.dnschallenge.provider=ovh
- --certificatesresolvers.ovh.acme.email=${RESOLVER_ACME_MAIL}
- --certificatesresolvers.ovh.acme.storage=/letsencrypt/acme.json
# ------------------------------------------- Pré-production
# Décommentez pour utiliser les serveurs de test de Letsencrypt.
# - --certificatesresolvers.ovh.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
# - --log.level=DEBUG
…
- Les
providers
définissent le fournisseur de nos services, ici Docker. - Les
entrypoints
permettent de nommer les points d'entrées HTTP/S tels que les autres services docker les appeleront :web
etwebsecure
. - Les
certificatesresolvers
contiennent la configuration SSL, s'appuyant ici sur un challenge DNS avec les services d'OVH.
La configuration SSL avec OVH se trouve dans le fichier .env
détaillé à la fin de ce guide.
Pour les certificatesresolvers
, comme pour les entrypoints
,
les services l'appeleront par son nom (ovh
) que nous
définissons ici globalement.
Note : afin de ne pas trop solliciter les braves serveurs de Letsencrypt et et par conséquent se retrouvé bridé, il est possible d'utiliser les serveurs de pré-production (dé-commentez la ligne idoine).
Les étiquettes de métadonnées
…
labels:
# ------------------------------------------- Global
- traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)
- traefik.http.routers.http-catchall.entrypoints=web
- traefik.http.routers.http-catchall.middlewares=redirect-to-https@docker
- traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
…
Ici nous définissons une règle globale qui permet de forcer le traffic HTTP vers du HTTPS, ceci via l'utilisation d'un intermédiaire et de routeurs.
Certification SSL avec OVH
Rendez-vous d'abord sur https://eu.api.ovh.com/createToken puis créez les droits suivants :
GET /domain/zone/*
PUT /domain/zone/*
POST /domain/zone/*
DELETE /domain/zone/*
Vous êtes libre d'affiner cette configuration en remplaçant les jokers
représentés par les astérisques *
par vos domaines concernés ou en
définissant une durée de validité plus ou moins permissive.
Ensuite, authentifiez-vous puis stockez en sécurité les informations fournies :
ApplicationKey
ApplicationSecret
ConsumerKey
Les fichiers de configuration finale
Le fichier .env
qui contient les réglages du service
et les identifiants du challenge DNS.
On oubliera pas d'ajouter le paramètre env_file: .env
dans le fichier docker-compose.yml
qui réside à ses côtés.
Note : pensez à conserver les identifiants dans un gestionnaire de mot
de passe comme Bitwarden ou Keepass et conservez un modèle .env.model
dans votre dépôt de sources.
# Pile Docker
# ====================================
SRV_VOL_BASE=./volumes
# Træfik
# ====================================
TZ=Europe/Paris
# Certificats SSL (Challenge DNS OVH)
# ------------------------------------
RESOLVER_ACME_MAIL=<bar@foo.com>
OVH_ENDPOINT=<ovh-eu>
OVH_APPLICATION_KEY=<key>
OVH_APPLICATION_SECRET=<secret>
OVH_CONSUMER_KEY=<consumer>
Le fichier docker-compose.yml
au complet.
version: '3'
services:
traefik:
image: traefik:v2.4.5
env_file: .env
restart: always
command:
# ------------------------------------------- Configuration Docker
- --providers.docker=true
- --providers.docker.exposedbydefault=false
# ------------------------------------------- Points d'entrée
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
# ------------------------------------------- Configuration SSL
- --certificatesresolvers.ovh.acme.dnschallenge=true
- --certificatesresolvers.ovh.acme.dnschallenge.provider=ovh
- --certificatesresolvers.ovh.acme.email=${RESOLVER_ACME_MAIL}
- --certificatesresolvers.ovh.acme.storage=/letsencrypt/acme.json
# ------------------------------------------- Pré-production
# - --certificatesresolvers.ovh.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
# - --log.level=DEBUG
ports:
- 80:80
- 443:443
volumes:
- ${SRV_VOL_BASE}/letsencrypt:/letsencrypt
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- traefik
labels:
# ------------------------------------------- Global
- traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)
- traefik.http.routers.http-catchall.entrypoints=web
- traefik.http.routers.http-catchall.middlewares=redirect-to-https@docker
- traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
networks:
traefik:
driver: bridge
Un simple docker-compose up -d
à la racine du projet et c'est parti !
Conclusion et suite…
Avoir un reverse proxy c'est bien, lui donner de quoi s'occuper c'est mieux !
Quoi de mieux que de d'attaquer avec un autre service tout aussi prépondérant aus sein de notre pile technologique : l'annuaire d'utilisateurs (LDAP), sur lequel repose une foultitude de services courants.