Kubernetes Ingress Controller : petit manuel de migration

9 Janvier 2024 • 13 min

Dans cet article, nous vous présentons différentes techniques pouvant être utilisées pour migrer d’un ingress controller Kubernetes existant vers un autre.

Qu’est-ce qu’un Ingress Controller ?

Un Ingress est une ressource populaire de l’API Kubernetes utilisée pour router les requêtes HTTP(S) entrantes vers les backends (pods) correspondants. Les utilisateurs du cluster peuvent créer des ressources ingress pour définir les règles de routage comme par exemple : “fait suivre les requêtes vers www.exemple.com/api vers les pods associés au service XYZ”. Ces ressources s’appuient sur un ou plusieurs ingress pour mettre en œuvre la gestion du trafic, le routage et l’équilibrage de charge.

Un ingress controller est essentiellement un équilibreur de charge HTTP (aussi appelé load-balancer layer 7) configuré via l’API Kubernetes. Lorsque vous envoyez une requête à un domaine géré par un ingress controller, ce dernier reçoit la requête, l’analyse pour effectuer un éventuel pre-processing (filtrage, ajout de header…), détermine sa destination (c’est-à-dire vers quel pod(s) l’envoyer), lui fait suivre et enfin vous renvoie la réponse.

Attention, les utilisateurs et la documentation Kubernetes entretiennent parfois une certaine confusion en parlant d'« un ingress » qui peut, selon le contexte, désigner ou bien une ressource ingress ou bien un ingress controller.

Pourquoi Migrer d’un Ingress Controller à un Autre ?

Malheureusement, l'API d’ingress originale est relativement limitée. Elle ne permet de router les requêtes qu’en fonction du nom d’hôte et du chemin de l’URL, ce qui est souvent insuffisant dans le monde web moderne.

Par exemple, nous avons souvent besoin de spécifier une redirection HTTP pour garantir que les utilisateurs accèdent à nos sites et applications via une connexion sécurisée TLS (commençant par le schéma d’URL https://). Nous pouvons également vouloir activer HSTS pour améliorer la sécurité, implémenter un déploiement canary/blue-green, ou encore faciliter l’authentification via Oauth2.

De nombreux ingress controllers ont comblés ces lacunes en décidant d’implementer leur propre solution. Mais comme ces fonctionnalités “avancées” ne faisaient pas partie de la spécification originale d’un ingress, la qualité et les spécificités d’implémentations varient d’un ingress controller à l’autre. Cela signifie que même si pratiquement tous les ingress controllers implémentent la spécification ingress, ils offrent diverses extensions, et lorsque ces extensions sont cruciales pour la disponibilité ou la performance de nos applications, le choix de l’ingress controller devient un pilier de notre solution de cluster Kubernetes. Initialement prévus pour être intercheangeables, les ingress-controllers ne le sont plus du tout !

Certaines choix de conception peuvent être remis en cause à long terme, et les changer peut nécessiter des migrations complexes et coûteuses. Cependant, dans cet article, nous verrons que le choix d’un ingress controller n’a pas à être définitif. Il existe des solutions de migration d’un ingress controller à un autre si le choix initial ne s’avère plus optimal ou si nos exigences ont changé.

Si vous êtes cependant dans le processus de décision ou de remise en cause du choix d’un ingress controller, ce document très exhaustif et fréquemment mis à jour de comparaison des Ingress Controllers Kubernetes peut vous être utile.

Une Typologie des Ingress Controllers

Avant de parler des scénarios de migration, nous pourrions classer les ingress controllers selon les catégories suivantes :

Type

Description

Cloud Ingress

L’ingress controller est implémenté par le cloud provider et fonctionne généralement en dehors du cluster Kubernetes.

Comme les détails de l'implémentation sont très spécifiques au fournisseur, il est important de lire attentivement la documentation pour s'assurer que tout est correctement configuré. Par exemple, dans certains scénarios, certains ingress controllers pourraient nécessiter des services « nodePort », ce qui conduit généralement à des performances non optimales.

Ingress Interne exposé via service de type LoadBalancer

L’ingress controller fonctionne à l'intérieur du cluster Kubernetes et est accessible via un service de type LoadBalancer, qui est lui-même matérialisé par un Cloud Load Balancer fournissant un load balancer de niveau 4 (L4).

Dans ce scénario, les requêtes HTTP suivent le chemin suivant :

client → cloud load balancer → pod ingress controller → pod application.

C'est une méthode de déploiement très populaire, mais avec la plupart des cloud load balancers, les informations d'adresse IP source du client sont perdues (les requêtes semblent provenir du load balancer). Pour préserver l'adresse IP du client, dans certains cas, il est possible d'utiliser « externalTrafficPolicy : Local ». Une autre option est d'utiliser le PROXY protocol entre cloud load balancer et le pod d'ingress - quand ils supportent tous les deux ce protocole.

Ingress Interne exposé via hostPort

L’ingress controller fonctionne à l'intérieur du cluster Kubernetes et est exposé via un « hostPort » (typiquement les ports 80 et 443). Cela mappe essentiellement ces ports du node où le pod est en cours d'exécution, au pod lui-même.

Cela préserve naturellement l'adresse IP source. Cependant, pour des scénarios de haute disponibilité, les pods doivent être déployés sur plusieurs nodes, et les adresses IP publiques (externes) de ces nodes doivent toutes être mises à jour dans les enregistrements DNS de l’ingress controller. Ceci est généralement réalisé avec un DaemonSet (et éventuellement un nodeSelector).

Ingress Interne exposé via hostNetwork

L’ingress controller fonctionne à l'intérieur du cluster Kubernetes et a un accès direct aux interfaces réseau des nodes sur lesquels il fonctionne.

Il est similaire au scénario « hostPort », mais comme l’Ingress Controller a un accès complet à la stack réseau du node, il peut même tirer parti de IPv6 ou de UDP (HTTP3).

Scénarios de Migration des Ingress Controllers

Maintenant que nous avons décrit les principales façons dont les ingress controllers peuvent être déployés sur Kubernetes, parlons des scénarios de migration.

Ces scénarios dépendront principalement de la façon dont nos ingress controllers sont déployés. Ils sont des guides génériques qui doivent être traités avec attention. Il est important de toujours essayer au préalable vos étapes de migration sur un environnement de staging avant de passer sur un environnement de production.

A) DNS Switchover

Dans ce scénario, les deux Ingress Controllers ont leur propre adresse IP externe, ce qui signifie que la migration se fait principalement en mettant à jour les enregistrements DNS.

Applicabilité du Scénario

DE

À

Cloud Ingress Ingress interne exposé via LoadBalancer Service
Ingress interne exposé via LoadBalancer Service Cloud Ingress
Ingress interne exposé via LoadBalancer Service Un autre Ingress interne exposé via LoadBalancer Service

Note : il n’y a pas de scénario « Cloud Ingress vers Cloud Ingress » dans le tableau ci-dessus, car ce cas est extrêmement rare (cas où un fournisseur de cloud aurait au moins deux ingress controllers distincts). Si vous êtes dans ce scénario, le fournisseur cloud vous proposerait probablement un guide de migration dédié.

Downtime

Aucune interruption de service.

Description

Pour éviter un downtime, la meilleure option est de faire fonctionner les deux ingress controllers en parallèle, vérifier que tout fonctionne correctement avec le nouvel ingress, et finalement basculer le DNS l’un après l’autre pour minimiser les risques.

Les ingress controllers s’appuient normalement sur la classe “ingress” d’une resource ingress pour déterminer s’ils doivent gérer cette ressource ou non. Lors de la migration d’un ingress controller à un autre, vous pouvez soit créer des ressources ingress en double (pour les deux ingress controllers), soit configurer au moins l’un des ingress controllers pour gérer toutes les ressources ingress, en ignorant leur classe ingress.

Notez que la deuxième option peut entraîner des conflits entre les deux ingress controllers, car ils pourraient être en concurrence pour mettre à jour le champ “status.loadbalancer” des ressources ingress afin de refléter l’adresse IP externe de leur load balancer. Bien que cela soit en soi inoffensif, cela peut poser des problèmes si vous utilisez un outil qui dépend de ce champ (par exemple, ExternalDNS).

Puisque la gestion d’une ressource ingress donnée avec deux ingress controllers simultanément n’est pas explicitement prise en charge dans Kubernetes, si vous empruntez cette voie, vous devriez la tester de manière approfondie dans votre environnement de préproduction et vous assurer qu’elle ne déclenche pas de bugs au niveau de l’un des ingress controllers.

Et si ces tests révèlent des problèmes (par exemple, si les deux ingress controllers se disputent le même objet d’entrée), revenez au plan initial et gardez une ingress class pour chaque ingress controller, puis changez lorsque vous êtes prêt.

Lors de la mise à jour des enregistrements DNS, il est conseillé de réduire leur TTL à une valeur plus basse (par exemple, entre 30 et 300 secondes) un certain temps avant la migration pour atténuer l’impact des enregistrements DNS obsolètes pointants toujours vers une ancienne adresse IP (et potentiellement invalide !) ; et de restaurer le TTL à sa valeur initiale lorsque la migration est terminée.

Exemples

Si vous déployez NGINX Ingress avec son chart Helm, vous pouvez ajouter la valeur suivante :

--set controller.watchIngressWithoutClass=true

Si vous déployez HAProxy Kubernetes Ingress Controller avec son chart Helm, la valeur correspondante est :

--set "controller.extraArgs[0]=--empty-ingress-class”

B) Réutilisation de l’Adresse IP du LoadBalancer

Applicabilité du Scénario

DE À
Ingress interne exposé avec un service LoadBalancer Un autre ingress interne exposé avec un service LoadBalancer

Downtime

Quelques secondes (lié au cloud controller manager).

Description

Conceptuellement, dans ce scénario nous réattribuons l’adresse IP publique d’un service LoadBalancer à un autre. Ceci est réalisé en définissant le champ “spec.loadBalancerIP” dans le nouveau service.

Si vous souhaitez utiliser ce scénario, il y a quelques détails importants à connaître.

Tout d’abord, cette fonctionnalité est uniquement prise en charge sur certains cloud controller managers ; vous devez donc vérifier si une documentation est disponible dans votre environnement cloud spécifique.

Ensuite, cela peut nécessiter de “verrouiller” l’adresse IP correspondante afin qu’elle ne soit pas libérée et rendue au pool du fournisseur cloud.

Enfin, le champ “spec.loadBalancerIP” a été déprécié dans Kubernetes 1.24 au profit d’une annotation spécifique au fournisseur cloud. Certains fournisseurs implémentent toujours le champ, et d’autres nécessitent d’utiliser l’annotation. Là encore, vous devrez vérifier la documentation (par exemple pour Google Cloud, ou pour Microsoft Azure).

Voici les différentes étapes de ce scénario de migration :

  1. déployez le nouvel ingress controller avec un service de type LoadBalancer
  2. dans ce nouveau service de type LoadBalancer, définissez “spec.loadBalancerIP” (ou l’annotation correspondante dans la version 1.24 ou ultérieure) à l’adresse IP du service de type LoadBalancer de l’ancien ingress controller (qui est toujours en fonctionnement) ; cette adresse IP externe du load balancer peut apparaître comme en attente pour le moment
  3. éventuellement, faites une sauvegarde de l’ancien service de type LoadBalancer (avec kubectl get service -o yaml …)
  4. vérifiez que le nouvel ingress controller fonctionne correctement (par exemple, avec kubectl port-forward ou toute autre technique)
  5. lorsque vous êtes prêt, supprimez l’ancien service de type LoadBalancer Service
  6. le cloud controller manager réattribuera automatiquement l’adresse IP de l’ancien load balancer au nouveau.

Exemple

Pour définir loadbalancerIP pour l’ingress Traefik :

service:
	spec:
		loadbalancerIP: <old_loadbalancer_IP>

C) Node rollout

Applicabilité du Scénario

DE À
Ingress exposé via HostPort Ingress exposé via HostPort
Ingress exposé via HostNetwork Ingress exposé via HostPort
Ingress exposé via HostPort Ingress exposé via HostNetwork
Ingress exposé via HostNetwork Ingress exposé via HostNetwork

Downtime

  • Aucun downtime lors du déploiement sur différents nodes (nécessitant des étapes supplémentaires, par exemple avec DNS)
  • Faible downtime lors du déploiement sur le même ensemble de nodes (le temps nécessaire pour démarrer le pod d’un ingress controller ; typiquement entre quelques secondes et une minute)

Description

Dans ce scénario, l’ ingress controller fonctionne sur un ensemble de nodes. C’est typiquement réalisé en exécutant l’ingress controller avec un Daemon Set, soit sur l’ensemble du cluster (pour les petits clusters), soit sur un sous-ensemble désigné de nodes (en utilisant un nodeSelector). Le trafic HTTP et HTTPS est envoyé à ces nodes sur les ports 80 et 443.

Si l’ingress controller actuel est déployé sur un sous-ensemble de nodes, il est possible d’éviter une interruption de service en déployant le nouvel ingress sur un autre ensemble de nodes. Le comportement du nouvel ingress peut alors être testé de manière approfondie sans perturber l’ingress actuel. Une fois le fonctionnement est validé, le trafic peut être basculé vers le nouvel ingress en mettant à jour les enregistrements DNS :

  1. Ajouter des enregistrements pour les nouveaux nodes
  2. Supprimer ceux des anciens nodes
  3. Une fois que le trafic a intégralement migré vers les nouveaux nodes, arrêter l’ancien ingress.

Si l’ingress controller actuel est déployé sur tous les nodes du cluster, la méthode à employer est différente. Pour tester le nouvel ingress, on le déploie sur des numéros de port différents (par exemple, 8080 et 8443). Une fois que le nouvel ingress fonctionne correctement, la migration peut avoir lieu. Ci-dessous, voici les étapes légèrement différentes selon que nous utilisons HostNetwork ou HostPort.

En cas d’utilisation de HostPort, c’est Kubernetes qui s’occupe de transférer les connexions des ports de l’hôte (par exemple, 80 et 443) vers les pods. Les ports étant explicitement listés dans le manifeste du pod, Kubernetes “sait” que ces ports sont alloués sur l’hôte. Il empêchera donc deux pods d’utiliser le même port : le premier pod sera programmé et démarrera normalement, et le second pod avec le même port restera en état “Pending” car l’ordonnanceur est capable de reconnaître que la ressource hostport demandée n’est pas disponible. Nous pouvons nous appuyer sur ce comportement pour réaliser une migration fluide de l’ancien ingress au nouveau de la façon suivante :

  1. créer le Daemon Set pour le nouvel ingress controller (ses pods seront créés mais resteront “Pending”)
  2. supprimer le Daemon Set de l’ancien ingress controller avec le flag “–cascade=orphan” (pour que ses pods continuent à fonctionner)
  3. supprimer les anciens pods un par un, en vérifiant que les nouveaux pods démarrent correctement

En cas de problème, cette méthode prudente vous permet de revenir rapidement à l’ancien ingress controller en recréant son Daemon Set.

En cas d’utilisation de HostNetwork, les pods de l’ingress controller utilisent le namespace réseau par défaut des noeuds sur lesquels ils s’exécutent. En d’autres termes, lorsque l’ingress controller écoute sur le port TCP/80 en HostNetwork, c’est directement le port TCP/80 sur l’hôte ; il n’y a donc aucun traitement supplémentaire des paquets IP. Ceci lui permet de proposer des performances natives et donc optimales. L’inconvénient de ce mode est que Kubernetes n’a plus la main sur l’allocation et la réservation des ports et ne pourra pas empêcher deux pods d’essayer de se rattacher à un même port. Ceci provoquerait un conflit si deux ingress controllers différents tentaient de fonctionner sur le même hôte : le premier réussira, tandis que le second recevra un message d’erreur de type “adresse déjà utilisée”.

Le déploiement devra être soigneusement coordonné, en arrêtant chaque pod de l’ancien ingress controller et en créant son remplaçant de façon séquentielle.

Une note finale : la migration entre les approches HostNetwork et HostPort est possible, mais elle nécessite également une coordination soigneuse, car une fois de plus Kubernetes ne sera pas au courant des ports utilisés par les pods utilisants HostNetwork.

Exemple

Pour configurer les HostPort de Kong :

--set proxy.http.hostPort=

8080 --set proxy.tls.hostPort=8443

Conclusions

Dans cet article, nous avons listé les scénarios les plus courants que vous pourriez appliquer pour effectuer la migration d’un ingress controller à un autre.

En pratique, si vous exécutez Kubernetes dans le cloud, vous êtes plus susceptible de vous trouver dans l’un des deux premiers scénarios. Le troisième scénario est plus courant pour les clusters Kubernetes locaux de petite taille.

Dans tous les cas, les ingress controllers sont des composants sans état, ils peuvent donc être changés “à la volée” avec un impact minimal sur le service (voire aucun). En résumé, si votre ingress controller actuel ne répond pas à toutes vos exigences, il est possible de ne pas accumuler de dette technique : vous pouvez migrer en toute sécurité vers un nouvel ingress controller plus adapté !


Ne ratez pas nos prochains articles DevOps et Cloud Native! Suivez Enix sur Twitter!