
Interaction entre Kube-proxy, Services et Endpoints/EndpointSlices
Comprenez comment Kube-proxy, les Services et les Endpoints (ou EndpointSlices) interagissent pour fournir un accès stable et équilibré aux Pods dans Kubernetes.
Le défi : accéder aux Pods éphémères de manière fiable
Nous savons que les Pods dans Kubernetes sont éphémères : ils peuvent être créés, détruits, ou reprogrammés sur d'autres noeuds, changeant ainsi d'adresse IP. Comment les autres Pods (ou les systèmes externes) peuvent-ils accéder de manière fiable à un ensemble de Pods fournissant une fonctionnalité donnée (par exemple, les Pods d'un backend) sans avoir à suivre constamment leurs adresses IP changeantes ?
La réponse réside dans l'objet Service. Un Service fournit une adresse IP virtuelle stable (le ClusterIP, pour le type de Service le plus courant) et un nom DNS stable. Les clients se connectent à cette IP et à ce port de Service. Mais comment cette connexion est-elle ensuite acheminée vers l'un des Pods réels et sains qui fournissent le service ? C'est là qu'intervient une interaction cruciale entre le Service lui-même, les objets Endpoints (ou leurs successeurs plus évolutifs, les EndpointSlices) et le composant Kube-proxy.
Comprendre cette interaction est fondamental pour saisir comment Kubernetes réalise l'abstraction de Service et l'équilibrage de charge de base au sein du cluster.
Services et Endpoints/EndpointSlices : définir la cible et ses instances
Lorsqu'un Service est créé, il est défini par un sélecteur de labels (spec.selector) qui identifie les Pods cibles auxquels ce Service doit acheminer le trafic. Le Service se voit attribuer une adresse IP virtuelle stable (ClusterIP) à partir d'une plage réservée dans le cluster.
Simultanément, le control plane de Kubernetes (spécifiquement le Endpoint Controller ou EndpointSlice Controller) surveille en permanence les Pods qui correspondent au sélecteur du Service. Pour chaque Service, il crée et maintient à jour un ou plusieurs objets qui listent les adresses IP et les ports des Pods actuellement sains et prêts à recevoir du trafic pour ce Service :
- Endpoints : Historiquement, un seul objet Endpoints était créé par Service. Il contenait la liste complète des paires `IP:Port` de tous les Pods correspondants.
- EndpointSlices : Introduits pour améliorer la scalabilité, notamment pour les Services avec des milliers de Pods. Au lieu d'un seul grand objet Endpoints, le contrôleur crée plusieurs objets EndpointSlice plus petits pour le même Service. Chaque EndpointSlice contient un sous-ensemble des `IP:Port` des Pods. Cela réduit l'impact des mises à jour (seul un petit slice doit être modifié/transmis lorsqu'un Pod change d'état) et améliore les performances du control plane et de Kube-proxy. EndpointSlices est le mécanisme par défaut dans les versions récentes de Kubernetes.
Ces objets Endpoints ou EndpointSlices servent de 'liste d'adresses' dynamique que les autres composants peuvent consulter pour savoir où envoyer le trafic destiné à un Service spécifique.
Kube-proxy : le traducteur de Services en règles réseau
Kube-proxy est un démon (agent) qui s'exécute sur chaque noeud du cluster Kubernetes (y compris les noeuds du control plane, bien que son rôle principal soit sur les noeuds workers). Son rôle n'est pas de proxyfier directement le trafic comme le ferait un proxy inverse traditionnel (dans ses modes les plus courants).
La fonction principale de Kube-proxy est de surveiller l'API Server Kubernetes pour détecter les changements dans les objets Service et Endpoints/EndpointSlices. Pour chaque Service, Kube-proxy utilise les informations du Service (ClusterIP, port) et la liste des backends sains (depuis Endpoints/EndpointSlices) pour configurer des règles réseau directement sur le noeud où il s'exécute. Ces règles interceptent le trafic destiné au ClusterIP:Port du Service et le redirigent vers l'une des adresses IP:Port des Pods backend.
En d'autres termes, Kube-proxy traduit l'abstraction de Service en mécanismes concrets de routage et d'équilibrage de charge au niveau du noyau Linux du noeud (dans la plupart des modes).
Les modes de fonctionnement de Kube-proxy
Kube-proxy peut opérer selon différents modes, les plus courants étant iptables et ipvs :
- Mode
iptables: C'est le mode par défaut historique et largement compatible. Kube-proxy crée des règles iptables (principalement dans les chaînes `KUBE-SERVICES`, `KUBE-NODEPORTS`, `KUBE-POSTROUTING`, `KUBE-MARK-MASQ`, etc.). Pour un Service ClusterIP, il crée des règles DNAT (Destination Network Address Translation). Lorsqu'un paquet arrive sur le noeud destiné au ClusterIP:Port, une règle iptables sélectionne l'un des Pods backend (souvent de manière aléatoire ou basée sur des statistiques iptables) et réécrit l'adresse IP et/ou le port de destination du paquet avec ceux du Pod choisi. Le paquet est ensuite routé vers ce Pod. Bien que robuste, ce mode peut souffrir de problèmes de performance à très grande échelle (milliers de Services) en raison de la nature séquentielle du traitement des règles iptables. - Mode
ipvs: Introduit pour améliorer la performance et la scalabilité. Il utilise IPVS (IP Virtual Server), une fonctionnalité d'équilibrage de charge intégrée au noyau Linux, plus performante qu'iptables pour un grand nombre de services car elle utilise des tables de hachage. Kube-proxy configure des services virtuels IPVS correspondant aux Services Kubernetes et ajoute les IPs des Pods backend comme serveurs réels. IPVS offre également plus d'algorithmes d'équilibrage de charge (ex: round-robin, least connections). Il utilise toujours iptables pour certaines fonctionnalités comme le masquerading ou pour gérer le trafic des NodePorts, mais le chemin principal de l'équilibrage de charge pour les ClusterIPs passe par IPVS. - Mode
userspace(Obsolète) : Le mode original où Kube-proxy agissait réellement comme un proxy en espace utilisateur, écoutant sur le port du Service et transférant les connexions. Très peu performant et n'est plus utilisé ni recommandé. - Mode
kernelspace(Windows) : Equivalent fonctionnel pour les noeuds Windows, utilisant les capacités réseau du noyau Windows.
Le choix du mode (iptables ou ipvs) est une configuration du cluster lors de son installation.
Le flux d'une requête vers un Service
Imaginons un Pod Client sur `Node A` qui veut contacter `my-service` (ClusterIP `10.0.0.5`, port `80`) dont les Pods backend sont sur `Node B` et `Node C`.
- Le Pod Client fait une requête vers `10.0.0.5:80`.
- La requête sort du Pod Client et atteint la pile réseau du `Node A`.
- Les règles configurées par Kube-proxy sur `Node A` (en mode iptables ou ipvs) interceptent ce paquet destiné au ClusterIP.
- Basé sur la liste des Endpoints/EndpointSlices pour `my-service` (qu'il a obtenue de l'API Server), Kube-proxy (ou plutôt les règles qu'il a programmées) sélectionne un Pod backend sain, disons le Pod sur `Node B` avec l'IP `192.168.1.10` et le port cible `8080`.
- La règle iptables/ipvs sur `Node A` réécrit l'adresse IP et le port de destination du paquet en `192.168.1.10:8080`.
- Le système de routage du `Node A` (géré par le CNI) achemine le paquet modifié vers `Node B`.
- Le paquet arrive sur `Node B`, est traité par sa pile réseau et finalement livré au Pod backend sur le port `8080`.
Grâce à Kube-proxy qui maintient les règles à jour sur chaque noeud en fonction de l'état des Services et des Pods, le Pod Client n'a jamais besoin de connaître les adresses IP réelles ou l'emplacement des Pods backend. Il utilise simplement l'adresse stable du Service, et Kube-proxy s'occupe du reste en coulisses via les mécanismes du noyau Linux.