Projet de Fin d'Études : Partie 2 - Configurer Traefik v2 avec une API Node.js sous Docker
Table des matières
Nous avons désormais une configuration fonctionnelle avec Node.js et Docker, nous pouvons maintenant configurer le serveur de production.
Je voulais qu’il soit facile à utiliser pour les déploiements, et le plus autonome et sécurisé possible avec la génération automatique de certificats SSL et du load balancing.
L’API utilise MongoDB, et tout fonctionne dans des conteneurs docker. J’ai choisi Traefik comme reverse proxy car il fournit un support natif pour le reverse proxying des conteneurs docker, il fonctionne bien avec letencrypt pour obtenir des certificats automatiquement, et il supporte le load balancing sur les conteneurs docker. Et également parce que je voulais essayer une alternative NGINX pour satisfaire ma curiosité.
Cependant, s’il y a BEAUCOUP d’informations en ligne sur la version 1.7 de Traefik, il n’y en a pas tant pour la version 2, et cette v2 comporte de nombreuses modifications importantes. Et à l’heure actuelle, la documentation officielle n’est pas facile à comprendre et de nombreuses fonctionnalités ne sont pas documentées.
Mise en place de Traefik dans Docker
La configuration finale ressemblera à cela :
Explication rapide du fonctionnement de Traefik
Traefik traite les requêtes de cette manière :
Voici le routeur utilisé dans cette chaîne (notez la partie rule
), avec sa configuration TLS (méthode letencrypt http) et le middleware utilisé (basicauth).
Cela correspond à : “Pour une requête avec host=traefik.domain.com
venant du port 443, nous passons par le routeur traefik-secure@docker
(parce que l’hôte satisfait à la règle du routeur), puis par tout middleware utilisé dans ce routeur (ici un basicauth) et enfin, jusqu’au service (ici api@internal fait référence à l’api interne de traefik, qui est le tableau de bord web Traefik)“
Structure des fichiers
J’aime utiliser Docker-Compose pour gérer mes conteneurs persistants (conteneurs que je veux pouvoir redémarrer facilement).
Pour commencer avec une architecture claire, notre répertoire traefik devrait ressembler à ceci :
traefik/
├── acme.json
├── config
│ └── config.yml
├── docker-compose.yml
└── traefik.yml
Explication :
acme.json
stockera tous les certificats SSL obtenus par Traefik, nous le monterons en volume pour les conserver lors des redémarrages des conteneurs.- Le répertoire
config/
contient nos fichiers de configuration dynamiques (où nous définissons manuellement certaines routes et règles “à la NGINX”). Traefik surveille ce répertoire et intègre automatiquement toutes les règles créées dans les fichiers qu’il contient. docker-compose.yml
sera utilisé pour gérer le conteneur Traefik.traefik.yml
est le fichier de configuration statique, lu une fois au démarrage de Traefik.
Configuration de Traefik
acme.json
devrait être vide pour le premier départ.
Commençons par traefik.yml
:
# Voici le tableau de bord de traefik, accessible via traefik.domain.com
api:
dashboard: true
debug: true
# Seulement avec les ports habituels :80 et :443, mais traefik v2 peut aussi prendre d'autres ports comme :8080 si vous le souhaitez !
entryPoints:
http:
address: ":80"
https:
address: ":443"
# Définition de l'endroit où nos services et middlewares peuvent être trouvés
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
file:
directory: /config
watch: true
log:
level: "ERROR"
filePath: "log/error.log"
format: "json"
accessLog:
filePath: "log/access.log"
format: "json"
# Configuration pour le cert resolver Letsencrypt
certificatesResolvers:
http:
acme:
email: yourmail@domain.com
storage: acme.json
httpChallenge:
entryPoint: http
Maintenant, notre fichier de configuration dynamique config/config.yml
:
http:
# Création de nos middlewares
middlewares:
# Lorsqu'il est utilisé, il redirige le client vers le point d'entrée https
https-redirect:
redirectScheme:
scheme: https
# Applique certains en-têtes lorsqu'il est utilisé
default-headers:
headers:
frameDeny: true
sslRedirect: true
browserXssFilter: true
contentTypeNosniff: true
forceSTSHeader: true
stsIncludeSubdomains: true
stsPreload: true
# Lorsqu'il est utilisé, applique le middleware default-headers
secured:
chain:
middlewares:
- default-headers
# Il s'agit d'une redirection globale http -> https. Avec ceci, vous pouvez seulement définir un routeur https pour vos services, ceci redirigera les utilisateurs venant de http vers votre routeur https.
# Si vous n'en avez pas besoin, supprimez tout ce qui se trouve sous cette ligne
routers:
https-redirouter:
rule: HostRegexp(`{any:.*}`)
middlewares: [https-redirect]
service: dummy
services:
dummy:
loadBalancer:
servers:
- url: localhost
Et enfin, le docker-compose.yml
:
version: '3.5'
services:
traefik:
image: traefik:v2.0
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- web
ports:
- 80:80
- 443:443
volumes:
# Synchronisation de l'heure avec l'hôte
- /etc/localtime:/etc/localtime:ro
# Accès au socket docker pour contrôler les autres conteneurs
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik.yml:/traefik.yml:ro
- ./acme.json:/acme.json
- ./config:/config:ro
- ./log:/log
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.entrypoints=http"
- "traefik.http.routers.traefik.rule=Host(`traefik.domain.com`)"
- "traefik.http.middlewares.traefik-auth.basicauth.users=admin:$apr1$Z3U0xxxxxxxxxxxxxxxxxRz1"
- "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.traefik.middlewares=traefik-https-redirect"
- "traefik.http.routers.traefik-secure.entrypoints=https"
- "traefik.http.routers.traefik-secure.rule=Host(`traefik.domain.com`)"
- "traefik.http.routers.traefik-secure.middlewares=traefik-auth"
- "traefik.http.routers.traefik-secure.tls=true"
- "traefik.http.routers.traefik-secure.tls.certresolver=http"
- "traefik.http.routers.traefik-secure.service=api@internal"
# Mettre traefik dans un réseau docker externe.
# Les autres conteneurs qui doivent être accessibles seront déclarés dans le même réseau
networks:
web:
external: true
⚠️ Partager la socket docker avec un conteneur docker peut être dangereux, car cela signifie que ce conteneur peut avoir un accès root à la machine hôte. L’utilisation d’un proxy sur la socket est recommandé.
Notez la section labels
dans ce docker-compose. Traefik lira les labels de chaque conteneur dans son réseau (web
), et appliquera les instructions comme configuration dynamique.
Ici, nous définissons donc 2 routeurs : traefik
pour http et traefik-secure
pour https.
traefik
utilise un middleware pour la redirection vers https ( également défini ici, c’esttraefik-https-redirect
).traefik-secure
utilise un middleware de basic-auth avec une combinaison user:password.
Cette configuration est celle que nous avons vue plus tôt
Démarrage du conteneur Traefik
Une fois la configuration terminée, il suffit de faire :
docker-compose up -d
Les données de log peuvent être trouvées dans le répertoire log/
.
Reverse-Proxying vers d’autres conteneurs
Attacher la configuration traefik à chaque conteneur
Maintenant que Traefik est opérationnel, nous pouvons l’utiliser pour servir d’autres conteneurs, comme notre API.
J’utilise le même fichier docker-compose dont j’ai parlé dans la première partie de cette série, vous pouvez le trouver ici.
Nous devons le connecter au réseau web
, et appliquer la configuration de traefik appropriée dans les labels
pour chaque conteneur. Nous devons éviter que le conteneur MongoDB soit accessible à traefik, il sera donc interdit.
api-ecobol/docker-compose.yml
version: '3'
services:
api-ecobol:
env_file:
- .env
restart: on-failure
build: .
links:
- mongo
depends_on:
- mongo
labels:
- "traefik.enable=true"
- "traefik.http.routers.api-ecobol.entrypoints=http"
- "traefik.http.routers.api-ecobol.rule=Host(`api.example.com`)"
- "traefik.http.routers.api-ecobol.middlewares=https-redirect@file"
- "traefik.http.routers.api-ecobol-secure.middlewares=secured@file"
- "traefik.http.routers.api-ecobol-secure.entrypoints=https"
- "traefik.http.routers.api-ecobol-secure.rule=Host(`api.example.com`)"
- "traefik.http.routers.api-ecobol-secure.tls=true"
- "traefik.http.routers.api-ecobol-secure.tls.certresolver=http"
- "traefik.http.routers.api-ecobol-secure.service=api-ecobol"
- "traefik.http.services.api-ecobol.loadbalancer.server.port=3000"
- "traefik.docker.network=web"
ports:
- '127.0.0.1:3001:3000'
networks:
- web
mongo:
image: 'mongo:4'
volumes:
- './data:/data/db'
restart: on-failure
labels:
- "traefik.enable=false"
ports:
- '127.0.0.1:27018:27017'
networks:
- web
networks:
web:
external: true
Note : Si vous avez gardé la redirection http -> https globale dans
config/config.yml
, vous pouvez supprimer ces lignes (le routeur http) des labels :- "traefik.http.routers.api-ecobol.entrypoints=http" - "traefik.http.routers.api-ecobol.rule=Host(`api.example.com`)" - "traefik.http.routers.api-ecobol.middlewares=https-redirect@file"
Il y a deux nouvelles choses ici : la ligne loadbalancer.server.port
, qui indique à traefik sur quel port l’application tourne à l’intérieur du conteneur, et la ligne traefik.enable=false
, pour empêcher traefik d’exposer le conteneur.
Suite à mon dernier article, cette configuration peut être spécifiée uniquement pour la production dans
docker-compose.prod.yml
.
Comme toujours :
docker-compose up -f docker-compose.yml -f docker-compose.prod.yml -d
Scaling des conteneurs : Load Balancing
Maintenant, nous pouvons essayer le load balancing. Avec Docker-Compose, la commande pour scale un conteneur est :
docker-compose scale api-ecobol=3
Cela créera un total de 3 conteneurs, et Traefik les détectera automatiquement, et équilibrera le trafic entrant entre chacun d’eux, tout en surveillant leur statut. Vous pouvez observer cela dans le tableau de bord à l’adresse traefik.domain.com
.