Ingress et Load Balancers dans Kubernetes avec MetalLB et nginx-ingress

Ingress et Load Balancers dans Kubernetes avec MetalLB et nginx-ingress

Vous appréciez notre travail......nous recrutons !

Ne ratez pas nos articles sur l'open source, le big data et les systèmes distribués, fréquence faible d’un email tous les deux mois.

Lorsque l’on souhaite exposer des services depuis un cluster Kubernetes et les rendre accessibles depuis l’extérieur du cluster, la solution la plus adéquate est d’utiliser des services de type load-balancer afin de rediriger le trafic jusqu’au pod concerné. Dans un cluster bare-metal, utiliser ce type de services laisse l’adresse externe EXTERNAL-IP du service comme “pending”, ce qui rend les services concernés inaccessibles depuis l’extérieur. Comparativement, c’est une fonctionnalité supportée nativement par les clusters administrés par les fournisseurs Cloud. Intéressons-nous à l’obtention d’un résultat similaire dans un cluster bare-metal.

Ces dernières années, les offres de clusters Kubernetes administrés par les fournisseurs Cloud ont prospéré, avec la croissance continue d’Amazon EKS, Google GKE ou encore Microsoft Azure AKS. Nous aimons bricoler et expérimenter : la flexibilité de notre installation nous importe grandement, surtout dans notre administration de clusters. C’est pour cela que nous avons choisi un cluster physique, ceux-ci offrant une performance accrue grâce à une latence réseau minime et des entrées/sorties plus rapides.

Comme notre cluster n’est pas rattaché à un fournisseur Cloud, il lui manque une fonctionnalité cruciale : le load-balancing. Les load-balancers ont pour rôle de rediriger le trafic réseau entrant vers le bon nœud. Il s’agit d’une manière d’équilibrer la charge au sein du cluster, fournissant un moyen efficace d’empêcher la surcharge de nœuds. Ils établissent également un point de contact unique entre le réseau et le cluster.

Afin d’implémenter cette fonctionnalité au sein du cluster, une solution purement logicielle existe : MetalLB. MetalLB rend l’utilisation du load-balancing pour les services externes possible et, combiné avec un contrôleur Ingress, apporte une couche dynamique entre le réseau et les services. Pourquoi cette couche est dynamique ? Les services exposés à travers le contrôleur Ingress peuvent changer, certains nœuds peuvent tomber ou être ajoutés : l’accès se fera toujours à travers la même adresse IP liée avec le service qui expose le pod du contrôleur Ingress, sans perdre l’accessibilité aux services.

Utiliser seulement un contrôleur Ingress signifie qu’un nœud du cluster doit être exposé afin de rendre les services accessibles, ce qui crée un point de défaillance unique pour le cluster si ce nœud tombe. Dans le mode Layer2 de MetalLB, l’adresse IP du service n’expose pas le nœud. De plus, un basculement de nœud a lieu dans le cas où le nœud en charge de gérer le trafic tombe : un autre nœud prend le relais.

Nous allons voir comment MetalLB et nginx-ingress se combinent afin d’implémenter un load-balancer dans un cluster Kubernetes physique.

Pré-requis

Avant d’expliquer le fonctionnement de MetalLB, nous devons vérifier que votre cluster est en accord avec les pré-requis :

  • Un cluster Kubernetes version 1.13.0 ou plus ;
  • Un plugin CNI (Container Network Interface) compatible avec MetalLB (référez-vous à la liste des CNI compatibles de MetalLB) :
    • Canal ;
    • Cilium ;
    • Flannel ;
    • Kube-ovn ;
    • Calico (majoritairement) ;
    • Kube-router (majoritairement) ;
    • Weave-net (majoritairement).
  • Des adresses IPv4 uniques au sein du réseau du cluster pour configurer des pools d’adresses IP ;
  • Le trafic UDP et TDP sur le port 7646 doit être autorisé entre les nœuds, MetalLB utilisant Go Memberlist.

Comment fonctionne MetalLB ?

MetalLB doit être déployé depuis le control-plane. Le control-plane gère les ressources du cluster. Pour les cluster à petite échelle, il s’agit d’un nœud maître qui orchestre et planifie le déploiement des pods, sélectionne sur quels nœuds déployer ces pods, et gère leur cycle de vie.

Le déploiement de MetalLB sur le control-plane va créer des pods pour les ressources suivantes :

  • Le MetalLB controller ;
  • Les MetalLB speakers.

Le pod contrôleur gère la configuration du load-balancer en utilisant une ou plusieurs pools d’adresses IP fournies par l’utilisateur et distribue ces adresses IP de la pool sélectionnée aux services de type load-balancer.

Les pods speakers sont ensuite déployés sur les nœuds du cluster avec leurs “listeners” et “role bindings” correspondants. Chaque nœud travailleur reçoit un pod speaker, et un des pods est choisi depuis la memberlist pour être responsable du routage du trafic. Si le nœud contenant ce pod ne fonctionne plus, un autre pod dans un autre nœud sera choisi pour le remplacer.

MetalLB possède trois modes :

  • Layer2 ;
  • BGP ;
  • FRR (expérimental).

Dans ce tutoriel, nous utiliserons le mode Layer2. Il utilise l’Address Resolution Protocol (ARP) pour résoudre les adresses MAC des nœuds par des adresses IPv4.

Note : Si votre cluster utilise des adresses IPv6, ce mode utilise à la place le Neighbor Discovery Protocol.

Le mode BGP, pour Border Gateway Protocol, communique avec ses pairs BGP au sein du cluster pour fournir les services de type load-balancer avec une adresse d’une pool d’adresses IP.

Enfin, le mode FRR est une extension du mode BGP, supportant la Bidirectional Forwarding Detection, rendant ce mode compatible avec les adresses IPv6.

Chaque mode a ses avantages et inconvénients. Le mode Layer2 est intéressant, offrant de la redondance et du basculement pour les nœuds. C’est également le mode le plus facilement configurable. Néanmoins, il possède deux inconvénients : du bottlenecking et un basculement lent. Pour aller plus en détails, le schéma suivant illustre le fonctionnement de MetalLB en mode Layer2. Il est important de noter que ce cas d’étude est établi avec des assomptions.

Un cluster est composé d’un nœud maître et de trois nœuds travailleurs avec :

  • le contrôleur MetalLB spécifiquement déployé sur le nœud maître en modifiant le manifeste ;
  • un déploiement nginx server de 2 répliques ainsi que le service associé ;
  • un réseau hôte correspondant à ce subnet : 192.168.120.0/24.

Schéma décrivant l'architecture du cluster avec MetalLB en mode Layer2

Le contrôleur génère l’IP du service de type load-balancer en sélectionnant la première adresse libre de la pool d’adresses IP fournies. La pool d’adresses IP doit être composée d’adresses IP unique du réseau hôte.

Quand des données entrent à l’IP du load-balancer, elles sont redirigées vers le pod speaker préalablement élu par le contrôleur. Dans cet exemple, le metallb-speaker du nœud worker-node-1 a été élu, permettant au pod de partager l’information avec kube-proxy, cherchant le pod à accéder.

Si le nœud du pod speaker ne fonctionne plus, un autre speaker prend sa place. Si nous avions décidé d’ajouter un deuxième service de type load-balancer, son IP de load-balancer serait la prochaine IP disponible, 192.168.120.201.

Note : Ce mode n’implémente pas de vrai load-balancing, car tout le trafic est dirigé vers la node contenant le pod speaker choisi. Les modes BGP et FRR sont plus efficaces pour cette situation, répartissant le trafic à la manière d’un load-balancer cloud.

Tutoriel

Étape 1 : Installation de MetalLB

Afin de mettre en pratique ce que nous venons de voir, déployons MetalLB sur un environnement de test local en déployant les manifestes suivants :

  • namespace.yaml crée un namespace MetalLB
  • metallb.yaml déploie le contrôleur et speakers MetalLB, ainsi que les role bindings et listeners nécessaires
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/namespace.yaml
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.12.1/manifests/metallb.yaml

Une fois MetalLB installé, nous devons configurer une pool d’adresses IP. En la déployant, le contrôleur alloue des adresses IP pour le load balancer. La pool d’adresses IP peut contenir une ou plusieurs adresses IP, en fonction des besoins. Les adresses allouées doivent faire partie du réseau hôte du cluster et être uniques.

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - {ip-start}-{ip-stop}

Sauvegardons ce fichier sous le nom config-pool.yaml et appliquons-le simplement en utilisant kubectl apply -f config-pool.yaml.

Note : Il est possible de déployer plus d’une pool d’adresses IP.

Étape 2 : Tests

Pour éviter toute confusion pouvant être causée par le nom du serveur que nous allons déployer, dans les sections restantes de l’article, nous nommerons :

  • nginx-ingress comme nginx-ingress qui est le contrôleur Ingress qui expose les services
  • l’image serveur nginx comme nginx-server qui est un serveur web qui correspond au service de test que nous exposons

Déployons et exposons nginx-server sur un nœud pour voir si notre installation est un succès :

kubectl create deploy nginx --image nginx
kubectl expose deploy nginx --port 80 --type LoadBalancer

Pour accèder au service de type load-balancer, nous devons récupérer le champ EXTERNAL-IP de nginx-server.

INGRESS_EXTERNAL_IP=`kubectl get svc nginx -o jsonpath='{.status.loadBalancer.ingress[0].ip}'`
curl $INGRESS_EXTERNAL_IP

Le résultat désiré est le suivant :

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Nettoyons les éléments crées pendant la phase de test :

kubectl delete deployment nginx
kubectl delete svc nginx

Associer MetalLB et nginx-ingress

Dans la partie précédente, nous avons réussi à déployer un service et à le rendre accessible en dehors du cluster. Cette configuration n’est toutefois pas adéquate, car une adresse IP différente est allouée à chaque fois que nous déployons un service. Nous voulons être capable d’accéder à un service depuis une adresse IP unique. Pour y parvenir, nous utilisons nginx-ingress.

Les Ingress sont un moyen d’exposer des services en dehors du cluster. Ils permettent à des hôtes et des routes d’être créés, permettant ainsi la résolution DNS d’adresses IP. nginx-ingress écoute et gère chaque service exposé sur les ports 80 et 443. Il permet à plusieurs services de coexister sur la même adresse IP, en utilisant des hôtes différents, ce qui implique d’une seule adresse IP est utilisée.

Dans les clusters Kubernetes administrés par les fournisseurs Cloud, les Ingress sont habituellement exposés à travers des load-balancers. Dans les clusters bare-metal, les Ingress sont généralement exposés à travers des services NodePort ou grâce à la configuration HostNetwork, ce qui n’est pas souhaitable car un nœud du cluster doit être exposé, ce qui induit un point de défaillance unique. Si le nœud concerné tombe, l’accès aux services auparavant accessible à travers ce nœud sera redirigé par défaut sur un autre nœud, ce qui veut dire nous devrons gérer manuellement le changement d’adresse IP du nœud à un autre.

Utiliser MetalLB et nginx-ingress permet de se rapprocher d’un load-balancer en défèrant les accès aux services. L’adresse IP reste la même, même si un nœud tombe grâce au mécanisme de basculement de MetalLB, et les services sont centralisés grâce à nginx-ingress.

En utilisant les assomptions précédentes, nous pouvons mettre à jour le schéma :

Schéma décrivant le fonctionnement de MetalLB avec une gateway nginx

La ConfigMap fournit les adresses IP aux services de type load-balancer, et le contrôleur MetalLB élit un pod speaker, comme vu précédemment.

1 : Une requête arrive sur http://nginx.example.com. Ce nom DNS est résolu comme l’adresse IP du load-balancer du service exposant le pod nginx-ingress-controller : 192.168.120.200.

2 : Le trafic quitte le service jusqu’au pod metallb-speaker élu qui communique avec les autres pods metallb-speaker afin de rédiriger le trafic au pod nginx-ingress-controller.

3 : Le trafic va vers le pod nginx-ingress-controller.

4 : Le pod nginx-ingress-controller envoie le trafic au service dont la demande d’accès a été fait à travers le nom d’hôte nginx.example.com. Le pod nginx-server est contacté et sert le site internet au client externe.

Étape 1 : Installation de nginx-ingress

Déployons le manifest suivant sur un nœud du control-plane :

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.2.0/deploy/static/provider/cloud/deploy.yaml

Étape 2 : Vérification

Avec la commande suivante, vérifions si le champ EXTERNAL-IP est en état pending ou non.

kubectl get service ingress-nginx-controller --namespace=ingress-nginx

Normalement, la première adresse de la config-map devrait être allouée au service ingress-nginx-controller.

Exemple pratique

Pour tester notre nouvelle installation, déployons deux services sans en spécifier le type, par défaut liant le service avec la ClusterIP des pods. Premièrement, nginx-server :

  • Créez un déploiement
  • Exposez le déploiement
  • Créez un ingress sur le nom d’hôte nginx.example.com
kubectl create deployment nginx --image=nginx --port=80
kubectl expose deployment nginx
kubectl create ingress nginx --class=nginx \
  --rule nginx.example.com/=nginx:80

Suivez les mêmes étapes pour le déploiement du serveur httpd, qui est similaire au serveur nginx-server et est simplement un service que l’on expose, agissant comme un “Hello world”, sur le nom d’hôte httpd.example.com :

kubectl create deployment httpd --image=httpd --port=80
kubectl expose deployment httpd
kubectl create ingress httpd --class=nginx \
  --rule httpd.example.com/=httpd:80

Nous devons accèder aux services à travers l’ordinateur hôte pour voir s’ils ont été correctement exposés. Afin de rendre la procédure d’accès au site plus aisée, nous devons ajouter une règle de DNS en modifiant le fichier /etc/hosts sur l’ordinateur hôte. Nous obtenons le champ EXTERNAL-IP du service ingress-nginx-controller :

INGRESS_EXTERNAL_IP=`kubectl get svc --namespace=ingress-nginx ingress-nginx-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'`
echo $INGRESS_EXTERNAL_IP

Après avoir récupéré l’IP du service, nous mettons la ligne suivante dans /etc/hosts, en remplaçant la variable INGRESS_EXTERNAL_IP par celle que nous avons précedemment copié : $INGRESS_EXTERNAL_IP nginx.example.com httpd.example.com.

Maintenant, accéder aux services se fait sur la même adresse IP en utilisant la résolution DNS. Nous pouvons ouvrir le navigateur sur l’ordinateur hôte et entrer les adresses manuellement, ou utiliser curl afin d’obtenir les pages Web grâce aux commandes suivantes :

curl nginx.example.com
curl httpd.example.com

Dans le cas où changer le fichier /etc/hosts n’est pas possible, nous pouvons utiliser curl avec l’option -H afin d’indiquer le nom d’hôte sur l’ordinateur hôte :

curl $INGRESS_EXTERNAL_IP -H "Host: nginx.example.com"
curl $INGRESS_EXTERNAL_IP -H "Host: httpd.example.com"

Nous nous attendons à voir “It works” pour httpd.example.com et la même chose que dans les tests de MetalLB pour nginx.example.com.

Pour nettoyer les ressources utilisées pour les tests, supprimons ce que nous avons créé :

kubectl delete deployment nginx
kubectl delete svc nginx
kubectl delete ingress nginx
kubectl delete deployment httpd
kubectl delete svc httpd
kubectl delete ingress httpd

Problèmes communs

Parfois, nginx-ingress a un problème lié aux notifications web hook : Internal error occurred: failed calling webhook "validate.nginx.ingress.kubernetes.io".

Ceci était mentionné dans cette issue GitHub.

Un workaround simple est de supprimer la ValidatingWebhookConfiguration :

kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission

Conclusion

Il vous est dorénavant possible d’utiliser un load-balancer tel que MetalLB et de l’utiliser en tandem avec nginx-ingress pour créer un unique point d’accès aux services de votre cluster. MetalLB est un outil pratique pour les clusters bare-metal ou on-premise grâce à son mécanisme de basculement. Ce sont des logiciels simples d’utilisation qui, malgré des limitations relatives aux CNI, fournissent une bonne expérience utilisateur et sont très faciles à déployer.

Tout n’est cependant pas parfait, le temps de réponse est lent. De plus, des bottlenecks peuvent occasionnellement arriver étant donné qu’il ne s’agit pas d’un réel load-balancer distribuant des paquets. Il s’agit malgré tout d’un outil utile nous rendant la vie plus facile.

Partagez cet article

Canada - Maroc - France

Nous sommes une équipe passionnée par l'Open Source, le Big Data et les technologies associées telles que le Cloud, le Data Engineering, la Data Science le DevOps…

Nous fournissons à nos clients un savoir faire reconnu sur la manière d'utiliser les technologies pour convertir leurs cas d'usage en projets exploités en production, sur la façon de réduire les coûts et d'accélérer les livraisons de nouvelles fonctionnalités.

Si vous appréciez la qualité de nos publications, nous vous invitons à nous contacter en vue de coopérer ensemble.

Support Ukrain