Machins de dev

Traefik

Traefik-wan

Je me suis mis à tater traefik ce week-end afin d’avoir quelque chose de plus fiable avec Let’s Encrypt, que mon script un peu bancale. En effet il s’agit d’un reverse proxy écrit en go, pensé micro-services et, avec intégration Let’s Encrypt.

Cela rentre à priori parfaitement dans mon cadre d’utilisation. À savoir, 90% de ce que je souhaite publier, que cela soit interne ou public, sont des containers Docker. Est-ce que traefik pourra remplacer nginx, mon reverse proxy actuel ? C’est ce que nous allons voir.

Refactorisation

Dans mon architecture précédente, je mélangeais un peu les rôles de reverse proxy et serveur http. Les deux rôles étant gérés par nginx, cela me permettait d’éviter un petit gaspillage de RAM en ayant plusieurs instance de nginx. Lorsqu’on héberge tout sur seulement 32Go, on essaie de grapiller là où l’on peut ! Mais c’est de l’histoire ancienne cela et je peux bien sacrifier quelques Mo supplémentaires depuis que j’ai upgradé mon nuc à 64Go de RAM. Mon hyperviseur est cosy maintenant.

L’intérêt est qu’en utilisant traefik, je m’oblige à séparer les deux rôles et donc à avoir quelque chose de plus propres. En effet, si je souhaite migrer un service, je n’ai qu’à le rsync sur une autre machine et faire chauffer les containers. Il n’est plus nécessaire de porter la configuration du reverse proxy à la main, puisque celle-ci est intégrée à ma stack Docker. Et surtout l’ajout d’un backend ne nécessite pas de recharger traefik, contrairement à nginx.

Et l’autre atout phare, comme mentionné précédemment, est l’intégration directe de Let’s Encrypt. Il n’est plus nécessaire d’écrire un script, de le lancer régulièrement pour quémander un nouveau certificat, remplacer le fichier et raffraichir la conf du reverse proxy. traefik le fait pour toi, et ça marche du premier coup !

J’ai pu ainsi passer de ça :

 

nginx en tant que reverse proxy, ssl endpoint et serveur http

À ca :

traefik comme reverse proxy et ssl endpoint, nginx en tant que serveur http

Bon ok, en image ce n’est pas transcendent.

Mais la conf est cool

Le container traefik pèse 80Mo là où nginx se content de 20Mo. On reste sur du container léger et la différence de taille provient surtout du binaire traefik (68Mo à lui tout seul). On reconnait bien ici la signature go.

Pour l’installer, rien de plus simple qu’un docker-compose.yml par défaut avec :

En option, j’ai fixé l’ip du container sur la pate DMZ. traefik ajoute également les en-têtes X-Forwarded-For* aux backends. Avoir une ip fixe facilite la configuration du trust du reverse proxy au niveau des backends.

Note: le choix de l’ip dépend bien évidemment du subnet avec lequel le réseau dmz a été créé (explicitement ou implicitement).

version: '3.5'

networks:
  dmz:
    external:
      name: dmz

services:
  traefik:
    image: traefik:1.7-alpine
    ports:
#      - 80:80
      - 443:443
#      - 8443:443
      - 1443:1443
    networks:
      dmz:
        ipv4_address: 172.16.0.126
    volumes:
      - ./traefik.toml:/etc/traefik/traefik.toml:ro
      - ./acme.json:/etc/traefik/acme.json
      - /var/run/docker.sock:/var/run/docker.sock

Le fichier de configuration de traefik, traefik.toml, va permettre de configurer :

Mis à part le cas nexus que je détaillerais plus bas, ce fichier de configuration est complètement agnostique de ce que je vais coller au cul de traefik. Exit la conf d’un virtual host qui fait péter tout le serveur !

defaultEntryPoints = ["https"]
# i will manage updates through watchtower
checkNewVersion = false
insecureSkipVerify = true

[entryPoints]
  [entryPoints.http]
  address = ":80"
  [entryPoints.https]
  address = ":443"
    [entryPoints.https.tls]
    minVersion = "VersionTLS12"
    sniStrict = true
#    cipherSuites = [
#      "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
#      "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
#      "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305"
#    ]
  [entryPoints.nexus-registry]
  address = ":8082"
    [entryPoints.nexus-registry.tls]
    minVersion = "VersionTLS12"
    sniStrict = true
  [entryPoints.traefik]
  address = ":1443"
    [entryPoints.traefik.tls]
    minVersion = "VersionTLS12"
#    cipherSuites = [
#      "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
#      "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
#      "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305"
#    ]

[acme]
email = "veovis@kveer.fr"
storage = "/etc/traefik/acme.json"
entryPoint = "https"
KeyType = "EC256"
acmeLogging = true
onHostRule = true

[acme.tlsChallenge]

[docker]
endPoint = "unix:///var/run/docker.sock"
domain = "home.kveer.fr"
watch = true
exposedByDefault = false

[api]
entryPoint = "traefik"
dashboard = true

Préparer Docker

Comme mentionné précédemment, j’utilise un réseau docker nommé dmz pour relier traefik avec les backends. A part le nom et un espace suffisamment grand, c’est un réseau tout ce qu’il y a de plus normal:

docker network create --subnet 172.16.0.64/26 dmz

Ajouter des backends Docker à traefik

Pour connecter un container à traefik, grâce à la configuration de traefik, tout se fait dans la définition même du container, au moyens de labels. Cela signifie que:

Par exemple voici la configuration de nginx sur la stack faisant marcher ce blog:

 

 

version: '3.5'

networks:
  dmz:
    external: true
    name: dmz
  internal:
    internal: false

services:
  nginx:
    image: nginx:alpine
    restart: unless-stopped
    depends_on:
      - app
    networks:
      dmz:
      internal:
    environment:
      TZ: Europe/Paris
    labels:
      traefik.frontend.rule: 'Host: blog.kveer.fr'
      traefik.backend: blog
      traefik.domain: kveer.fr
      traefik.protocol: h2c
      traefik.enable: true
      traefik.docker.network: dmz
      traefik.frontend.headers.STSPreload: true
      traefik.frontend.headers.STSSeconds: 315360000
    volumes:
      - ./app-files:/var/www/html
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
  [...]

Par défaut, le protocol utilisé pour communiquer avec le backend est http mais il est aussi possible de spécifier h2c ou https.

h2c est utilisé pour autoriser le HTTP2 sur un canal non-SSL. Considérant la connexion traefik ↔ nginx comme totalement fiable, cela me permet de bénéficier des atouts du HTTP2 sans le surcoût du chiffrement dû à la couche SSL.

https est aussi disponible, notamment lorsque le backend ne support QUE ce moyen de transport. C’est par exemple le cas de unms de Ubiquity.

Rediriger vers du non Docker

Bien que traefik s’interface très bien dans un monde pleins de containers, il est aussi capable de servir de reverse proxy vers des backends autre, par exemple mon NAS.

En effet traefik dispose de pleins de providers autre que Docker, dont File. Celui-ci permet de définir des frontends et backends à la main.

Par exemple pour proxifier la page web de mon NAS, j’ai simplement du déclarer un frontend et un backend dans le fichier de configuration de traefik.

[file]
[frontends]
  [frontends.nas]
  backend = "nas"
    [frontends.nas.routes.main]
    rule = "Host: mon-nas.home.kveer.fr"

[backends]
  [backends.nas]
    [backends.nas.servers]
      [backends.nas.servers.server0]
      url = "https://192.168.77.56:443"
      weight = 1

1 container, 2 backends

traefik sait aussi gérer des cas plus particuliers. Celui de nexus par exemple, qui est un gestionnaire de dépôt open-source. Nexus gère des dépôts de plusieurs type (docker, nuget…) et à ce titre nécessite des endpoints différents.

À l’aide d’une syntaxe étendue, traefik support la définition de plusieurs frontends et backends. Pour nexus, j’ai défini deux couples, web et registry, ayant chacun un frontend et un backend.

Cela permet de voir que traefik fonctionne vraiment comme un passe-plat :

 

version: '3.5'

networks:
  dmz:
    external: true
    name: dmz

services:
  nexus3:
    image: sonatype/nexus3
    restart: unless-stopped
    ulimits:
      nofile: 65536
    environment:
      INSTALL4J_ADD_VM_PARAMS: -Xms500m -Xmx500m -XX:MaxDirectMemorySize=2g -Djava.util.prefs.userRoot=/nexus-data/javaprefs
    labels:
      traefik.frontend.whitelist.sourceRange: "192.168.77.0/24"
      traefik.web.frontend.rule: 'Host: nexus.kveer.fr'
      traefik.web.port: 8081
      traefik.web.backend: nexus-web
      traefik.registry.frontend.rule: 'Host: nexus.kveer.fr'
      traefik.registry.port: 8082
      traefik.registry.backend: nexus-registry
      traefik.registry.frontend.entryPoints: nexus-registry
      traefik.domain: kveer.fr
      traefik.enable: true
      traefik.docker.network: dmz
      traefik.frontend.headers.STSPreload: true
      traefik.frontend.headers.STSSeconds: 315360000
    networks:
      - dmz
    volumes:
      - ./data:/nexus-data

On remarquera au passage l’utilisation du label traefik.frontend.whitelist.sourceRange, qui permet de restreindre l’accès à un frontend (ici tous ceux de nexus) à un subnet particulier.

Egalement l’ajout de l’en-tête HSTS avec les deux labels traefik.frontend.headers.STSPreload et traefik.frontend.headers.STSSeconds.

Parce qu’il faut bien râler

Au final traefik a entièrement rempli sa mission. Mais c’est un produit encore relativement jeune, notamment sur les points suivants :

Cela dit, ce sont des points assez mineurs. traefik, je valide.