Table des matières
Bonjour à tous,
Comme j’en parlais dans cet article de juillet dernier, j’ai migré mon infrastructure et mes services sous Docker (avec Docker-compose). Aujourd’hui, je vais vous présenter un outil que j’ai fortement utilisé dans cette infrastructure (spoilé par le titre): Traefik.
Traefik: Qu’es aquò ?
Un reverse-proxy
En français, on dirait proxy inverse, simplement, vous n’entendrez jamais cette traduction, donc on parlera ici de reverse-proxy.
Rien ne vaut un petit schéma maison pour décrire plus simplement ce que c’est.
Donc comme on peut le voir, le reverse-proxy est un outil qui fait le lien entre internet (et ses utilisateurs) et un serveur (et ses applications).
Petit exemple concret: www.pofilo.fr
et searx.pofilo.fr
redirigent tous 2 (via DNS comme expliqué dans cet article) vers le même serveur.
Le rôle du reverse-proxy (ici Traefik) est donc de rediriger les requêtes entrantes sur un serveur vers le bon service (searx ou mon site).
Quand on parle de reverse-proxys, on pense le plus souvent à Apache et nginx. Mais il y a un piège car ils sont capables d’être à la fois serveur web et reverse-proxy.
Traefik
Traefik est donc un reverse-proxy (et également un load-balancer (équilibreur de charge), point que l’on ne va pas détailler ici). Il est bien sûr Open Source (code source disponible ici (miroir ici)) et il est écrit en Go.
Les principales fonctionnalités de Traefik sont les suivantes (en partie reprises et traduites depuis le README):
- Rechargement à chaud des configurations: pour moi une solution moins fiable qu’un reload du logiciel car il existe le piège de sauvegarder la configuration sans qu’elle ne soit terminée, ce qui peut générer une configuration invalide.
- Supporte plusieurs algorithmes de load-balancing (équilibrage de charge).
- Intégration native de Let’s Encrypt pour fournir du HTTPS aux différents services.
- Interface web pour contrôler la configuration et mise à disposition d’une API Rest.
- Fournit des métriques (Rest, Prometheus, Datadog, Statsd, InfluxDB) et des logs d’accès (JSON, CLF).
- Go oblige, il est fourni sous un binaire unique (ou via une image Docker relativement petite).
- Supporte l’ajout de plugins écrits en Go via un Marketplace (depuis la récente version
2.3.0
du 23 septembre dernier).
Traefik se veut également rapide et veut rendre facile le déploiement de micro-services. Cela se confirme à l’usage, c’est maintenant un plaisir d’utiliser Traefik.
Traefik s’intègre avec votre infrastructure (Docker, Swarm mode, Kubernetes, Marathon, Consul, Etcd, Rancher, Amazon ECS, …) et se configure lui-même automatiquement et dynamiquement. Faire le lien entre Traefik et l’orchestrateur devrait être la seule étape de configuration à réaliser.
Derrière cette belle phrase se cache tout de même un fond de vérité.
En réalité, vous allez galérer et le détester au démarrage mais une fois qu’on maîtrise un peu (bien) la bestiole, tout se fait très simplement et rapidement. Le temps d’apprentissage reste relativement court, mais vous allez faire un paquet d’erreur au début.
Tutoriel par l’exemple
Ici, je vais reprendre l’exemple utilisé dans la documentation officielle, on n’ira pas étape par étape mais fichier par fichier (avec les explications que je peux y apporter). Je vous déconseille de les recopier tels quels (même si ça fonctionnera aux certificats TLS prêts), ce sont plus des exemples dont vous pouvez vous inspirer pour les adapter à votre sauce.
Pour les versions des images Docker, je vous laisse gérer, un article dédié à ce sujet est en cours de rédaction pour les prochains mois.
On retrouve une configuration statique (nécessite un redémarrage de Traefik en cas de modification) et une configuration dynamique (prise en compte dès la sauvegarde, à ne surtout pas éditer en production donc: une erreur dans ce fichier et votre instance de Traefik est cassée).
Architecture
xx/ # Dossier "principal"
xx/traefik/docker-compose.yml
xx/whoami/docker-compose.yml
xx/data/traefik/conf/traefik.toml # Fichier de configuration statique
xx/data/traefik/conf/dynamic/traefik.config.toml # Fichier de configuration dynamique
xx/data/traefik/certs/example.com # Dossier comprenant les certificats TLS
Fichier de configuration statique
Il s’agit ici du fichier xx/data/traefik/conf/traefik.toml
.
### STATIC ###
defaultEntryPoints = ["websecure", "web"]
[entryPoints]
[entryPoints.web]
address = ":80"
[entryPoints.web.http]
[entryPoints.web.http.redirections]
[entryPoints.web.http.redirections.entryPoint]
to = "websecure"
scheme = "https"
[entryPoints.websecure]
address = ":443"
[providers]
[providers.file]
# Dynamic files
directory = "/etc/traefik/dynamic/"
watch = true
[providers.docker]
endpoint = "unix:///var/run/docker.sock"
exposedByDefault = false
watch = true
[api]
dashboard = true
On retrouve donc tout d’abord les entrypoints (points d’entrées) qui permettent à Traefik de récupérer tout ce qui se passe sur les ports 80 et 443 (http et https). Il y a également une redirection pour rediriger tout ce qui arrive du port 80 vers le port 443.
On a ensuite les providers qui sont des mécanismes qui permettent de découvrir les services présents sur notre infrastructure qui sont dans notre cas le fichier de configuration dynamique ainsi que Docker.
exposedByDefault = false
spécifie que, par défaut, un conteneur ne sera pas exposé par Traefik. D’un point de vue sécurité, c’est pour moi une hérésie que cette variable soit à true
par défaut … (source).
Enfin, on active le dashboard pour avoir l’interface web pour vérifier la configuration.
Fichier de configuration dynamique
Il s’agit ici du fichier xx/data/traefik/conf/dynamic/traefik.config.toml
.
### DYNAMIC ###
[tls]
[tls.stores]
[tls.stores.default]
# needed otherwise traefik generates a xx.traefik.default certificate
[tls.stores.default.defaultCertificate]
certFile = "/etc/certs/example.com/fullchain.pem"
keyFile = "/etc/certs/example.com/privkey.pem"
# *.example.com
[[tls.certificates]]
certFile = "/etc/certs/example.com/fullchain.pem"
keyFile = "/etc/certs/example.com/privkey.pem"
# Not necessary as it goes to default by default
# But it will maybe change one day ! ...
stores = ["default"]
[tls.options]
[tls.options.intermediate]
minVersion = "VersionTLS12"
cipherSuites = [
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305"
]
sniStrict = true
[http]
[http.middlewares]
[http.middlewares.secure-headers.headers]
stsSeconds=63072000
stsIncludeSubdomains=true
stsPreload=true
contentTypeNosniff=true
referrerPolicy="no-referrer"
customFrameOptionsValue="deny"
contentSecurityPolicy="default-src 'self'"
[http.middlewares.secure-headers.headers.customResponseHeaders]
Strict-Transport-Security = "max-age=63072000"
En premier lieu, il y a la déclaration des certificats TLS (un peu étrange de devoir configurer de deux façons différents le certificat par défaut mais sinon, Traefik génère en plus un certificat en xx.traefik.default
).
On retrouve ensuite les options TLS qui sont générées à l’aide de l’outil SSL Configuration Generator de Mozilla.
Les middlewares suivent dans le fichier et permettent de modifier les requêtes avant de les envoyer à un service. Ici, notre middleware permet de spécifier les headers que l’on souhaite.
Docker-compose Traefik
Il s’agit ici du fichier xx/traefik/docker-compose.yml
version: '3.7'
services:
traefik:
image: traefik:2.2.11
container_name: "traefik"
ports:
# External:Internal
- "80:80"
- "443:443"
volumes:
- ../data/traefik/conf/dynamic/:/etc/traefik/dynamic/:ro
- ../data/traefik/conf/traefik.toml:/etc/traefik/traefik.toml:ro
- ../data/traefik/certs/:/etc/certs/:ro
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
# so that Traefik can listen to the Docker events
- /var/run/docker.sock:/var/run/docker.sock:ro
restart: always
networks:
- traefik-frontend
labels:
# Dashboard
- "traefik.enable=true"
- "traefik.docker.network=traefik-frontend"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.middlewares.auth-traefik.basicauth.users=admin:$apr1$QbRRQO7i$h9Qzt3ox47ZB233g8PeIa1"
- "traefik.http.routers.traefik.rule=Host(`traefik.example.com`)"
- "traefik.http.routers.traefik.entrypoints=websecure"
# TLS and security
- "traefik.http.routers.traefik.tls=true"
- "traefik.http.routers.traefik.tls.options=intermediate@file"
- "traefik.http.routers.traefik.middlewares=auth-traefik, secure-headers@file"
networks:
traefik-frontend:
name: traefik-frontend
La particularité de Traefik, grâce à son intégration avec Docker, est qu’il a besoin d’accéder à /var/run/docker.sock
.
Cela lui permet, quand on rajoute ou retire un service de modifier sa configuration à chaud notamment avec les nombreux labels à spécifier:
traefik.enable=true
: pour le conteneur soit détecté par le provider Docker.traefik.docker.network=traefik-frontend
: pour que le conteneur soit sur le réseau frontend utilisé par Traefik.traefik.http.routers.traefik.service=api@internal
: pour déclarer un routeur qui va accéder à l’interface web (option activée dans la configuration statique).traefik.http.middlewares.auth-traefik.basicauth.users=admin:$apr1$QbRRQO7i$h9Qzt3ox47ZB233g8PeIa1
: la déclaration d’un middleware pour rajouter un mot de passe à l’interface web (ici admin/admin) avec la commandeecho $(htpasswd -nb user password)
).traefik.http.routers.traefik.rule=Host(\
traefik.example.com`)`: l’adresse pour accéder à l’interface web.traefik.http.routers.traefik.entrypoints=websecure
: chaque routeur nécessite la configuration d’un entrypoint, on spécifie celui sur le port 443.traefik.http.routers.traefik.tls=true
: pour activer le TLS pour ce routeur.traefik.http.routers.traefik.tls.options=intermediate@file
: pour indiquer les options à utiliser qui sont factorisées dans le fichier dynamique.traefik.http.routers.traefik.middlewares=auth-traefik, secure-headers@file
: on indique les 2 middlewares à utiliser: celui pour l’authentification par mot de passe et celui pour les headers à utiliser (suffixé de@file
pour préciser qu’il s’agit d’un middleware défini dans le provider de type fichier).
Ici, les labels ne servent que pour accéder à l’interface web de Traefik, qui est en réalité le service api@internal
. On peut tous les supprimer si on ne souhaite pas l’activer.
Depuis la rédaction de cet article, la version 2.3.0
est sortie, je vous invite à l’utiliser directement si vous voulez vous mettre à Traefik.
Docker-compose Whoami
Il s’agit ici du fichier xx/whoami/docker-compose.yml
version: '3.7'
services:
whoami:
image: traefik/whoami:v1.6.0
container_name: "whoami"
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik-frontend"
- "traefik.http.services.whoami.loadbalancer.server.port=80"
- "traefik.http.routers.whoami.rule=Host(`whoami.example.com`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
# TLS and security
- "traefik.http.routers.whoami.tls=true"
- "traefik.http.routers.whoami.tls.options=intermediate@file"
- "traefik.http.routers.whoami.middlewares=secure-headers@file"
networks:
- traefik-frontend
networks:
traefik-frontend:
name: traefik-frontend
Concernant les labels, on retrouve donc le même principe que pour les labels utilisés pour mettre en avant l’interface web de Traefik.
On retrouve en plus la déclaration du service pour expliciter à Traefik qu’il faut qu’il communique avec le port 80 du conteneur.
Dans les 2 exemples, on peut distinguer traefik.http.routers.whoami
et traefik.http.routers.traefik
, whoami
et traefik
ont toute leur importance puisque qu’ils correspondent aux noms des routeurs: vous ferez forcément des oublis lors des copiés-collés donc méfiance !
Des logs
Il est également possible de mettre en place des logs, que ce soit pour Traefik en cas d’erreur ou warning levé par le logiciel mais aussi pour tous les accès réalisés en HTTP et HTTPS. Je vous laisse vous référer aux documentations en question:
Certificats avec Let’s Encrypt
Si vous voulez laisser Traefik gérer vos certificats, la documentation à suivre est disponible ici (il y aura des modifications à faire dans les fichiers d’exemples ci-dessus).
Pour ma part, je préfère les gérer par moi-même. La raison initiale est que j’avais déjà l’habitude de générer un certificat wildcard que je redistribue sur mes différents serveurs.
Sous Debian, il faut installer certbot avec la commande apt install certbot
.
Pour générer un certificat Wildcard, on peut ensuite utiliser une commande du genre certbot certonly --server https://acme-v02.api.letsencrypt.org/directory --manual --preferred-challenges dns-01 --rsa-key-size 4096 --domain example.com
Les certificats seront alors générés dans le dossier /etc/letsencrypt/live/example.com
et il vous faudra copier les fichiers fullchain.pem
et privkey.pem
dans le dossier xx/data/traefik/certs/example.com/
.
Pour ma part, j’utilise un script qui automatise ces générations et copies de fichiers.
Résultats sur l’interface web
Voici un aperçu de ce qu’on peut trouver sur l’interface web avec l’exemple montré dans cet article:
Conclusion
Traefik est un jeune outil formidable. En effet, une fois le premier apprentissage (par la pratique, la théorie ne vous apprend que très peu) effectué, Traefik est un outil très puissant où l’on peut à peu près tout faire … d’une façon ou d’une autre.
Et c’est ce dernier point son principal inconvénient. On peut tout écrire dans les fichiers Docker-compose, on peut écrire dans des fichiers de configurations (ne pas mélanger les statiques et dynamiques) que l’on peut écrire en TOML ou en YML.
Par exemple pour rediriger tout le trafic HTTP en HTTPS, il existe deux manières totalement distinctes:
- Version 1 avec un middleware et un routeur (dans la configuration dynamique):
[http.middlewares.redirect-to-https.redirectScheme]
scheme = "https"
...
# Global redirect to https
[http.routers.http-all]
entryPoints = ["web"]
rule = "HostRegexp(`{any:.+}`)"
middlewares = ["redirect-to-https"]
# <container-name>-<folder-docker-compose-of-traefik>
service = "traefik-traefik@docker" # <@docker> because the service is provided by the Docker provider
- Version 2 apportée par une version plus récente (dans la configuration statique):
[entryPoints.web.http]
[entryPoints.web.http.redirections]
[entryPoints.web.http.redirections.entryPoint]
to = "websecure"
scheme = "https"
Donc si vous avez le temps et l’envie d’apprendre, je vous conseille Traefik qui a quelques défauts de jeunesses mais est très fiable et très robuste. En revanche, je n’ai essayé que très peu de ses concurrents et je ne saurai dire si d’autres sont meilleurs ou non. Et vous, avez-vous déjà utilisé Traefik ? Qu’en pensez-vous ?
Ce n’était pas le sujet donc je n’en ai pas parlé, mais en plus de l’HTTP, Traefik est également capable de gérer le TCP et l’UDP.