Haute disponibilité, répartition de charge avec Traefik & Kubernetes

La répartition de charge par reverse-proxy est bien connue mais est souvent synonyme de configuration à rallonge, dans une infrastructure en constante évolution, Traefik s’impose comme un outil révolutionnaire.
Dans cet article, j’expliquerai comment j’ai configuré Traefik avec l’orchestrateur de conteneur Kubernetes. Mon environnement n’est certainement pas le même que le votre donc si certains points de l’article ne fonctionnent pas pour vous, referez vous à la documentation officielle.
Je ne vous indiquerai pas comment installer Traefik ni Kubernetes, referez vous aux documentations officielles pour cela.
Liens de documentations:
Traefik: https://docs.traefik.io/
Kubernetes: https://kubernetes.io/fr/docs/home/
Qu’est-ce que Kubernetes ?
Kubernetes est un orchestrateur de conteneur. C’est même l’un des orchestrateurs le plus connu aujourd’hui grâce à son développement rapide et collaboratif. C’est un outil open source supporté par plusieurs sociétés comme Google, Amazon et IBM et bien d’autres organismes comme le CNCF (Cloud Native Computing Foundation).
On pourrait comparer Kubernetes à un hyperviseur. Il met à disposition plusieurs composants à la solution finale. Là ou un hyperviseur fournit la gestion réseau, stockage, virtualisation et d’autres services indépendants de sa fonction, Kubernetes fourni une interface de gestion par API, du service discovery (via DNS), de l’autoscaling, une architecture de gestion (espace de noms, objets customisés, etc…) ainsi que des moyens de déploiement supplémentaires comparé aux hyperviseurs. Mais il ne remplace pas les moteurs de conteneurisation tel que Docker ou cri-o. Il est d’ailleurs compatibles avec ces derniers (voir d’autres).
Je parlerai plus en détail de Kubernetes et Docker dans de prochains articles.
Article sur Kubernetes: C’est quoi Kubernetes et pourquoi l’utiliser ?
Qu’est-ce que Traefik
Traefik est un reverse proxy destiné pour être déployé dans une architecture « cloud » (hyper-convergé). Un reverse proxy sert principalement à cacher l’adresse IP d’un site et à répartir la charge entre plusieurs serveurs. Ici, Traefik rempli ces fonctions mais ajoute des petites touches telles que l’acquisition automatique de certificats TLS via Let’s Encrypt ou la modification du comportement de la requête via des middlewares. Le logiciel est disponible via une image Docker ou en « standalone ». Il optionnellement relié à un orchestrateur (Kubernetes par exemple) ou à Docker, traefik obtiendra la liste des services de ce dernier afin de router la requête.

Comment je les utilise
Dans mon environnement, j’utilise Traefik déployé en tant que DaemonSet (c’est à dire, une instance du logiciel sur chaque serveur de mon cluster Kubernetes).
Pour pouvoir fonctionner avec mes paramètres réseau, j’ai du le mettre dans l’espace de nom réseau de l’hôte (c’est-à-dire qu’il voit toutes les interfaces réseau de l’hôte). Sans cela, impossible d’utiliser le port 80 avec ma configuration Kubernetes. Mais cela veut dire que par défaut, si un service n’utilise pas le port 80, il sera occupé par Traefik, ce n’est pas tellement problématique mais il faut le signaler.
Pour Kubernetes, je suis en version 1.16.3 (je sais je ne suis pas à jour !) avec le CNI (Container Network Interface) Calico. Ce dernier n’autorise pas l’utilisation de « hostPort » qui m’aurait permis d’utiliser le port 80 sans utiliser le paramètre « hostNetwork ». Passer par un service en « nodePort » non plus (c’est une plage de port réservé). Le type de service « LoadBalancer » de Kubernetes permet l’utilisation du port 80 mais nécessite une adresse IP publique supplémentaire routée sur les noeuds Kubernetes, ce que je ne peux pas encore faire à cause des limitations de mon fournisseur.
Comment il faudrait les utiliser
Pour une configuration optimale et résiliente, Traefik doit être déployé en tant que « Deployment » sur Kubernetes mais avec uniquement une instance par noeud (sinon on perd l’IP du visiteur), de ce fait, le nombre d’instance pourra être modifié afin de tenir la charge avec de l’auto-scaling horizontal (selon le nombre de noeud).
Ensuite, l’utilisation d’un service Kubernetes de type « LoadBalancer » est conseillé avec le paramètre « externalTrafficPolicy » en « Local » afin d’annoncer le service que depuis les noeuds ou traefik est lancé tout en gardant l’adresse IP d’origine du visiteur. Cela revient à faire un service « Anycast » ou l’adresse IP à plusieurs points de terminaisons (Si 3 instances lancées sur 3 noeuds (une par noeud), le service sera routé sur 3 noeuds et le trafic sera balancé équitablement).
Pour que l’adresse soit correctement routé vers les noeuds Kubernetes, le logiciel MetalLB permet d’annoncer les IP en arp ou en bgp mais ne sera pas nécessaire si vous utilisez Kubernetes via un fournisseur tel que AWS (AKS) ou Google Cloud (GKE) car ils gèrent pour vous l’adressage et le routage de ce type de service.
Configuration de Traefik
Dans mon environnement, Traefik est configuré via les arguments de lancement. Ma configuration permet l’accès http/https, la remontée de métriques prometheus (statistiques de monitoring), l’utilisation d’objets Kubernetes lié à Traefik (on y revient plus bas) et la génération automatique de certificats TLS Let’s Encrypt pour les sessions https pour les domaines wildcard ou non.
Voici mon objet DaemonSet dans Kubernetes :
kind: DaemonSet
apiVersion: apps/v1
metadata:
name: traefik
namespace: infra
spec:
selector:
matchLabels:
app: traefik
template:
metadata:
creationTimestamp: null
labels:
app: traefik
name: traefik
spec:
volumes:
- name: traefik
persistentVolumeClaim:
claimName: fs-traefik-pvc
containers:
- name: traefik
image: 'traefik:v2.3'
args:
- '--accesslog'
- '--entrypoints.http.address=:80'
- '--entrypoints.https.address=:443'
- '--entrypoints.metrics.address=:8089'
- '--metrics.prometheus=true'
- '--metrics.prometheus.entryPoint=metrics'
- '--providers.kubernetescrd'
- '--certificatesResolvers.letsencrypt.acme.httpChallenge=true'
- >-
--certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=http
- >-
--certificatesResolvers.letsencrypt.acme.email=redacted
- >-
--certificatesResolvers.letsencrypt.acme.storage=/storage/acme.json
- >-
--certificatesResolvers.letsencrypt.acme.caserver=https://acme-v02.api.letsencrypt.org/directory
- >-
--certificatesResolvers.letsencrypt.acme.dnsChallenge.provider=cloudflare
- >-
--certificatesResolvers.letsencrypt.acme.dnsChallenge.delayBeforeCheck=60
ports:
- name: http
hostPort: 80
containerPort: 80
protocol: TCP
- name: https
hostPort: 443
containerPort: 443
protocol: TCP
- name: admin
hostPort: 8089
containerPort: 8089
protocol: TCP
env:
- name: CF_API_EMAIL
value: *Redacted*
- name: CF_API_KEY
value: *Redacted*
resources:
limits:
cpu: 750m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
volumeMounts:
- name: traefik
mountPath: /storage
securityContext:
capabilities:
add:
- NET_BIND_SERVICE
drop:
- ALL
serviceAccountName: traefik-ingress-controller
serviceAccount: traefik-ingress-controller
hostNetwork: true
Il y a un volume de stockage monté sur /storage afin de stocker les certificats générés.
Pour ce qui est du conteneur, j’utilise l’image traefik:v2.3 donc la version 2.3.
Passons aux arguments, la partie la plus intéressante car c’est là où la configuration se fait.
--accesslog
Permets d’afficher les logs de chaque requête dans le stdout (logs du conteneur)--entrypoints.http.address=:80
C’est là où on nome notre point d’entrée des connexions, ici son nom est « http » et écoute sur toute IP avec le port 80--entrypoints.https.address=:443
Pareil, mais pour https--entrypoints.metrics.address=:8089
Pareil, mais pour les métriques prometheus--metrics.prometheus=true
Activation du module de rapport de métriques prometheus--metrics.prometheus.entryPoint=metrics
Et on lui dit de mettre ça sur le point d’entrée « metrics » défini plus haut--providers.kubernetescrd
Ici, c’est un paramètre important, sans ça, il ne prendra pas en compte les routes http (relation entre le domaine et le service Kubernetes) ni les middlewares.--certificatesResolvers.letsencrypt.acme.httpChallenge=true
On configure Let’s Encrypt pour utiliser une méthode de vérification par requête HTTP.--certificatesResolvers.letsencrypt.acme.httpChallenge.entryPoint=http
Et on lui dit de faire la vérification HTTP sur le point d’entrée « http » configuré plus haut--certificatesResolvers.letsencrypt.acme.email=mail
Let’s Encrypt requiert la configuration d’une adresse e-mail pour les informations de sécurité ou pour les alertes de certificats à renouveler, ici Traefik renouvelle automatiquement chaque certificat avant qu’un mail ne soit envoyé par Let’s Encrypt.--certificatesResolvers.letsencrypt.acme.storage=/storage/acme.json
On configure ou Traefik va écrire les certificats générés par Let’s Encrypt. Attention a bien indiquer un fichier dans le volume de stockage sinon il sera vidé à chaque redémarrage de Traefik.--certificatesResolvers.letsencrypt.acme.caserver=https://acme-v02.api.letsencrypt.org/directory
Afin d’indiquer qu’on veut utiliser le serveur d’autorité de production. Si vous désirez faire des tests, utilisez https://acme-staging-v02.api.letsencrypt.org/directory
--certificatesResolvers.letsencrypt.acme.dnsChallenge.provider=cloudflare
Cette ligne sert à vérifier par challenge DNS les certificats wildcards (*.otux.cloud par exemple) avec cloudflare (si votre fournisseur dns est cloudflare)--certificatesResolvers.letsencrypt.acme.dnsChallenge.delayBeforeCheck=60
Sert à indiquer le temps que Let’s Encrypt doit attendre avant d’effectuer son challenge DNS après l’avoir demandé. Cela règle un bug présent du côté des binaires de Let’s Encrypt lié à Cloudflare qui vérifiait la présence d’un champ DNS TXT avant que ce dernier ne soit créé. Ce problème semble aussi être présent sur le « cert-manager » de Kubernetes mais je n’ai pas trouvé de fix.
Maintenant, parlons des variables d’environnement. Elles sont peu nombreuses car elle ne servent qu’à indiquer les informations CloudFlare pour que le challenge DNS fonctionne.CF_API_EMAIL
Indiquez simplement l’adresse e-mail du compte CloudFlareCF_API_KEY
Indiquez la « Global API Key » de votre compte CloudFlare
Je ne détaillerai pas la partie « resources » ni « volumeMounts » car elles sont assez explicites.
La partie « securityContext » permet d’ajouter ou retirer des capacités au conteneur, ici on lui enlève toutes les permissions sauf celle d’utiliser un port réseau.
La ligne la plus importante est « hostNetwork: true », sans ça Traefik n’écoute pas sur les IP du noeud Kubernetes mais sur l’IP du conteneur et ne sera pas accessible depuis internet (ce qui est quand même le but).
Routage d’un domaine vers un service
Pour router un domaine vers un service, il faut créer des objets spécifiques à Traefik appelé « IngressRoute » dans la base de données de Kubernetes. Pourquoi « des » et pas « un » ? Car traefik fonctionne par point d’entrée, donc il faut une route pour http et une autre pour https !
Dans ma configuration, chaque requête http est mise à niveau vers https grâce au middleware « redirectScheme », voici l’objet correspondant que j’ai nommé « https-only » dans l’espace de nom d’objets « clients »:
kind: Middleware
apiVersion: traefik.containo.us/v1alpha1
metadata:
name: https-only
namespace: clients
spec:
redirectScheme:
scheme: https
Il est plutôt court car il ne fait pas grand-chose à part rediriger.
Ensuite, les objets « IngressRoute » peuvent l’utiliser dans leurs configurations.
Pour l’exemple j’utilise la configuration de mon site.
Voici la route pour le point d’entrée « https »:
kind: IngressRoute
apiVersion: traefik.containo.us/v1alpha1
metadata:
name: guillaumeouint.fr
namespace: clients
spec:
entryPoints:
- https
routes:
- kind: Rule
match: 'Host(`guillaumeouint.fr`, `www.guillaumeouint.fr`)'
services:
- name: guillaumeouint-fr
port: 80
tls:
certResolver: letsencrypt
Ici, les hôtes guillaumeouint.fr et www.guillaumeouint.fr sont routés vers le service Kubernetes nommé « guillaumeouint-fr » sur le port 80. (Notez qu’en effet, le https n’est pas de bout en bout). Et on utilise Let’s Encrypt en tant que fournisseur de certificat TLS.
Et voici la configuration pour le point d’entrée « http »:
kind: IngressRoute
apiVersion: traefik.containo.us/v1alpha1
metadata:
name: guillaumeouint.fr-http
namespace: clients
spec:
entryPoints:
- http
routes:
- kind: Rule
match: 'Host(`guillaumeouint.fr`, `www.guillaumeouint.fr`)'
middlewares:
- name: https-only
services:
- name: guillaumeouint-fr
port: 80
C’est sensiblement la même chose à quelques détails près. Ici on utilise le middleware « https-only » défini plus haut afin de rediriger l’utilisateur sur le point d’entrée « https ».
Attention, si aucun service n’est indiqué, la route ne sera pas prise en compte par Traefik.
Le concept de service Kubernetes est développé dans l’article dédié à Kubernetes mais pour vulgariser, c’est un répartiteur de charge dynamique.
Conclusion
Nous voici déjà à la fin de ce premier article sur ce blog. J’espère qu’il vous aura aidé dans votre périple avec Kubernetes et Traefik ! Il sera certainement mis à jour s’il y a des demandes pour.
Si vous avez des questions, n’hésitez pas à laisser un commentaire.
