Le RIPE vient d’allouer son dernier bloc d’adresses IPv4. Quel impact cela va-t-il avoir pour des applications Cloud Native ?
Spoiler alert : pas grand chose. Mais voyons plus en détail pourquoi les plateformes Cloud Native (et en particulier, Kubernetes) ne sont pas gourmandes en adresses IPv4.
État des lieux
Deux machines qui communiquent entre elles via Internet (par exemple, un smartphone et un serveur web) ont besoin chacune d’une adresse IP. (On dit parfois pour simplifier, “une IP”.) La majeure partie d’Internet utilise encore IPv4 (Internet Protocol version 4), dans lequel une adresse est codée sur 32 bits. On les écrit couramment sous la forme de quatre octets séparés par des points, par exemple 127.0.0.1
. En théorie, on peut donc avoir un peu plus de 4 milliards (2^32) de machines sur Internet. En pratique, on estime qu’il y a près de 20 milliards d’appareils connectés à Internet. Cela fait donc un bon moment qu’il n’y a plus assez d’adresses IP pour tout le monde.
Mais alors, comment ça marche ?
Adresses publiques, adresses privées
Il existe trois plages d’adresses dites “privées”, définies par la RFC1918 :
- 10.0.0.0/8,
- 172.16.0.0/12,
- 192.168.0.0/16.
Si vous regardez l’adresse IP de votre ordinateur en ce moment (avec ifconfig sous Linux, ipconfig sous Windows, dans les préférences réseau sous MacOS…) il y a de grandes chances que votre adresse appartienne à l’une de ces trois plages.
En revanche, si vous tapez dans votre moteur de recherche “what’s my IP address?" il vous donnera une adresse différente. Cette adresse est une adresse publique.
Une adresse IP publique est “routable” sur tout Internet. Cela veut dire que n’importe qui, n’importe quelle machine connectée à Internet, peut envoyer un paquet à cette adresse, et la magie d’Internet l’acheminera à destination. (La “magie d’Internet”, c’est entre autres le protocole BGP et sans doute une quantité non négligeable de poudre verte.)
En revanche, l’adresse de notre ordinateur (par exemple, la mienne en ce moment est 192.168.1.218
) est une adresse privée, valide uniquement à l’intérieur de notre réseau local (par exemple, mon WiFi à la maison). Elle permet de communiquer avec les autres appareils de ce réseau local, mais elle n’est pas reconnue en dehors de ce réseau. Si vous essayez d’envoyer un paquet à 192.168.1.218
depuis chez vous, il y a plusieurs scénarios possibles :
- si votre réseau est
192.168.1.0/24
(ou un autre réseau auquel appartient cette adresse), et que l’adresse a été allouée à une machine de votre réseau, cette machine recevra le paquet à ma place ; - si votre réseau est différent, le paquet va passer par votre “route par défaut”, c’est-à-dire le routeur qui vous connecte à Internet ; par exemple votre point d’accès WiFi ou votre routeur DSL, câble, fibre … Et la plupart du temps, ce routeur (ou bien le suivant sur la route) va reconnaître que ce paquet est destiné à une adresse locale, et le jeter à la poubelle sans chercher à négocier.
Donc, comment ça marche ?
Côté client
Cela fait des décennies qu’on utilise une technique appelée IP masquerading pour permettre à plusieurs machines de partager la même adresse IP publique de manière transparente. Le principe est simple : lorsqu’un paquet doit passer du réseau privé au réseau public (par exemple, lorsque j’ouvre https://kubernetes.io dans mon navigateur), ce paquet passe par le fameux routeur évoqué plus haut. Ce routeur va remplacer l’adresse IP source de ma machine (par exemple, 192.168.1.218) par la sienne (par exemple, 70.254.192.42). Cela permettra à ce paquet d’arriver à destination. Les réponses à ce paquet sont envoyées à notre routeur, qui “reconnaît” qu’elles correspondent à mon paquet initial. Le routeur fait l’opération inverse, et m’envoie ces paquets comme s’ils m’avaient été adressés directement.
(Notons qu’on appelle aussi cette technique “NAT”, pour Network Address Translation. Techniquement, le terme NAT correspond à une technique différente de l'IP masquerading, mais à part les admins réseau de haut niveau, pas grand-monde ne connaît la différence, donc personne ne nous en voudra si on appelle ça du NAT quand même.)
Ce fameux IP masquerading marche tellement bien, que peu de gens se préoccupent du problème d’épuisement des adresses IPv4. On pourrait passer à IPv6, qui utilise 128 bits par adresse, ce qui est tellement énorme que chacun de nos cheveux pourrait sans problème avoir sa propre adresse IPv6 (je ne sais pas si ça faciliterait le boulot chez le coiffeur, mais ça vous donne une idée des ordres de grandeur). En fait, je viens de faire le calcul : même si chaque atome de chaque être humain sur Terre avait sa propre adresse IPv6, on utiliserait toujours moins de 1% des adresses disponibles. Le problème, c’est que passer à IPv6 demande aux opérateurs et aux hébergeurs tout un travail de configuration, de supervision, mais aussi de formation des équipes. Tant qu’on ne sera pas complètement coincés, ce travail ne sera pas considéré comme prioritaire. Autrement dit, on n’est pas sortis de l’auberge ; IPv4 (et notre IP masquerading) a encore de beaux jours devant lui.
Sauf que, nos serveurs aussi ont besoin d’adresses IP. Traditionnellement, à chaque fois qu’on commande un serveur chez Amazon, Enix, ou OVH, ce serveur a une adresse IP publique.
Et donc, comment on va faire pour que ça marche ?
Côté serveur
Quand on commande un serveur, en effet, on a besoin de s’y connecter d’une manière ou d’une autre. Il est donc bien utile qu’il ait une adresse IP publique. Mais quand on a tout un ensemble de serveurs, c’est une autre histoire.
Par sécurité, on souhaite souvent empêcher l’accès direct aux serveurs qui n’en ont pas besoin. Par exemple, une base de données ou une API interne n’ont pas besoin d’être accessibles de l’extérieur. On va donc, par sécurité, filtrer les accès venant de l’extérieur (avec un firewall, par exemple). Mais on peut aussi placer ces serveurs sur un réseau privé, interne, avec des adresses IP privées. Cela permet d’économiser autant d’adresses IP publiques. Pour s’y connecter de l’extérieur, on utilise alors un “bastion” (une machine relais connectée à la fois au réseau public et privé) ou un VPN.
Quant aux services exposés au monde extérieur, on veut généralement qu’ils soient redondants, c’est-à-dire que la panne d’un serveur ne mette pas tout le service par terre. Pour ça, on utilise souvent un répartiteur de charge (ou load balancer). Celui-ci reçoit les connexions des clients, et les distribue sur un ensemble de serveurs. Lorsqu’un serveur tombe en carafe, le load balancer cesse de lui envoyer des connexions, et normalement, les clients n’y voient que du feu. Seul le load balancer a besoin d’une adresse IP publique, et il peut ensuite communiquer avec les serveurs (les “backends”) via un réseau interne, avec des adresses privées.
Bilan : tous les services, internes ou externes, peuvent être portés par des serveurs ayant des adresses privées, et on n’a plus besoin d’adresses publiques, sauf pour les load balancers et éventuellement les fameux “bastions” évoqués plus haut.
Ah oui, mais si on travaille à l’ancienne avec des adresses IP publiques partout, ça peut demander pas mal de boulot de ré-architecturer tout ça. Je ne dis pas le contraire.
Et quand on utilise des containers, comment ça marche ?
Côté containers
Sauf exception rarissime, les containers utilisent déjà des adresses IP internes. Cela veut dire que pour se connecter à un container, on utilise déjà un subterfuge quelconque. Si on déploie nos containers sur un hôte Docker isolé, on va souvent utiliser du port mapping pour les services TCP, et un reverse proxy comme Traefik pour les services HTTP.
Le port mapping va permettre d’utiliser des ports différents. Par exemple, si j’ai 4 serveurs PostgreSQL dans des containers, je peux les “exposer” sur les ports 5001, 5002, 5003, 5004.
Le principe du reverse proxy n’est pas nouveau non plus. Lorsqu’une requête HTTP arrive sur un serveur web, celui-ci peut examiner les en-têtes de la requête avant de décider ce qu’il va en faire. Il peut en particulier utiliser l’en-tête Host:
, qui contient le nom de domaine auquel la requête est adressée. Par exemple, au moment où j’écris ces lignes, www.k8s.io
et docs.k8s.io
correspondent à la même adresse IP (et donc au même serveur). Quand je fais une requête vers ces deux domaines, les requêtes vont vers le même serveur, mais la requête contient un en-tête Host: www.k8s.io
(ou respectivement, Host: docs.k8s.io
) afin que ce serveur sache où l’envoyer. C’est grâce à ce procédé que depuis des dizaines d’années, un même serveur est capable d’héberger un grand nombre de sites, sans avoir besoin d’une adresse IP par site.
Et si on utilise une plateforme comme Kubernetes, comment ça marche ?
Côté Kubernetes
Pour contrôler un cluster Kubernetes, on n’a pas besoin de communiquer directement avec les nœuds du cluster. Tout se passe via un composant appelé API server. Par exemple, quand on exécute kubectl create deployment web --image=nginx
, le programme kubectl
se connecte à notre cluster via l'API server pour créer un objet de type deployment appelé web. Quelques instants plus tard, un nœud du cluster reçoit l’instruction (via l'API server) de lancer un container avec l’image nginx
.
Bon, et comment se connecte-t-on à ce container NGINX ?
Kubernetes nous donne plusieurs mécanismes. Tout un éventail, en fait. (Certain·e·s diraient, “un peu trop”, et n’auraient pas tort.)
Pour des connexions purement internes, on utiliserait un service de type ClusterIP
. Kubernetes alloue une adresse IP interne (privée, donc) et on peut s’y connecter depuis l’intérieur du cluster pour communiquer avec notre NGINX.
Pour des connexions venant de l’extérieur, on peut utiliser un service de type NodePort
ou bien LoadBalancer
.
Avec un NodePort
, un port est alloué (par défaut, dans la plage 30000-32767) et on peut se connecter à n’importe quel nœud de notre cluster sur ce port pour atteindre notre NGINX. Cela vous rappelle sans doute le port mapping mentionné dans la partie précédente avec Docker : le principe est le même. Vous allez me dire, “d’accord, mais si je veux me connecter de l’extérieur, il faut que mes nœuds soient accessibles de l’extérieur, et donc qu’ils aient des adresses IP publiques!” C’est vrai, mais on n’a pas besoin de donner des adresses IP publiques à tous nos nœuds. Un petit sous-ensemble suffit. On se connecte alors à n’importe lequel de ces nœuds, qui se débrouillera ensuite pour renvoyer les connexions vers le container NGINX, où qu’il soit.
Avec un LoadBalancer
, Kubernetes va créer un répartiteur de charge. Plus précisément, c’est un composant appelé le cloud controller manager qui va dialoguer avec une API cloud pour le faire. Par exemple, si on déploie Kubernetes sur Amazon, le cloud controller manager va dialoguer avec l’API AWS pour créer un ELB (Elastic Load Balancer). Si on déploie sur OpenStack, cela passera par Neutron ou Octavia. Une fois le load balancer créé, on peut l’utiliser pour se connecter à notre NGINX (sans port mapping cette fois-ci). Le load balancer a besoin d’une adresse IP publique, mais le reste du cluster peut rester entièrement interne dans ce scénario.
Enfin, dans le cas particulier d’un serveur HTTP (comme notre NGINX), on peut utiliser un autre type d’objet appelé ingress. Un ingress n’est ni plus ni moins qu’un fragment de configuration pour un reverse proxy HTTP, afin d’associer un nom de domaine (comme mon-beau-nginx.tralala.io
) avec un service Kubernetes. Pour utiliser un ingress, il faut avoir un cluster Kubernetes disposant d’un ingress controller. La plupart des clusters Kubernetes dignes de ce nom en ont un pré-installé, mais on peut aussi en ajouter un si nécessaire (par exemple, on peut utiliser encore une fois le fameux Traefik mentionné plus haut). Notre ingress controller a son propre load balancer (avec, donc, une adresse IP publique) mais il pourra ensuite être utilisé pour autant d'ingress que l’on veut (tout comme notre bon vieux serveur PHP mutualisé peut avoir autant de noms de domaines que l’on veut, grâce à des directives VirtualHost
dans Apache).
Conclusions
Le RIPE vient d’allouer sa dernière plage d’adresses IP. IPv4 est mort, vive IPv4 ! Cela fait des dizaines d’années qu’on déploie à grande échelle et avec succès des techniques permettant de contourner le manque d’adresses IP. L’épuisement des adresses IPv4 ne va donc pas changer grand-chose pour nous dans un premier temps. En revanche, au fur et à mesure, on peut s’attendre à de plus en plus de pression de la part des hébergeurs, pour nous inciter à économiser les adresses IP publiques. C’est déjà le cas depuis longtemps sur AWS EC2, par exemple, où les Elastic IP coûtent de l’argent lorsqu’elles ne sont pas associées à une machine virtuelle en train de tourner.
En adoptant les containers (et Kubernetes en particulier), on se facilite la tâche de ce côté-là, car par défaut, un cluster Kubernetes n’a pas besoin d’une adresse IP publique par nœud. Par défaut, Kubernetes est économe en adresses IPv4. Ne nous méprenons pas : je ne suis pas en train de dire “on migre tout sur Kubernetes pour solutionner le problème d’adressage IP!”, mais au moins, la conception de Kubernetes est telle qu’on ne butera pas sur ce problème à l’avenir.
Si vous vous demandez “et l’IPv6 dans tout ça?” : Kubernetes a récemment ajouté le support dual stack dans Kubernetes 1.16 en alpha. Dans Kubernetes, alpha veut dire que la fonctionnalité est disponible mais qu’il faut l’activer à la main pour l’utiliser (via une feature gate). Et comme vous pouvez l’imaginer, si c’est alpha, ce n’est pas encore stable ; mais il est d’ores et déjà possible de tester.
Il est aussi possible de déployer un ingress controller dual stack qui sera capable d’accepter du traffic IPv6, puis le dispatcher sur un cluster Kubernetes en IPv4. Cela permet de supporter des connexions faites en IPv6, sans pour autant s’aventurer à faire tourner du code alpha.
Pour finir, si cela a aiguisé votre appétit d’en savoir plus sur Kubernetes, nous vous proposons des formations à la carte ! Nos prochaines sessions se dérouleront à Paris en février 2019. Si vous débutez, les premiers jours vous apprendront les fondamentaux sur Docker et comment optimiser vos Dockerfiles, ainsi que la priseen main de Kubernetes. Et pour les expert·e·s, les derniers jours couvrent le déploiement et l’administration de clusters Kubernetes, avec la sécurité, le scaling, y compris dans les environnements on premises. Venez déployer des clusters from scratch avec nous !
Ne ratez pas nos prochains articles DevOps et Cloud Native! Suivez Enix sur Twitter!