Gateway API - Fonctionnement et exemple avec Traefik

Introduction

Kubernetes est un écosystème très dynamique, en constante évolution.

Certaines méthodes, même si elles ont été utilisées durant un laps de temps important, peuvent être dépréciées et finir par laisser la place à d’autres solutions.

C’est le cas pour l’exposition des assets à l’extérieur des clusters.

Encore aujourd’hui l’une des façons de faire pour s’acquitter de cette tâche est de faire appel à des objets de type Ingress.

Ces objets sont manipulés et traités par des Ingress Controller, comme peut l’être NGINX ou Traefik.

Je détaille d’ailleurs l’usage de Traefik et le fonctionnement des ingress à travers cet article.

Bien que fonctionne la combinaison d’Ingress et d’Ingress Controller, l'ensemble commence à montrer certaines limites.

L’un des reproches les plus fréquents que l’on fait à ce type d’architecture est l’usage d’annotations non standardisées propres à chaque contrôleur pour définir des options avancées.

L’objet Ingress en tant que telle est très limitée dans ses fonctions, et, dès qu’il s’agit de mettre en place du routage avancé ou des fonctionnalités tierces, comme de la réécriture d’URL, chaque Ingress Controller y va de sa propre syntaxe.

De plus, la séparation entre le périmètre d’infrastructure et le périmètre applicatif est un peu floue, ce qui peut poser des problématiques de responsabilités autour de l’administration d’un cluster k8S dans certaines organisations.

C’est pourquoi, depuis 2019 des initiatives ont été prises pour essayer d’améliorer la situation. Cela a fini par aboutir en octobre 2023 à la parution en GA (General Availability) de la Gateway API.

Il s’agit d’un projet officiel Kubernetes différent du développement principal. Son cycle de vie est plus rapide que le core avec des évolutions tous les 3 à 6 mois.

Sans rendre obsolète la logique des Ingress, la Gateway API est désormais la voie privilégiée par la communauté Kubernetes pour exposer ses applications.

Cet article vise à démontrer comment passer à cette nouvelle méthode d'exposition et à expliquer le fonctionnement de la Gateway API en présentant les nouveaux éléments qu’elle introduit.

Sachez dès à présent qu’il est tout à fait possible de faire cohabiter au sein d’un même cluster, l’exposition via Ingress et l’exposition via Gateway API.

Notez également que si les Ingress sont toujours pleinement intégrés à K8S, NGINX c’est déjà prononcé pour la fin à venir du support de ces derniers au seul profit de la Gateway API.

En ce qui me concerne, j’utilise principalement Traefik. Bien que ce dernier n’ait pas encore annoncé de changement au moment où j’écris ces lignes, je vais commencer à migrer vers la Gateway API pour rester à jour.

Cet article se concentrera donc sur Traefik, mais les utilisateurs de NGINX y trouveront également des informations utiles sur le fonctionnement de la Gateway API, qui pourraient leur être utiles lors de leurs propres migrations.

Les fondamentaux

Rappel sur le fonctionnement des Ingress

Avant d’aborder la Gateway API, rappelons à travers un schéma simplifié, la méthode actuelle d’exposition de mes applications basées sur mon cluster Kubernetes, Traefik et les Ingress.

Schéma de principe pour le fonctionnement des Ingress

Cliquez sur l'image pour l'agrandir.

Dans ce mode de fonctionnement, tout traitement spécifique avancé passe par des objets middelwares propres aux objets Traefik.L’Ingress est très basique et sert surtout à faire le lien avec le Service.

Si vous voulez en savoir plus sur ce type de logique, n’hésitez pas à parcourir cet article.

Les bases sur la Gateway API

La Gateway API est différente. Elle va faire appel à plusieurs nouveaux objets.

Ressource Scope Rôle
GatewayClass Cluster Définit le contrôleur (Traefik)
Gateway Namespace Point d'entrée concret (listeners)
HTTPRoute Namespace Règles de routage (hostname → service)
ReferenceGrant Namespace Autorise le cross-namespace secret access

Comme vous pouvez le constater, cela ajoute de la complexité, mais, en contrepartie, on gagne en séparation des usages et en capacité native à traiter des problématiques de routages avancés.

Voici l’équivalent du schéma simplifié précédent, mais dans la logique de la Gateway API.

Schéma simplifié pour le fonctionnement de la Gateway API

Cliquez sur l'image pour l'agrandir.

Traefik bascule d’un Ingress Controller à une GatewayClass.

Cette GatewayClass va permettre de créer des Gateway. On peut décider de créer plusieurs Gateways ou mutualiser une Gateway pour plusieurs applications.

Une Gateway va pouvoir traiter des HTTPRoute dans lesquels on va retrouver les règles de routages pour envoyer les requêtes vers les bons Services en y appliquant les bons traitements.

Lorsqu’une gateway ne se trouve pas dans le même Namespace que les HTTPRoute qu’elle contrôle, il est impératif de créer un objet ReferenceGrant pour autoriser la gateway à accéder à des objets du namespace de l’application, tels que des secrets renvoyant aux certificats à utiliser.

Alors oui c’est plus complexe, mais on délègue ainsi les fonctionnalités avancées à des objets K8S indépendant du choix de l’outil retenu comme GatewayClass.

Ainsi, contrairement à avant, qu’on utilise NGINX ou Traefik, les syntaxes de routage seront les mêmes.

De plus, on définit clairement des périmètres de responsabilités si besoin.

Répartition des responsabilité

Cliquez sur l'image pour l'agrandir.

Enfin, la Gateway API est amenée à évoluer et d’autres objets peuvent être utilisés ou vont pouvoir être utilisés.

Par exemple, un objet GRPCRoute existe déjà pour permettre à une passerelle de faire du mappage de trafic sur le protocole gRPC (gRPC Remote Procedure), une manière améliorée de faire des appels API.

D’autres objets sont en tests actuellement, comme TCPRoute, UDPRoute, BackendLBPolicy, RateLimitPolicy à des stades plus ou moins avancés.

Comme vous le comprendrez à la lecture des noms de ces objets, on s’approche de la mise en œuvre d’un véritable loadbalancer au sein même de Kubernetes, et ceux de manière standardisée.

Installation et configuration

Essayons maintenant de rendre ça plus concret à travers une mise en application sur mon lab.

Je vous invite à prendre connaissance de mon architecture en place, notamment via ce lien .

L’objectif est d’ajouter à mon instance Traefik dédiée à mon flux en DMZ le support de la Gateway API et de rendre disponible une application de tests à l’extérieur de mon cluster via les nouveaux objets associés.

Installation de la Gateway API

La Gateway API n’est pas dans le cœur de K8S, il faut donc commencer par ajouter ces fameux objets à l’API de Kubernetes.

Plusieurs manières de procéder existent. La méthode la plus simple consiste à appliquer les yamls fournis sur le GitHub du projet directement par la commande kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml.

Bien sûr la version est à adapter en fonction du moment et des contraintes.

Si, comme moi, vous rencontrez un incident lors du déploiement, il vous suffit de supprimer l’installation qui vient juste d’être faite, puis de la relancer.

kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.0/standard-install.yaml

Cela peut être lié à des éléments déjà installés, par exemple via le déploiement en amont de Traefik, qui peut lui-même déployer la Gateway API.

Premiere tentative de déploiement

Cliquez sur l'image pour l'agrandir.

Succès de l'installation de l'API

Cliquez sur l'image pour l'agrandir.

Modification de la configuration Traefik

Traefik va justement être le second composant à être modifié.

En l’occurrence je ne vais pour l’instant que toucher à mon instance Traefik dédiée à ma DMZ. (N’hésitez pas à prendre connaissance de mon architecture Traefik ).

Je vais modifier le fichier de configuration que j’ai l’habitude de passer à Helm lorsque je déploie ou je met à jour Traefik sur mon cluster.

Je vais repartir de mon fichier de départ en lui ajoutant certains éléments.

Voici la configuration complète issue de mon fichier traefik-config-dmz.yaml:

image:
  tag: "3.5.4"
  pullPolicy: IfNotPresent

deployment:
  enabled: true
  kind: DaemonSet
  labels:
    network: dmz
  podLabels:
    network: dmz

gatewayClass:
  enabled: true
  name: traefik-dmz
  labels:
    network: dmz

gateway:
  enabled: false

providers:
  kubernetesGateway:
    enabled: true
    labelSelector: "network=dmz"
  kubernetesIngress:
    labelSelector: "network=dmz"
    allowExternalNameServices: true
  kubernetesCRD:
    enabled: true
    allowCrossNamespace: false
    allowExternalNameServices: true
    allowEmptyServices: false
    namespaces:
      - inf-traefik-dmz
      - prd-myprivatelab-dmz

serviceAccount:
  name: "svc-prd-traefik-dmz"

logs:
  general:
    level: ERROR

hostNetwork: true


ports:
  web:
    port: 80
    expose:
      default: true
    redirections:
      entryPoint:
        to: websecure
        scheme: https
        permanent: true
  websecure:
    port: 443
    expose:
      default: true
    proxyProtocol:
      trustedIPs:
        - "192.168.10.91"
    forwardedHeaders:
      trustedIPs:
        - "192.168.10.91"
    tls:
      enabled: true

securityContext:
  capabilities:
    drop: [ALL]
    add: [NET_BIND_SERVICE]
  readOnlyRootFilesystem: true
  runAsGroup: 0
  runAsNonRoot: false
  runAsUser: 0

updateStrategy:
  rollingUpdate:
    maxSurge: 0
    maxUnavailable: 1
  type: RollingUpdate

additionalArguments:
  - "--providers.kubernetesingress.ingressclass=traefik-dmz"
  - "--serversTransport.insecureSkipVerify=true"
Fichier de configuration helm pour Traefik

Cliquez sur l'image pour l'agrandir.

On va s’arrêter sur les quelques éléments clef:

gatewayClass:
  enabled: true
  name: traefik-dmz
  labels:
    network: dmz

gateway:
  enabled: false

Cette partie de la configuration va:

  • Permettre la création de la gatewayClass « traefik-dmz » associée au label network : DMZ
  • Va interdire la création automatique de la Gateway. Par défaut, Traefik cherche à créer une gateway pour l’attacher à la gatewayclass. Sauf que, dans ce cas, certains éléments ne peuvent être paramétrés. On va donc préférer la créer manuellement, à part, plus tard.
providers:
  kubernetesGateway:
    enabled: true
    labelSelector: "network=dmz"

Cette section active la prise en charge du provider KubernetesGateway associé à la Gateway API par Traefik. On indique également quelles publications associées à ce provider doivent être prises en compte, en appliquant un filtre aux Namespaces qui incluent le label « network=dmz ».

C’est ainsi que je pourrai continuer d’utiliser différentes instances de Traefik, selon que j’ai besoin de publier des applications en DMZ ou en LAN, puisque mon cluster Kubernetes comporte des nœuds dans ces deux segments réseau. C’était une manière pour moi d’économiser de la ressource et de ne pas avoir deux clusters spécifiques.

Il n’y a pas besoin de plus de paramètres.

Comme je l’explique dans mon cookbook je pousse mes fichiers de configuration avec Ansible afin d’automatiser mes mises à jour.

Je relance donc mon playbook pour pousser ce nouveau fichier vers mes control planes…mais c’est anecdotique dans le sujet d’aujourd’hui.

Déploiement du fichier de conf via Ansible

Cliquez sur l'image pour l'agrandir.

Quoi qu’il en soit, j’ai préparé mon fichier de configuration, et il ne me reste plus qu’à exécuter une commande helm (puisque j’ai déployé Traefik avec Helm) pour mettre à jour mon instance Traefik en DMZ avec cette nouvelle configuration.

helm upgrade traefik-dmz traefik/traefik -f traefik-config-dmz.yaml -n inf-traefik-dmz --set nodeSelector.traefik-dmz=yes --set tolerations[0].key=node-role.kubernetes.io/worker-dmz --set tolerations[0].operator=Exists --set tolerations[0].effect=NoSchedule
Mise à jour de la configuration Traefik

Cliquez sur l'image pour l'agrandir.

Une fois l’opération terminée, je peux vérifier que je dispose désormais d’une gatewayclass. Cet objet étant transverse au cluster pas besoin de spécifier un Namespace:

kubectl get gatewayclass
Controle du statut de la gatewayclass

Cliquez sur l'image pour l'agrandir.

Son statut doit être à true, car elle doit matcher avec mon installation de Traefik.

Si on consulte maintenant le dashboard de Traefik, on retrouve également la prise en charge de la Gateway API via le provider kubernetesGateway, en plus des Ingress et des Custom Object Traefik.

Controle de la prise en compte du provider de la gateway API par Traefik

Cliquez sur l'image pour l'agrandir.

C’est l’avantage ici, car on peut très bien exploiter les trois méthodes pour publier ses applications à l’extérieur du cluster.

Les trois méthodes (Custom Object Traefik, Ingress, Gateway API) peuvent cohabiter et vous pouvez changer application par application pour faire des transitions en douceurs.

Création de l'objet Gateway

Maintenant que ma gatewayclass est prête et que mon instance Traefik en DMZ est capable de travailler selon la logique de la Gateway API, il faut créer l’objet Gateway (puisqu’on a pas souhaité sa création de manière automatique dans la conf de traefik).

Pour cela, on crée le fichier de configuration suivant: 08-gateway-dmz.yml.

---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: traefik-gateway-dmz
  namespace: inf-traefik-dmz
  labels:
    network: dmz
spec:
  gatewayClassName: traefik-dmz
  listeners:
  - name: web
    port: 80
    protocol: HTTP
    allowedRoutes:
      namespaces:
        from: Selector
        selector:
          matchLabels:
            network: dmz
  - name: websecure
    port: 443
    protocol: HTTPS
    allowedRoutes:
      namespaces:
        from: Selector
        selector:
          matchLabels:
            network: dmz
    tls:
      mode: Terminate
      certificateRefs:
        - name: sec-gateway-default-cert

J’ai choisi de placer ce point d’accès dans le même espace de noms (inf-traefik-dmz) que mes conteneurs exécutant l’instance Traefik en DMZ. J’ai décidé de concevoir une Gateway unique qui sera en mesure de servir plusieurs HTTProute provenant de mes Namespaces réservés à mes applications en DMZ.

Le fichier est relativement simple à comprendre.

On définit les ports d’écoute de notre passerelle, c’est-à-dire les mêmes que ceux ouverts par Traefik, soit le 80 (web) et le 443(websecure).

On applique un filtre sur les Namespaces, de manière à ce que la Gateway n’interagisse qu’avec ceux contenant des applications conçues pour une publication en DMZ, en manipulant l’étiquette network=dmz.

Très important, on finit par la section tls :.

C’est ici qu’on doit spécifier un secret (sec-gateway-default-cert) contenant le certificat par défaut de la Gateway. En effet, dès qu’on demande à une Gateway de traiter du flux TLS, elle doit posséder un certificat.

Ce certificat ne sera pas utilisé, sauf si aucun autre n’est trouvé qui peut correspondre à l’URL du site publié grâce à la logique SNI (Server Name Indication).

Ce certificat peut donc être un certificat privé et ne nécessite pas forcément d’exploiter un certificat généré par une CA de référence.

Dans mon cas, je vais utiliser ma bonne vielle PKI Windows interne pour générer un certificat sur un nom par défaut traefik-gateway-dmz.coolcorp.priv.

Je ne vais pas détailler plus que ça la création du certificat, chacun dispose de sa façon de faire.

Création du certificat pour la gateway

Cliquez sur l'image pour l'agrandir.

Il est crucial que le certificat et la clé privée soient stockés dans un secret, qui sera lui-même stocké dans le même Namespace que la Gateway et qui sera déclaré dans sa configuration.

kubectl create secret tls sec-gateway-default-cert --cert=traefik-gateway-dmz.coolcorp.priv.cer --key=traefik-gateway-dmz.coolcorp.priv.key -n inf-traefik-dmz
Création du secret pour stocker le certificat

Cliquez sur l'image pour l'agrandir.

On applique ensuite la configuration pour déclarer notre gateway.

kubectl apply -f .\08-gateway-dmz.yml

On vérifie qu’elle est disponible dans le Namespace souhaité.

kubectl get gateway -n inf-traefik-dmz
Controle de la Gateway

Cliquez sur l'image pour l'agrandir.

Test et usage

On va pouvoir terminer par un test pour valider le fonctionnement de l’ensemble.

Pour ça, on commence par créer un Namespace dédié.

kubectl create ns dev-demoapigateway-dmz
Création du namespace de test

Cliquez sur l'image pour l'agrandir.

Puis on labelise ce namespace avec network=dmz pour qu’il soit reconnu par notre gateway.

kubectl label namespace dev-demoapigateway-dmz network=dmz
Lebelisation du namespace

Cliquez sur l'image pour l'agrandir.

On poursuit avec des objets classiques.

Comme un issuer basé sur let’s encrypt pour automatiser la création du certificat propre à l’application publiée.

---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: issuer-demoapigateway-letsencrypt
  namespace: dev-demoapigateway-dmz 
  labels:
    environment: dev
    network: dmz
    application: demoapigateway
spec:
  acme:
    email: [email protected]
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: sec-issuer-demoapigateway
    solvers:
      - http01:
          ingress:
            class: traefik-dmz
            podTemplate:
              metadata:
                labels:
                  network: dmz
              spec:
                nodeSelector:
                  network: dmz
                tolerations:
                  - key: "node-role.kubernetes.io/worker-dmz"
                    operator: "Exists"
                    effect: "NoSchedule"
            ingressTemplate:
              metadata:
                labels:
                  network: dmz

Pour ceux qui ne sont pas à l’aise avec ce type d’objet, je vous encourage à passer par mon article sur l’installation de CertManager ainsi que de mon tutoriel de publication d’application sur mon cluster K8S de référence.

Puis on conçoit son deployment et son service pour décrire son application.

En l’occurrence, ici, une simple mise en ligne d’une image nginx.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy-demoapigateway-lan
  namespace: dev-demoapigateway-dmz
  labels:
    environment: dev
    network: dmz
    application: demoapigateway
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      environment: dev
      network: dmz
      application: demoapigateway
  template:
    metadata:
      labels:
        environment: dev
        network: dmz
        application: demoapigateway
    spec:
      nodeSelector:
        network: dmz
      tolerations:
         - key: "node-role.kubernetes.io/worker-dmz"
           operator: "Exists"
           effect: "NoSchedule"  
      containers:
      - name: demo-nginx-traefik-dmz
        image: nginx:1.27
        resources:
          requests:
            memory: "8Mi"
            cpu: "250m"
          limits: 
            memory: "256Mi"
            cpu: 1  
        livenessProbe:
          initialDelaySeconds: 5 
          periodSeconds: 5 
          exec:
           command:
           - ls /usr/share/nginx/html
        ports:
        - containerPort: 80
 
---
kind: Service
apiVersion: v1
metadata:
  name: svc-demoapigateway-lan
  namespace: dev-demoapigateway-dmz
  labels:
    environment: dev
    network: dmz
    application: demoapigateway
spec:
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: 80
  selector:
    environment: dev
    network: dmz
    application: demoapigateway

Enfin, dernier objet dont on a l’habitude, le certificat qu’on va associer à notre issuer pour qu’il soit généré automatiquement et mis dans un secret.

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: cert-demoapigateway
  namespace: dev-demoapigateway-dmz
  labels:
    environment: dev
    network: dmz
    application: demoapigateway
spec:
  secretName: sec-demoapigateway-tls
  issuerRef:
    name: issuer-demoapigateway-letsencrypt
    kind: Issuer
  dnsNames:
    - demo.myprivatelab.tech

Là où les choses vont changer, c’est pour les objets suivants.

On commence par un objet ReferenceGrant.

---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
  name: allow-gateway-ref-demoapigateway
  namespace: dev-demoapigateway-dmz
  labels:
    environment: dev
    network: dmz
    application: demoapigateway
spec:
  from:
  - group: gateway.networking.k8s.io
    kind: Gateway
    namespace: inf-traefik-dmz
  to:
  - group: ""
    kind: Secret
    name: sec-demoapigateway-tls

Comme vu précédemment, ma gateway n’est pas dans le même Namespace que mon application, mais je veux pour autant qu’elle utilise le certificat décrit juste avant et propre à mon URL de test demo.myprivatelab.tech.

Je ne veux pas que la gateway exploite le certificat par défaut qu’elle possède.

Mais pour que le bon certificat soit choisi, il faut que j’autorise la gateway à exploiter un secret issu d’un autre Namespace que son namespace d’exécution.

C’est le rôle de l’objet ReferenceGrant, nommé ici allow-gateway-ref-demoapigateway.

Enfin on passe au dernier objet, notre HTTProute:

---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: httproute-demoapigateway
  namespace: dev-demoapigateway-dmz
  labels:
    environment: dev
    network: dmz
    application: demoapigateway
spec:
  parentRefs:
  - name: traefik-gateway-dmz
    namespace: inf-traefik-dmz
    sectionName: websecure  
  hostnames:
  - demo.myprivatelab.tech
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: svc-demoapigateway-lan
      port: 80

On y retrouve une syntaxe et une logique assez proche des ingress.

Le but est de renvoyer les requêtes sur demo.myprivatelab.tech vers le bon Service associé à mon pod nginx.

C’est au niveau de cette configuration:

  
spec:
  parentRefs:
  - name: traefik-gateway-dmz
    namespace: inf-traefik-dmz
    sectionName: websecure  

Qu’on fait le lien entre l’HTTProute et la Gateway.

On applique tous nos objets pour aller au bout de l’expérience.

kubectl apply -f mondossiercontenantlesyamls

Normalement on ne doit pas rencontrer d’erreur.

Pour vérifier que tout est bien pris en charge côté Traefik, on peut regarder sur le dashboard de ce dernier.

On y retrouve bien ma publication demo.myprivatelab.tech, qui s’appuie bien sur le provider KubernetesGateway.

Controle de la publication dans le dash traefik

Cliquez sur l'image pour l'agrandir.

Le site est également visible en ligne.

L’opération est réussie.

Conclusion

Cet article m’aura servi d’introduction à l’usage de la Gateway API au sein de Kubernetes.

Il me parait important que, dès à présent, on s’habitue à son utilisation, car elle est devenu la méthode préconisée, et peut être un jour la seule supportée, pour publier ses applications à l’extérieur du cluster.

Si cela peut paraitre plus complexe qu’avant, on peut vite s’y faire grâce à une standardisation des syntaxes et l'approche d'un loadbalancer traditionnel.

La migration peut se faire à son rythme et la cohabitation aujourd’hui possible avec les Ingress rend les changements plus simples.

Donc, même si la Gateway API n’est pas encor arrivé à sa pleine maturité, ses objets de bases peuvent dès à présent être utilisés. N’hésitez donc pas à l’adopter et démarrer vos montées en compétences…il n’est jamais trop tôt pour bien faire 😊.