Aller au contenu

🌐 Deploiement de Traefik

traefik-docker-swarm

Présentation de Traefik

Traefik est un "edge router" open source. il est principalement utilisé en tant que reverse-proxy et load-balancer. C'est justement l'utilisation que je vais en faire.

Edge router

Un "edge router" (ou routeur de périphérie) est un dispositif de réseau qui se situe à la frontiÚre entre un réseau interne (tel qu'un réseau d'entreprise ou un réseau domestique) et un réseau externe, comme l'Internet.

Les raisons qui m'ont poussé à choisir Traefik :

  • LĂ©ger et performant
  • Une interface web
  • Gestion du HTTP/S et gĂ©nĂ©ration de certificats
  • Gestion du HTTP/2 et HTTP/3
  • Collection Crowdsec
  • Exposition des mĂ©triques vers Prometheus
  • Prise en charge du proxy protocol
  • Configuration dynamique

Traefik s'appuie sur les éléments suivants :

  • Les providers : c’est le ou les Ă©lĂ©ment(s) qui vont servir de source Ă  traefik pour rĂ©cupĂ©rer sa configuration, donc la socket docker par exemple. Les providers permettent de dĂ©tecter oĂč se trouvent les services.
  • Les entrypoints : qui sont les points d’écoute, donc un port, un proto ( TCP / UDP / HTTP ) pour constituer un point d’entrĂ©e pour la requĂȘte.
  • Les routers : analysent la requĂȘte pour savoir oĂč la rediriger et Ă©ventuellement lui appliquer un ou des middlewares.
  • Les middlewares : permettent de changer la requĂȘte avec par exemple l'ajout d’entĂȘte, d’authentification, de chemin dans l’URL etc

  • Les services : dĂ©finition de la destination de la requĂȘte et load balancing vers cette derniĂšre
  • Les certificates resolvers : permettent de gĂ©nĂ©rer les certificats via Let’s Encrypt : le choix du challenge, la taille de la clĂ© etc

traefik-overview

Principe de fonctionnement

Toutes les requĂȘtes Ă  destination d'une des applications exposĂ©es par Traefik passera d'abord par HAProxy. La requĂȘte sera ensuite dirigĂ©e vers un des noeuds du cluster Docker Swarm. Elle passera par le rĂ©seau ingress pour ĂȘtre routĂ©e vers Traefik. Ce dernier routera alors la requĂȘte Ă  travers un autre rĂ©seau de type overlay vers le conteneur faisant tourner l'application. Ce dernier rĂ©seau sera nommĂ© ici "web" et sera crĂ©Ă© par nous.

haproxy-swarm-traefik

Préparation de Traefik

Création des répertoires

Création des repertoires
olivier@ds01:/mnt/nfsdatas$ sudo mkdir traefik
olivier@ds01:/mnt/nfsdatas$ sudo chown olivier: traefik
olivier@ds01:/mnt/nfsdatas$ mkdir -p traefik/config/dyn_traefik
olivier@ds01:/mnt/nfsdatas$ mkdir traefik/{log,secrets}

Configuration du service Traefik

Nous allons commencer par configurer le service Traefik à travers le fichier docker-stack.yml avec un accÚs sécurisé au dashboard. Pour sécuriser l'accÚs au dashboard, nous mettrons en place une authentification user/password. Il faut pour cela préparer un hash du password.

Création d'un user et d'un password pour l'accÚs authentifié au dashboard
olivier@ds01:/mnt/nfsdatas$ sudo apt install apache-utils -y
olivier@ds01:/mnt/nfsdatas$  echo $(htpasswd -nbB olivier "UN_SUPER_MOT_DE_PASSE") | sed -e s/\\$/\\$\\$/g
olivier@ds01:/mnt/nfsdatas$  olivier:$$2y$$05$$0n7VDgZCB4LZwTL5nWCK1.1yOn13yNTbGfkLzAatEQYEdN24lUGAe
DĂ©composition de la commande "htpasswd -nbB olivier "UN_SUPER_MOT_DE_PASSE"" :

  • htpasswd : Un utilitaire fourni par le paquet apache-utils pour Debian pour gĂ©rer les fichiers contenant des mots de passe pour l'authentification HTTP Apache.
  • -n : Affiche le rĂ©sultat sur la sortie standard au lieu de l'Ă©crire dans un fichier.
  • -b : Utilise le mode batch, permettant de spĂ©cifier le mot de passe en ligne de commande (plutĂŽt que d'ĂȘtre invitĂ© Ă  le saisir).
  • -B : Utilise l'algorithme bcrypt pour hacher le mot de passe.
  • olivier : Le nom d'utilisateur pour lequel le mot de passe est hachĂ©.
  • "UN_SUPER_MOT_DE_PASSE" : Le mot de passe en clair Ă  hacher.

Dans le cas d'utilisation du mot de passe dans notre fichier de configuration du service, nous devons Ă©chapper le symbole "$" en remplaçant ce dernier par "$$" d'oĂč l'utilisation de la commande sed pour le faire de façon automatique.

Passons au fichier de configuration du service.

Configuration du service Traefik /mnt/nfsdatas/traefik/docker-stack.yml
olivier@ds01:/mnt/nfsdatas$ cd traefik
olivier@ds01:/mnt/nfsdatas/traefik$ vim docker-stack.yml 

secrets:
  ovh_endpoint:
    file: "./config/secrets/ovh_endpoint.secret"
  ovh_application_key:
    file: "./config/secrets/ovh_application_key.secret"
  ovh_application_secret:
    file: "./config/secrets/ovh_application_secret.secret"
  ovh_consumer_key:
    file: "./config/secrets/ovh_consumer_key.secret"

version: "3.3"
services:
  traefik:
    # La version de Traefik utilisée est la version 3 qui est sortie récemment.
    image: "traefik:v3.0"
    environment:
      - TZ=Europe/Paris
    networks:
      - web
    secrets:
      - "ovh_endpoint"
      - "ovh_application_key"
      - "ovh_application_secret"
      - "ovh_consumer_key"
    environment:
      - "OVH_ENDPOINT_FILE=/run/secrets/ovh_endpoint"
      - "OVH_APPLICATION_KEY_FILE=/run/secrets/ovh_application_key"
      - "OVH_APPLICATION_SECRET_FILE=/run/secrets/ovh_application_secret"
      - "OVH_CONSUMER_KEY_FILE=/run/secrets/ovh_consumer_key"
    volumes:
      # Mapping sur le socket interne de Docker
      - '/var/run/docker.sock:/var/run/docker.sock:ro'
      # Mapping du fichier de configuration statique
      - './traefik/traefik.yml:/traefik.yml'
      # Mapping du dossier contenant la configuration dynamique
      - './traefik/dyn_traefik/:/dyn_traefik/'
      # Mapping du fichier de stockage des certificats
      # Le fichier initial doit ĂȘtre crĂ©er manuellement.
      - './traefik/acme.json:/acme.json'
      # Mapping du dossier contenant les logs des accĂšs et de traefik
      - "./traefik/log:/var/log"
    ports:
      - "80:80"
      - "443:443"
    deploy:
      placement:
        constraints:
          # La seule condition requise pour que Traefik fonctionne avec le mode Swarm 
          # est qu'il doit s'exĂ©cuter sur un nƓud manager depuis que 
          # l'API Swarm n'est exposĂ©e que sur les nƓuds manager.
          - node.role == manager
        restart_policy:
          condition: on-failure
      labels:
        # Paramétrages pour accéder au dashboard de Traefik
        - "traefik.enable=true"
        - "traefik.http.routers.traefik-dashboard.rule=Host(`d.dev.quercylibre.fr`)"
        - "traefik.http.routers.traefik-dashboard.service=api@internal"
        - "traefik.http.routers.traefik-dashboard.entrypoints=websecure"
        - "traefik.http.routers.traefik-dashboard.tls.certresolver=letsencrypt"
        - "traefik.http.routers.traefik-dashboard.middlewares=traefik-dashboard-auth"
        - "traefik.http.middlewares.traefik-dashboard-auth.basicauth.users=olivier:$$2y$$05$$0n7VDgZCB4LZwTL5nWCK1.1yOn13yNTbGfkLzAatEQYEdN24lUGAe"
        - "traefik.http.services.traefik-dashboard-service.loadbalancer.server.port=8080"

networks:
  # RĂ©seau de type overlay qui permettra Ă  Traefik de communiquer avec les autres conteneurs
  web:
    # external permet de connecter d'autre conteneur Ă  cette stack
    # sans qu'ils soient définis dans celle-ci.
    external: true

Explication des labels permettant l'accĂšs au dashboard de Traefik :

  • Activation de Traefik pour ce service
    • traefik.enable=true : Cette Ă©tiquette indique Ă  Traefik de gĂ©rer ce conteneur. Si cette Ă©tiquette est absente ou dĂ©finie sur false, Traefik ignorera ce conteneur.
  • DĂ©finition du routeur HTTP
    • traefik.http.routers.traefik-dashboard.rule=Host(d.dev.quercylibre.fr) : Cette rĂšgle spĂ©cifie que le routeur (router) doit diriger les requĂȘtes HTTP vers ce service si l'hĂŽte de la requĂȘte est d.dev.quercylibre.fr.
    • traefik.http.routers.traefik-dashboard.service=api@internal : Cette Ă©tiquette indique que le service utilisĂ© par ce routeur est api@internal, qui est un service intĂ©grĂ© de Traefik pour accĂ©der Ă  son propre tableau de bord et API.
    • traefik.http.routers.traefik-dashboard.entrypoints=websecure : Ce label spĂ©cifie que le routeur doit utiliser le point d'entrĂ©e (entrypoint) nommĂ© websecure que nous dĂ©finirons plus tard dans le fichier de configuration statique traefik.yml. Les points d'entrĂ©e dĂ©finissent les ports sur lesquels Traefik Ă©coute (par exemple, 80 pour HTTP et 443 pour HTTPS).
    • traefik.http.routers.traefik-dashboard.tls.certresolver=letsencrypt : Ce label indique que le routeur doit utiliser letsencrypt pour obtenir des certificats TLS.
  • Middleware pour l'authentification
    • traefik.http.routers.traefik-dashboard.middlewares=traefik-dashboard-auth : Ce label assigne le middleware nommĂ© auth au routeur, ajoutant ainsi une couche d'authentification.
    • traefik.http.middlewares.traefik-dashboard-auth.basicauth.users=olivier:$$2y$$05$$0n7VDgZCB4LZwTL5nWCK1.1yOn13yNTbGfkLzAatEQYEdN24lUGAe : Ce label configure le middleware traefik-dashboard-auth pour utiliser l'authentification basique.
  • Service de backend
    • traefik.http.services.traefik-dashboard-service.loadbalancer.server.port=8080 : Ce label configure le service Traefik pour Ă©quilibrer la charge sur le port 8080 du conteneur. Cela signifie que les requĂȘtes dirigĂ©es vers ce service seront envoyĂ©es au port 8080 du conteneur Docker.

Le choix de traefik-dashboard, traefik-dashboard-auth et traefik-dashboard-service contenus dans les labels est purement arbitraire. Je dirai qu'il doit ĂȘtre le plus parlant possible puisque visible plus tard dans le dashboard.

Passons au fichier de configuration statique de Traefik.

Configuration de Traefik

Configuration de l'application Traefik /mnt/nfsdatas/traefik/config/traefik.yml
olivier@ds01:/mnt/nfsdatas/traefik$ vim config/traefik.yml

api:
  dashboard: true

providers:
  swarm:
    # Il faut avoir mappé le volume dans le docker-compose.yml
    # Ex : '/var/run/docker.sock:/var/run/docker.sock:ro'
    endpoint: "unix:///var/run/docker.sock"

    # Les conteneurs sont par défaut via Traefik. Si cette option est définie sur false, 
    # les conteneurs qui n'ont alors pas de label "traefik.enable=true" sont ignorés 
    # de la configuration de routage résultante.
    exposedByDefault: false

  # Fichier permettant de passer des options de configuration définies dans un fichier et 
  # chargées de maniÚre dynamique sans devoir redémarrer Traekif.
  file:
    directory: /dyn_traefik/
    watch: true

# Points d’entrĂ©e sur lesquels Traefik Ă©coute pour prendre en compte 
# les requĂȘtes entrantes.
entryPoints:
  web: # Nom arbitraire
    address: ":80"
    # Etant derriÚre deux load-balancer, il faut définir leurs IP 
    # dans les options forwardedHeaders et de proxyProtocol afin
    # d'obtenir la véritable IP source du client.
    forwardedHeaders:
      trustedIPs:
        - "127.0.0.1/32"
        - "10.0.0.0/24" # adressage IP du réseau docker "ingress"
        - "IP_HAPROXY"  # adresse IP cÎté LAN du serveur HAProxy
    proxyProtocol:
      trustedIPs:
        - "127.0.0.1/32"
        - "10.0.0.0/24"
        - "IP_HAPROXY"
    http:    
      redirections:
        entryPoint:
          to: websecure
          scheme: https

  websecure: # Nom arbitraire
    address: ":443"
    forwardedHeaders:
      trustedIPs:
        - "127.0.0.1/32"
        - "10.0.0.0/24"
        - "IP_HAPROXY"   
    proxyProtocol:
      trustedIPs:
        - "127.0.0.1/32"
        - "10.0.0.0/24"
        - "IP_HAPROXY"

# Génération du certif HTTPS par challenge DNS avec OVH
certificatesResolvers:
  letsencrypt:
    acme:
      # En mode production
      caServer: "https://acme-v02.api.letsencrypt.org/directory"
      # En mode staging. Recommandé lors des premiers lancements de Traefik.
      #caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
      email: "olivier@monemail.fr"
      storage: "/acme.json"
      dnsChallenge:
        provider: ovh
        delayBeforeCheck: 10
        resolvers:
          - "1.1.1.1:53"
          - "8.8.8.8:53"

log:
  filePath: "/var/log/traefik.log"
  format: json
  level: INFO
  # Le level peut ĂȘtre positionnĂ© sur DEBUG lors du premier lancement du service
  # level: DEBUG

accessLog:
  filePath: "/var/log/access.log"
  format: json
  # Paramétrage pour prise en compte de la TZ Europe/Paris
  fields:
    names:
      StartUTC: drop

Configuration pour Let's Encrypt

Création des fichiers
# Remplacer <OVH_...> par vos valeurs. 
olivier@ds01:/mnt/nfsdatas/traefik$ echo "<OVH_ENDPOINT>" > config/secrets/ovh_endpoint.secret
olivier@ds01:/mnt/nfsdatas/traefik$ echo "<OVH_APPLICATION_KEY>" > config/secrets/ovh_consumer_key.secret
olivier@ds01:/mnt/nfsdatas/traefik$ echo "<OVH_APPLICATION_SECRET" > config/secrets/ovh_application_secret.secret
olivier@ds01:/mnt/nfsdatas/traefik$ echo "<OVH_CONSUMER_KEY>" > config/secrets/ovh_application_key.secret

olivier@ds01:/mnt/nfsdatas/traefik$ touch config/acme.json
olivier@ds01:/mnt/nfsdatas/traefik$ chmod 600 config/acme.json

Si vous utilisez OVH comme registrar de votre domaine, rendez-vous alors sur https://www.ovh.com/auth/api/createApp pour créer et récupérer les informations de connexion à leur API. Pour rappel, ces infos vont servir lors du challenge DNS utilisé par Let's Encrypt.

Une fois le challenge réussi, les certificats seront stockés dans le fichier acme.json.

Arborescence finale

olivier@ds01:/mnt/nfsdatas# tree traefik/
traefik/
├── docker-stack.yml
└── config
    ├── acme.json
    ├── dyn_traefik
    └── traefik.yml
└── log
    ├── access.log
    └── traefik.log

Configuration de HAProxy

Extrait du fichier de configuration de HAProxy
(...)
frontend http
        bind *:80
        mode http
        option http-keep-alive

        # DĂ©claration des ACL permettant de router vers Traefik
        acl acl_traefik-dashboard hdr(host) -i d.dev.quercylibre.fr
        acl acl_traefik-cobalt hdr(host) -i cobalt.dev.quercylibre.fr
        acl acl_traefik-cobalt-api hdr(host) -i cobalt-api.dev.quercylibre.fr

        use_backend TRAEFIK_HTTP_BACKEND if acl_traefik-dashboard
        use_backend TRAEFIK_HTTP_BACKEND if acl_traefik-cobalt
        use_backend TRAEFIK_HTTP_BACKEND if acl_traefik-cobalt-api

        default_backend DEFAULT_HTTP_BACKEND

frontend https
        bind *:443
        mode tcp
        option tcplog

        tcp-request inspect-delay 5s
        tcp-request content accept if { req_ssl_hello_type 1 }

        use_backend TRAEFIK_HTTPS_BACKEND if { req_ssl_sni -i d.dev.quercylibre.fr }
        use_backend TRAEFIK_HTTPS_BACKEND if { req_ssl_sni -i w.dev.quercylibre.fr }
        use_backend TRAEFIK_HTTPS_BACKEND if { req_ssl_sni -i cobalt.dev.quercylibre.fr }
        use_backend TRAEFIK_HTTPS_BACKEND if { req_ssl_sni -i cobalt-api.dev.quercylibre.fr }

        default_backend DEFAULT_HTTPS_BACKEND

## Note : Remplacer IP_DS0X par l'adresse IP du noeud en question.
backend TRAEFIK_HTTPS_BACKEND
        mode tcp
        option ssl-hello-chk
        server TRAEFIK_HTTPS_BACKEND01 IP_DS01:443 send-proxy-v2 check
        server TRAEFIK_HTTPS_BACKEND02 IP_DS02:443 send-proxy-v2 check
        server TRAEFIK_HTTPS_BACKEND03 IP_DS03:443 send-proxy-v2 check
        server TRAEFIK_HTTPS_BACKEND04 IP_DS04:443 send-proxy-v2 check
        server TRAEFIK_HTTPS_BACKEND05 IP_DS05:443 send-proxy-v2 check
        server TRAEFIK_HTTPS_BACKEND06 IP_DS06:443 send-proxy-v2 check

backend TRAEFIK_HTTP_BACKEND
        mode http
        balance source
        stick-table type ip size 50k expire 30m  
        stick on src
        http-reuse safe
        server TRAEFIK_HTTP_BACKEND01 IP_DS01:80 send-proxy-v2
        server TRAEFIK_HTTP_BACKEND02 IP_DS02:80 send-proxy-v2
        server TRAEFIK_HTTP_BACKEND03 IP_DS03:80 send-proxy-v2
        server TRAEFIK_HTTP_BACKEND04 IP_DS04:80 send-proxy-v2
        server TRAEFIK_HTTP_BACKEND05 IP_DS05:80 send-proxy-v2
        server TRAEFIK_HTTP_BACKEND06 IP_DS06:80 send-proxy-v2
(...)

Lancement de Traefik

Avant de lancer le service Traefik, il nous faut créer le réseau nommé "web". Pour rappel, c'est le réseau qui fera le lien entre Traefik et les conteneurs faisant tourner les applications. C'est un réseau de type overlay, c'est-à-dire qui s'étend à l'ensemble des noeuds présents dans le cluster Docker Swarm.

Création du network web
olivier@ds01:/mnt/nfsdatas/traefik$ docker network create --driver=overlay web

olivier@ds01:/mnt/nfsdatas/traefik$ docker network ls
NETWORK ID     NAME              DRIVER    SCOPE
b7ce22097876   bridge            bridge    local
36c82b48f1f0   docker_gwbridge   bridge    local
6a3a351c8f6c   host              host      local
18pqm0dz52z9   ingress           overlay   swarm
4be461a86c93   none              null      local
vhfy8nx6v5ak   web               overlay   swarm

Maintenant que tout est prĂȘt nous pouvons lancer le service Traefik et suivre l'Ă©tat de la tĂąche :

Lancement de traefik
olivier@ds01:/mnt/nfsdatas/traefik$ docker stack deploy -c docker-stack.yml -d traefik
Creating service traefik_traefik

olivier@ds01:/mnt/nfsdatas/traefik$ docker stack ps traefik 
ID             NAME                IMAGE            NODE      DESIRED STATE   CURRENT STATE         ERROR     PORTS
xv2qyaptol8l   traefik_traefik.1   traefik:v3.0.0   ds01      Running         Running 3 hours ago 
Vous pouvez également consulter les logs de Traefik pour vérifier si tout est OK.

Logs de Traefik
olivier@ds01:/mnt/nfsdatas/traefik$ tail -f log/traefik.log 
{"level":"info","time":"2024-05-20T13:23:05+02:00","message":"\nStats collection is disabled.\nHelp us improve Traefik by turning this feature on :)\nMore details on: https://doc.traefik.io/traefik/contributing/data-collection/\n"}
{"level":"info","entryPointName":"web","time":"2024-05-20T13:23:05+02:00","message":"Enabling ProxyProtocol for trusted IPs [127.0.0.1/32 10.0.0.0/24 IP_HAPROXY]"}
{"level":"info","entryPointName":"websecure","time":"2024-05-20T13:23:05+02:00","message":"Enabling ProxyProtocol for trusted IPs [127.0.0.1/32 10.0.0.0/24 IP_HAPROXY]"}
{"level":"info","time":"2024-05-20T13:23:05+02:00","message":"Starting provider aggregator aggregator.ProviderAggregator"}
{"level":"info","time":"2024-05-20T13:23:05+02:00","message":"Starting provider *file.Provider"}
{"level":"info","time":"2024-05-20T13:23:05+02:00","message":"Starting provider *traefik.Provider"}
{"level":"info","time":"2024-05-20T13:23:05+02:00","message":"Starting provider *docker.SwarmProvider"}
{"level":"info","time":"2024-05-20T13:23:05+02:00","message":"Starting provider *acme.ChallengeTLSALPN"}
{"level":"info","time":"2024-05-20T13:23:05+02:00","message":"Starting provider *acme.Provider"}
{"level":"info","providerName":"letsencrypt.acme","acmeCA":"https://acme-v02.api.letsencrypt.org/directory","time":"2024-05-20T13:23:05+02:00","message":"Testing certificate renew..."}

Connexion au dashboard

Il en vous reste plus qu'à se connecter au dashboard. Cela vous demandera de vous authentifier avec le user et le mot de passe créés précédemment.

traefik-dashboard-accueil
traefik-dashboard-routage

Mise Ă  jour de l'article

  • 22/06/2024 :
    • Mise Ă  jour de l'arborescence.
    • Utilisation de Secrets au lieu des variables d'env pour le challenge DNS.