Aujourd’hui, nous déployons de plus en plus d’applications et de micro-services dans Kubernetes. Gérer tous les points d’entrée de ces applications peut être problématique. Pour faciliter cette gestion, il existe des ingress controller, Traefik est l’un d’entre eux

Traefik 2 - Un ingress controller pour les contrôler tous

Pourquoi utiliser Traefik ?

  • Pour faire du reverse proxy simplement
  • Pour centraliser toute la gestion des certificats
  • Pour faire du loadbalancing TCP (disponible uniquement depuis Traefik 2)
  • Pour faire de l’autodiscovery depuis Kubernetes
  • Pour charger les nouveaux services dynamiquement, sans interruption de service
  • Pour faire du multibackend (Mesos, Kubernetes, Docker etc…)

Image provenant du site officiel

Je vous propose donc ci-dessous un petit “hands on” Traefik 2, où nous déploierons Traefik dans un daemonset et nous mettrons un service “whoami” derrière. Nous mettrons aussi en place un certificat via Let’s Encrypt.

Le but, vous l’aurez compris, est de manipuler et voir les possibilités offertes par Traefik.

Prérequis

Nous allons dans un premier temps déployer des CRD Kubernetes (Custom Resources Definition), le but de ces dernières est de mettre à disposition des objets que Traefik va utiliser pour faciliter son déploiement.

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressroutes.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRoute
    plural: ingressroutes
    singular: ingressroute
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: middlewares.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: Middleware
    plural: middlewares
    singular: middleware
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ingressroutetcps.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: IngressRouteTCP
    plural: ingressroutetcps
    singular: ingressroutetcp
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: tlsoptions.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: TLSOption
    plural: tlsoptions
    singular: tlsoption
  scope: Namespaced

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: traefikservices.traefik.containo.us

spec:
  group: traefik.containo.us
  version: v1alpha1
  names:
    kind: TraefikService
    plural: traefikservices
    singular: traefikservice
  scope: Namespaced

Déploiement de Traefik

Maintenant que nous avons déployé nos prérequis, nous allons pouvoir mettre en place un RBAC pour Traefik que nous déploierons en temps que Daemonset.

Le RBAC permettra à Traefik de faire l’autodiscovery sur Kubernetes, il utilisera des droits en lecture seule sur plusieurs objets.

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller

rules:
  - apiGroups:
      - ""
    resources:
      - services
      - endpoints
      - secrets
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses/status
    verbs:
      - update
  - apiGroups:
      - traefik.containo.us
    resources:
      - middlewares
      - ingressroutes
      - traefikservices
      - ingressroutetcps
      - tlsoptions
    verbs:
      - get
      - list
      - watch

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller

roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik-ingress-controller
subjects:
  - kind: ServiceAccount
    name: traefik-ingress-controller
    namespace: default
    
---
apiVersion: v1
kind: ServiceAccount
metadata:
 name: traefik-ingress-controller
 namespace: default

Une fois le ServiceAccount créé et configuré pour être utilisé, nous pouvons déployer notre agent Traefik en tant que DaemonSet, en plus d’un service Traefik pour porter le dashboard.

À propos du déploiement en tant que DaemonSet

Dans ce hands on, j’ai choisi de déployer Traefik en tant que DaemonSet, il sera donc déployé sur l’ensemble des noeuds de mon cluster Kubernetes. C’est le mode de déploiement qui est souvent choisi pour les ingress controllers afin d’avoir une forte redondance de cette brique critique, d’être au plus proche des backends et donc d’optimiser les flux et les ressources.

apiVersion: apps/v1
kind: DaemonSet
metadata:
 name: traefik-ingress-controller 
 labels:
   k8s-app: traefik-ingress-lb
   kubernetes.io/cluster-service: "true"
spec:
 selector:
   matchLabels:
      k8s-app: traefik-ingress-lb
 template:
   metadata:
     labels:
       k8s-app: traefik-ingress-lb
       name: traefik-ingress-lb
   spec:
     hostNetwork: true
     dnsPolicy: ClusterFirstWithHostNet
     serviceAccountName: traefik-ingress-controller
     terminationGracePeriodSeconds: 60
     tolerations:
     - key: node-role.kubernetes.io/master
       effect: NoSchedule
     containers:
     - image: traefik:v2.1.1
       name: traefik-ingress-lb
       imagePullPolicy: Always
       volumeMounts:
         - mountPath: "/cert/"
           name: cert
       resources:
         requests:
           cpu: 100m
           memory: 20Mi
       args:
       - --providers.kubernetescrd
       - --entrypoints.web.address=:80
       - --entrypoints.websecure.address=:443
       - --api.insecure
       - --certificatesresolvers.le.acme.email=masuperadresse@monmail.com
       - --certificatesresolvers.le.acme.storage=/cert/acme.json
       - --certificatesResolvers.le.acme.httpChallenge.entryPoint=web
     volumes:
     - name: cert
       hostPath:
         path: /home/kube/traefik/certs/
         type: Directory
---

apiVersion: v1
kind: Service
metadata:
 name: traefik-web-ui
spec:
 selector:
   k8s-app: traefik-ingress-lb
   app: traefik-ingress-lb
 ports:
 - port: 8080
   targetPort: 8080
   name: api
 - port: 443
   targetPort: 443
   name: websecure
 - port: 80
   targetPort: 80
   name: web

Comme on peut le voir dans le bloc ci-dessus, on déploie donc Traefik, ainsi que ses services, pour exposer les ports http et https, ainsi que le dashboard.

Attention, dans cette configuration, le dashboard est déployé sans authentification !

De plus, je créé un répertoire local dans lequel je stockerai mes certificats, car Let’s Encrypt limite le nombre de requêtes hebdomadaires pour un même certificat. À noter que Traefik, dans sa version communautaire ne gère pas ses certificats en mode “cluster”, cette option est spécifique à la version Entreprise.

Traefik 1.7 TraefikEE 1 Traefik 2.1 TraefikEE 2
Load Balancing HTTP Oui Oui Oui Oui
Load Balancing TCP Non Non Oui Oui
Certificats Let’s Encrypt Oui, mais nombre de requêtes limitées Oui, nombre de requêtes illimités Oui, mais nombre de requêtes limitées Oui, nombre de requêtes illimités
Configuration distribuée Non Oui, centralisée sur des noeuds "Control plane" Oui Oui, centralisée sur des noeuds "Control plane"
Authentification LDAP Non Non Non Oui

Plus d'informations sur TraefikEE 1 ici et TraefikEE 2 ici

En configuration “prod ready”, on pourra bien sûr déporter ce stockage vers une ressource partagée plutôt qu’un stockage local sur le noeud Kube, qui est à proscrire dans la plupart des cas. Néanmoins, dans le cas d’une gestion “ACME” des certificats, cela peut poser souci, le noeud qui requête le certificat n’étant pas forcément le noeud qui le reçoit.

À ce point, vous devriez pouvoir accéder au dashboard de Traefik sur le port 8080.

Source : Documentation officielle

Déploiement d’un backend : Whoami

Pour terminer notre déploiement, je vais ajouter un backend, basé sur l’image containous/whoami.

Nous allons donc créer un deployment qui mettra en place notre pod.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: whoami
  labels:
    app: whoami
spec:
  replicas: 3
  selector:
    matchLabels:
      app: whoami
  template:
    metadata:
      labels:
        app: whoami
    spec:
      containers:
      - name: whoami
        image: whoami:v1.4.0
        imagePullPolicy: Always
        ports:
        - containerPort: 80

Enfin, nous allons ajouter un service, qui permettra d’exposer nos containers au sein de notre cluster Kubernetes, puis un ingressroute, objet que nous avons créé via les CRD plus tôt, qui permet de porter toute la configuration nécessaire à Traefik.

kind: Service
apiVersion: v1
metadata:
  name: whoami
spec:
  ports:
  - port: 80
    name: web
    protocol: TCP
  selector:
    app: whoami
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: whoami-web
spec:
  entryPoints:
    - web
  routes:
  - kind: Rule
    match: Host(`mydomain.com`) && PathPrefix(`/`)
    services:
    - name: whoami
      port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: whoami-web-tls
spec:
  entryPoints:
    - websecure
  routes:
  - kind: Rule
    match: Host(`mydomain.com`) && PathPrefix(`/`)
    services:
    - name: whoami
      port: 80
  tls:
    certResolver: le

Concernant les ingressroute, on peut observer :

  • Qu’il y a deux définitions, une en http et une en https
  • Que la partie https est configurée pour requêter automatiquement son certificat auprès de Let’s Encrypt (“le” en certResolver) dans le cas où l’application ne l’aurait pas déjà
  • Que les règles de routage sont portées par l’ingressroute

À partir de ce point, vous devriez maintenant pouvoir accéder à vos pods via le port 80 ou 443 pour la partie sécurisée.

Pour aller plus loin…

Nous avons vu comment déployer basiquement Traefik avec un backend dans Kubernetes. Nous n’avons cependant touché qu’à la partie émergée de Traefik.

Pour aller plus loin nous pourrions :

  • Configurer plus finement la partie TLS (ciphers / curves / version TLS)
  • Mettre en place des middlewares pour faire, par exemple, du ray tracing
  • Mettre en place du load balancing TCP
  • Faire du multi backend
  • Externaliser les logs
  • Plugger Traefik à Prometheus pour externaliser ses métriques

Ces points seront sans doute abordés ultérieurement sur ce blog, donc stay tuned !