Rendre un service accessible publiquement

Quand un conteneur Docker doit être accessible depuis l’extérieur de la machine, il y a deux solutions :

  • Mapper un port de l’hôte sur un port du conteneur
  • Utiliser Traefik

Important:

La règle générale est la suivante : on utilise un mapping de port pour les services non-web (e.g. LDAP, mail, SFTP…) et Traefik pour tous les services web. Une explication se trouve sur la page dédiée à Traefik.

C’est la technique la plus simple. Supposons qu’un conteneur écoute sur son port interne 8080, et qu’on veuille le rendre accessible depuis l’extérieur. On va choisir arbitrairement un port de l’hôte que l’on va mapper dessus, par exemple :

snippet.yaml
services:
  example:
    image: <image>
    ports:
      - 2000:8080

Dans cet exemple, tout ce qui arrive sur le port 2000 de la machine sera redirigé sur le port 8080 du conteneur.

Attention:

Par contre, pour tout ce qui concerne la gestion de TLS ou d’autres fonctionnalités de Traefik, on n’en profite pas, car on ne passe pas du tout par lui.

Pour les services web, on préfère largement passer par Traefik, car on pourra utiliser un port standard (80 ou 443) et profiter de toutes ses fonctionnalités (HTTPS automatique, etc).

On configure le fait de router vers un conteneur grâce aux labels Docker. Voici un extrait commenté permettant de router toutes les requêtes arrivant sur Traefik à destination de app.picasoft.net vers le conteneur exemple, sur son port 8080 :

snippet.yaml
networks:
  proxy:
    external: true

services:
  exemple:
    container_name: example
    networks:
      - proxy
    labels:
      # Traefik va prendre ce conteneur en compte
      traefik.enable: true
      # websecure correspond au port 443 de la machine (config Traefik)
      # Remplacer <exemple> par le nom du conteneur
      traefik.http.routers.<exemple>.entrypoints: websecure
      # Il redirigera vers ce port, exposé par le conteneur
      # Remplacer <exemple> par le nom du conteneur
      # Remplacer app.picasoft.net par l'URL souhaitée
      traefik.http.routers.<exemple>.rule: Host(`app.picasoft.net`)
      # Lorsque l'utilisateur consulte cette URL
      # Remplacer <exemple> par le nom du conteneur
      # Remplacer 8080 par le port du service
      traefik.http.services.<exemple>.loadbalancer.server.port: 8080

Important:

Il est essentiel que le conteneur soit dans le même réseau que Traefik, qui est fixé à proxy ! C’est le sens des directives networks.

websecure désigne, dans la configuration Traefik, le port 443. Tout ce qui arrive vers Traefik est redirigé vers cet entrypoint, il faut donc se coller dessus.

Note:

Il est possible d’être plus fin sur l’URL à laquelle répondra le conteneur, et même d’utiliser des chemins spécifiques ou des expressions régulières. Voir la documentation officielle.

Il y a pas mal de cas d’utilisation où une sous-partie d’un site doit être sécurisée par authentification :

  • La partie /admin d’un service exposé sur internet mais sans authentification intégrée au service
  • La partie /metrics d’un service qui doit être accessible à la solution de monitoring mais pas aux internautes…

La solution consiste à créer un second point d’entrée à l’aide des labels. En reprenant l’exemple de la section précédente, on rajouterait ces labels :

snippet.yaml
# Toujours le même point d'entrée : HTTPS
traefik.http.routers.<exemple>-metrics.entrypoints: websecure
# Ce point d'entrée sera sélectionné uniquement pour les requêtes
# vers app.picasoft.net/metrics et ses dérivés.
traefik.http.routers.<exemple>-metrics.rule: "Host(`app.picasoft.net`) && PathPrefix(`/metrics`)"
# Indique que ce point d'entrée passe par un middleware nommé <exemple>-metrics-auth et qui est
# configuré via les labels Docker (voir juste en dessous)
traefik.http.routers.<exemple>-metrics.middlewares: "<exemple>-metrics-auth@docker"
# Ce middleware est simplement une authentification, qui vient se mettre entre le point d'entrée
# et l'application.
traefik.http.middlewares.<exemple>-metrics-auth.basicauth.users: "test:$2y$10$7Gv5tUZC1UBsTyQs/ZTwmu0jamzBJFnNEY4SMxqdfIYfTyLBDTINm"

La chaîne $2y$10$7Gv5tUZC1UBsTyQs/ZTwmu0jamzBJFnNEY4SMxqdfIYfTyLBDTINm correspond au mot de passe test hashé avec bcrypt.

Important:

Pour éviter de versionner la chaîne d’identification, on préfère plutôt utiliser une variable pour le label basicauth.users, qui vaudra “${METRICS_AUTH}”. La valeur sera placée dans un fichier .env sur la machine de production, sous le format suivant :

METRICS_AUTH=test:$2y$10$7Gv5tUZC1UBsTyQs/ZTwmu0jamzBJFnNEY4SMxqdfIYfTyLBDTINm

En effet, Docker Compose lit les fichiers .env et remplace les variables par leur valeur dans le fichier Compose. De plus, Git ignore les fichiers cachés par défaut, on ne risque pas de commiter les secrets par erreur.

A Savoir:

Pour générer la chaîne d’authenticiation Basic Auth qui est donnée au label Traefik, on commence par définir un nom d’utilisateur (ici test) et par générer un mot de passe (par exemple avec pwgen -sB 64). On place ces identifiants dans le vaultwarden.
On utilise ensuite la commande suivante pour générer la chaîne d’authentification qu’il faut renseigner dans le label.

$ echo $(htpasswd -nb USER PASSWORD)
USER:$apr1$4WpORg/6$.bUMzTnrDPivUXfu7.vvP0

  • technique/docker/good_practices/traefik.txt
  • de 127.0.0.1