Du BGP avec MetalLB

Du BGP avec MetalLB

Avant de commencer...

Comme souvent dans mes articles, je dois vous informer que la démonstration qui sera faite se base sur une nouvelle version du projet Kubeadm avec Vagrant et Ansible, idéal pour réviser votre certification !, n'hésitez pas à consulter cet article pour vous familiariser avec les technologies Vagrant et Ansible.

De plus, une introduction à MetalLB a déjà été effectuée avec l'article suivant : À la découverte de MetalLB. Les principaux concepts ne seront pas réexpliqués dans celui-ci.

Néanmoins, cet article a pour objectif de voir une configuration avancée de MetalLB avec la mise en place du BGP, protocole qui sera expliqué en dessous. C'est généralement la configuration qui simulera le mieux un répartiteur de charge, plutôt que celle que nous avons vu précédement (layer2).

Qu'est-ce que le BGP ?

Le BGP ou Border Gateway Protocol est un protocole de routage qui permet d'interconnecter plusieurs réseaux entre eux, définis comme des
Autonomous Systems (AS).

Il utilise le protocole de transport TCP sur le port 179 afin d'échanger différentes informations de routage. Ce protocole est très connu et pour cause, il est utilisé pour Internet.

Afin de mettre en place ce protocole, l'outil open-source BIRD permettra de transformer une machine virtuelle utilisant un système d'exploitation GNU/Linux en un routeur pour configurer le BGP.

Chaque machine que l'on souhaite déclarer au routeur est appelée peer ou neighboor. Dans notre cas, les noeuds de type worker contenant chaque Pod speaker de MetalLB seront déclarés.

BGP versus Layer 2

Le mode BGP de MetalLB permet, lors de la création du répartiteur de charge (load balancer) d'annoncer au routeur avec lequel il établit la connexion, l'IP externe du service. Ce qui permet de rendre accessible ce service depuis le routeur.

Si un poste client est correctement configuré au routeur, il pourra faire un curl de l'IP externe du service de type LoadBalancer sans aucun souci.

Si notre cluster possède plusieurs noeuds worker et que l'on interconnecte ces workers au routeur, cela permet une véritable répartition de charge, car les routes publiées par MetalLB sont équivalentes les unes aux autres, à l'exception du prochain saut, c'est ce que l'on appelle l'ECMP (Equal-cost multi-path routing). Enfin, c'est le kube-proxy qui va acheminer le paquet vers le Pod en question.

Exemple ici avec service de type LoadBalancer possédant l'IP 172.16.254.1 et avec un cluster possédant trois noeuds worker :

bird> show route stats
172.16.254.1/32    via 10.0.0.51 on enp0s8 [peer1 18:05:44] * (100) [AS22000?]
                   via 10.0.0.52 on enp0s8 [peer2 18:05:44] (100) [AS22000?]
                   via 10.0.0.53 on enp0s8 [peer3 18:05:44] (100) [AS22000?]

Les trois noeuds worker (10.0.0.51, 10.0.0.52 et 10.0.0.53) sont déclarés comme des routes possibles à emprunter.

Comme vous l'avez vu dans l'article présentant MetalLB, le mode layer2 a le gros avantage de ne pas nécessiter de configuration spécifique, en revanche, il n'y aura qu'un seul noeud worker qui se chargera d'être le point d'entrée du service. Ce qui ne permet pas de redistribuer la charge équitablement sur l'ensemble des noeuds et empêche la mise à l'échelle (scaling).

Comme vous l'aurez compris, le layer2 est réservé aux environnements de test ou PoC (Proof of Concept), tandis que le BGP s'inscrit dans une démarche de production.

Architecture

Avant de commencer à manipuler le cluster Kubernetes ou les équilibreurs de charge, il est important de parcourir l'architecture réseau du projet.

metallb-bgp-architecture

Comme vous pouvez le voir, il y a deux réseaux :

  • Un premier, pour le cluster qui permet de faire tourner les noeuds master/controlplane et le ou les worker(s). La plage réseau utilisée est 10.0.0.0/24 et l'ASN (Autonomous Systems Number) de 22000 ;
  • Un second, qui va contenir notre client qui permettra d'exécuter différentes opérations sur le cluster Kubernetes. La plage réseau utilisée est 172.16.0.0/24 et l'ASN est de 11000.

Ces deux réseaux sont reliés par un routeur qui possède deux cartes réseau, une sur le réseau client-network et l'autre sur le réseau k8s-network.

Le projet

Comme d'habitude, les outils Vagrant et Ansible sont nécessaires sur votre poste de travail si vous souhaitez exécuter ce qui va suivre.

Le dépôt de code est disponible à l'adresse suivante :

https://github.com/axinorm/kubeadm-automation-bgp

Vagrant

La configuration de Vagrant se trouve comme à son habitude dans le fichier Vagrantfile, elle se compose de plusieurs parties : routeur, master, workers et client.

Plusieurs réseaux ont été définis avec l'instruction :

router.vm.network "private_network", ip: <plage adresses IP>,
  netmask: '255.255.255.0', virtualbox__intnet: '<nom réseau interne>'

Le paramètre virtualbox__intnet permet de définir un nom pour le réseau interne et interconnecter les autres machines entre elles

L'image et la version de celle-ci sont fixées pour chaque machine virtuelle :

client.vm.box = IMAGE_NAME
client.vm.box_version = IMAGE_VERSION
client.vm.synced_folder '.', '/vagrant', disabled: true

De plus, le répertoire partagé vagrant est désactivé, car il n'est pas nécessaire pour ce cas d'utilisation.

Ansible

Le fichier contenant le playbook principal se trouve à la racine du projet avec le nom site.yml, on retrouve l'ensemble des rôles qui seront joués sur les différentes machines.

Par rapport à la version précédente, deux nouveaux hôtes ont été ajoutés : le router et le client.

- hosts: router
  gather_facts: true
  become: true
  roles:
    - { role: bird, tags: bird }
...
- hosts: client
  gather_facts: true
  become: true
  roles:
    - { role: client/route-config, tags: client }
    - { role: client/kubectl, tags: client }
    - { role: client/helm, tags: client }

Sur le router, on installe BIRD et on initialise sa configuration qui sera détaillée dans le prochain point.

Tandis que sur le client, on va venir paramétrer la machine virtuelle avec netplan qui est dans le rôle client/route-config qui permet de définir la configuration du réseau avec un fichier YAML.

Comme vous pouvez le voir ici :

network:
  version: 2
  renderer: networkd # Service réseau utilisé
  ethernets:
    enp0s8: # Carte réseau concernée
      routes:
        # Création d'une route pour atteindre le cluster Kubernetes depuis le routeur
        - to: {{ kubernetes_network_base }}.0/24 # 10.0.0.0/24 par défaut
          via: {{ client_network_base }}.1
        # Création d'une route pour atteindre le réseau BGP des load balancers depuis le routeur
        - to: {{ bgp_network_base }}.0/24
          via: {{ client_network_base }}.1 # 172.16.0.0/24 par défaut

Enfin, deux outils sont installés, kubectl pour atteindre le cluster Kubernetes et helm pour installer des charts.

BIRD

Pour ce qui est de la configuration de BIRD, elle se trouve dans le dossier roles à l'emplacement roles/bird/templates/bird.conf.j2 :

router id {{ client_network_base }}.1; # Adresse du routeur côté client

protocol direct {
  interface "lo"; # Enlève l'interface du routage de BIRD
}

protocol kernel {
  persist; # Conserve les règles même en arrêtant le service
  scan time 20; # Scanne la table de routage toutes les 20 secondes
  merge paths on; # Active l'ECMP 
  import all; 
  export all;
}

protocol device {
  scan time 10; # Scanne les interfaces toutes les 10 secondes
}

# On configure l'ensemble des noeuds worker
{% for worker_number in range(1, (kubernetes_worker_number | int) + 1) %}
protocol bgp peer{{ worker_number }} {
  local as 11000;
  neighbor {{ kubernetes_network_base }}.{{ worker_number + 50 }} as 22000;
  import all;
  export all;
}
{% endfor %}

MetalLB

Dans le fichier install_metallb.yml, on retrouve l'initialisation du chart de MetalLB avec une configuration spécifique pour notre architecture :

...
values:
  configInline:
      peers:
      - my-asn: 22000
      peer-asn: 11000
      peer-address: 10.0.0.1
      address-pools:
      - name: my-ip-space
      protocol: bgp
      avoid-buggy-ips: true
      addresses:
      - 172.16.254.0/24

On réserve la plage d'adresses IP 172.16.254.0/24 pour nos équilibreurs de charge qui seront annoncés au routeur. L'adresse 10.0.0.1 représente l'IP du routeur, my-asn est l'ASN du réseau client et peer-asn l'ASN du réseau Kubernetes.

Le paramètre avoid-buggy-ips: true évite d'utiliser les adresses .0 et .255 du réseau comme c'est indiqué ici.

Pour aller plus loin

Démonstration

Pour démarrer le tout, une commande est à exécuter :

vagrant up

Une fois l'ensemble des machines configurées, il ne reste plus qu'à installer MetalLB sur le cluster avec les deux commandes suivantes :

# Installation de la collection kubernetes.core
ansible-galaxy collection install kubernetes.core

# Installation du chart Helm de MetalLB avec la configuration spécifique pour notre cluster
ansible-playbook install_metallb.yml -i .vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory

Notre cluster est opérationnel, pour tester la création de load balancer, connectez-vous sur la machine client et exécutez ces commandes :

vagrant ssh client

# Création d'un déploiement nginx de 3 replicas
k create deploy nginx --image=nginx --replicas=3
# Création du service de type LoadBalancer
k expose deploy nginx --type=LoadBalancer --port=80

Une fois cela fait, vous pouvez récupérer l'IP externe de votre service :

vagrant@client:~$ k get svc
NAME         TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)        AGE
kubernetes   ClusterIP      10.96.0.1        <none>         443/TCP        152m
nginx        LoadBalancer   10.102.226.112   172.16.254.1   80:32730/TCP   4s

Il s'agit de 172.16.254.1 dans notre cas, et vous pouvez faire un curl dessus :

vagrant@client:~$ curl 172.16.254.1
<!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>

Tout est fonctionnel, notre réseau BGP fonctionne à merveille et tout est accessible depuis la machine virtuelle client !

Conclusion

Comme vous avez pu le voir, la configuration de MetalLB est plus compliquée qu'avec le mode layer2 présenté dans un article précédent. Cependant, cette configuration est beaucoup plus résiliente comme je vous l'ai exposé dans la partie BGP versus Layer 2.

Vous pouvez donc, avec ce projet, simuler des services de type LoadBalancer et avoir un résultat similaire aux services managés présents dans le Cloud.

Dans un prochain article, nous compléterons (encore) ce projet avec la mise en place de Traefik en tant qu'Ingress Controller pour faire sortir vos services du clusters !