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 :
À ca :
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 :
- les ports qu’on souhaite exposer à l’extérieur
- un réseau « DMZ » qui sera commun à traefik et les points d’entrée de mes autres stack
- le fichier de configuration de traefik.toml
- un fichier nommé acme.json et qui va contenir les clés privées générées et certificats émis par Let’s Encrypt
- on trust traefik en lui donnant l’accès à la socket de contrôle de Docker pour qu’il détecte automatiquement les containers et les intègre en fonction de la configuration
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 :
- s’il faut auto-demander des certificats auprès de Let’s Encrypt et quel type de clé privée générer, EC256 pour ma part plutôt que le fat RSA
- les versions de TLS activés ainsi que les ciphers disponibles
- et le plus important: d’ajouter un container automatiquement, s’il a les labels qu’il faut
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:
- je n’ai pas besoin de toucher à la configuration principale de traefik
- je n’ai pas besoin de redémarrer traefik
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 :
- le frontend, qui est un endpoint exposé par traefik pour accéder à un backend (ou plusieurs dans le cas d’une configuration en failover ou load-balancing)
- un ou des backends, qui sont des endpoints que peut appeler traefik
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 :
- impossibilité de demander un certificat RSA pour un frontend particulier (tout RSA ou tout ECDSA, pas de mix possible).
- impossibilité d’autoriser un certificat invalide pour un backend particulier (tout ou rien ici encore)
- parfois traefik ne voit pas le redémarrage d’un container, il faut alors le restarter à nouveau pour qu’il soit vu et intégré correctement
Cela dit, ce sont des points assez mineurs. traefik, je valide.
No Comments