Services, équilibrage de charge et réseau

Concepts et ressources qui sous-tendent le réseau dans Kubernetes.

Le modèle réseau de Kubernetes

Le modèle réseau de Kubernetes repose sur plusieurs éléments :

  • Chaque pod d'un cluster reçoit sa propre adresse IP, unique dans tout le cluster.

    • Un pod a son propre namespace réseau privé, partagé par tous les conteneurs du pod. Les processus qui s'exécutent dans différents conteneurs d'un même pod peuvent communiquer entre eux via localhost.
  • Le réseau des pods (aussi appelé réseau du cluster) gère la communication entre les pods. Il garantit que (sauf segmentation réseau volontaire) :

    • Tous les pods peuvent communiquer avec tous les autres pods, qu'ils soient sur le même nœud ou sur des nœuds différents. Les pods peuvent communiquer entre eux directement, sans proxy ni traduction d'adresses (NAT).

      Sous Windows, cette règle ne s'applique pas aux pods qui utilisent le réseau de l'hôte.

    • Les agents d'un nœud (comme les démons système ou le kubelet) peuvent communiquer avec tous les pods de ce nœud.

  • L'API Service vous permet de fournir une adresse IP ou un nom d'hôte stable (durable) pour un service mis en œuvre par un ou plusieurs pods backend, alors que les pods qui composent ce service peuvent changer au fil du temps.

    • Kubernetes gère automatiquement des objets EndpointSlice qui donnent des informations sur les pods qui servent actuellement de backends à un Service.

    • Une implémentation de proxy de service surveille l'ensemble des objets Service et EndpointSlice, et programme le plan de données pour acheminer le trafic des services vers leurs backends, en utilisant les API du système d'exploitation ou du fournisseur cloud pour intercepter ou réécrire les paquets.

  • L'API Gateway (ou son prédécesseur, Ingress) vous permet de rendre des Services accessibles à des clients extérieurs au cluster.

    • Un mécanisme plus simple, mais moins configurable, pour faire entrer le trafic dans le cluster est proposé par le type: LoadBalancer de l'API Service, si vous utilisez un fournisseur de cloud compatible.
  • NetworkPolicy est une API intégrée à Kubernetes qui vous permet de contrôler le trafic entre les pods, ou entre les pods et le monde extérieur.

Dans les anciens systèmes de conteneurs, il n'existait pas de connectivité automatique entre les conteneurs de différents hôtes. Il fallait donc souvent créer explicitement des liens entre les conteneurs, ou faire correspondre les ports des conteneurs à des ports de l'hôte pour que les conteneurs d'autres hôtes puissent les joindre. Ce n'est pas nécessaire dans Kubernetes : dans son modèle, les pods peuvent être traités presque comme des VM ou des hôtes physiques du point de vue de l'allocation des ports, du nommage, de la découverte de services, de l'équilibrage de charge, de la configuration des applications et de la migration.

Seules quelques parties de ce modèle sont mises en œuvre par Kubernetes lui-même. Pour les autres, Kubernetes définit les API, mais la fonctionnalité correspondante est fournie par des composants externes, dont certains sont facultatifs :

  • La mise en place du namespace réseau des pods est assurée par un logiciel système qui implémente la Container Runtime Interface.

  • Le réseau des pods lui-même est géré par une implémentation du réseau des pods. Sous Linux, la plupart des environnements d'exécution de conteneurs utilisent la Container Networking Interface (CNI) pour dialoguer avec l'implémentation du réseau des pods. C'est pourquoi ces implémentations sont souvent appelées plugins CNI.

  • Kubernetes fournit une implémentation par défaut du proxy de service, appelée kube-proxy, mais certaines implémentations du réseau des pods utilisent plutôt leur propre proxy de service, plus étroitement intégré au reste de leur implémentation.

  • Les NetworkPolicies sont en général aussi mises en œuvre par l'implémentation du réseau des pods. (Certaines implémentations plus simples du réseau des pods ne prennent pas en charge les NetworkPolicies, ou un administrateur peut choisir de configurer le réseau des pods sans elles. Dans ces cas, l'API reste présente, mais elle n'a aucun effet.)

  • Il existe de nombreuses implémentations de Gateway API, certaines propres à des environnements cloud particuliers, d'autres plutôt orientées vers les environnements « bare metal », et d'autres plus génériques.

A suivre

Le tutoriel Connecter des applications avec des Services vous permet de découvrir les Services et le réseau de Kubernetes avec un exemple pratique.

Réseau du cluster explique comment mettre en place le réseau de votre cluster, et donne aussi une vue d'ensemble des technologies utilisées.

Pour découvrir des concepts réseau précis, consultez :

  • Service : exposer une application derrière un point d'accès externe unique
  • Ingress : routage HTTP/HTTPS sensible au protocole, à partir des URI, des noms d'hôte et des chemins
  • Gateway API : provisionnement dynamique de l'infrastructure et routage avancé du trafic
  • Politiques réseau : contrôler les flux de trafic au niveau des adresses IP ou des ports (couches 3 ou 4 du modèle OSI)
  • DNS pour les Services et les Pods : découvrir les services de votre cluster grâce au DNS

1 - Service

Une manière abstraite d'exposer une application s'exécutant sur un ensemble de Pods en tant que service réseau.

Avec Kubernetes, vous n'avez pas besoin de modifier votre application pour utiliser un mécanisme de découverte de services inconnu. Kubernetes donne aux pods leurs propres adresses IP et un nom DNS unique pour un ensemble de pods, et peut équilibrer la charge entre eux.

Motivation

Les Pods Kubernetes sont mortels. Ils naissent et lorsqu'ils meurent, ils ne ressuscitent pas. Si vous utilisez un Déploiement pour exécuter votre application, il peut créer et détruire dynamiquement des pods.

Chaque pod obtient sa propre adresse IP, mais dans un déploiement, l'ensemble de pods s'exécutant en un instant peut être différent de l'ensemble de pods exécutant cette application un instant plus tard.

Cela conduit à un problème: si un ensemble de pods (appelez-les «backends») fournit des fonctionnalités à d'autres pods (appelez-les «frontends») à l'intérieur de votre cluster, comment les frontends peuvent-ils trouver et suivre l'adresse IP à laquelle se connecter, afin que le frontend puisse utiliser la partie backend de la charge de travail?

C'est là où les Services rentrent en jeu.

La ressource Service

Dans Kubernetes, un service est une abstraction qui définit un ensemble logique de pods et une politique permettant d'y accéder (parfois ce modèle est appelé un micro-service). L'ensemble des pods ciblés par un service est généralement déterminé par un selector (voir ci-dessous pourquoi vous voudrez peut-être un service sans un sélecteur).

Par exemple, considérons un backend de traitement d'image sans état qui s'exécute avec 3 replicas. Ces réplicas sont fongibles et les frontends ne se soucient pas du backend qu'ils utilisent. Bien que les pods réels qui composent l'ensemble backend puissent changer, les clients frontends ne devraient pas avoir besoin de le savoir, pas plus qu'ils ne doivent suivre eux-mêmes l'ensemble des backends.

L'abstraction du service permet ce découplage.

Découverte de services native du cloud

Si vous pouvez utiliser les API Kubernetes pour la découverte de services dans votre application, vous pouvez interroger l'API server pour les Endpoints, qui sont mis à jour chaque fois que l'ensemble des pods d'un service change.

Pour les applications non natives, Kubernetes propose des moyens de placer un port réseau ou un load balancer entre votre application et les modules backend.

Définition d'un service

Un service dans Kubernetes est un objet REST, semblable à un pod. Comme tous les objets REST, vous pouvez effectuer un POST d'une définition de service sur le serveur API pour créer une nouvelle instance.

Par exemple, supposons que vous ayez un ensemble de pods qui écoutent chacun sur le port TCP 9376 et portent une étiquette app.kubernetes.io/name=MyApp:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app.kubernetes.io/name: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

Cette spécification crée un nouvel objet Service nommé «my-service», qui cible le port TCP 9376 sur n'importe quel pod avec l'étiquette «app.kubernetes.io/name=MyApp».

Kubernetes attribue à ce service une adresse IP (parfois appelé l'"IP cluster"), qui est utilisé par les proxies Service (voir IP virtuelles et proxy de service).

Le contrôleur de service recherche en continu les pods qui correspondent à son sélecteur, puis POST toutes les mises à jour d'un objet Endpoint également appelé "my-service".

Note:

Un service peut mapper n'importe quel port entrant vers un targetPort. Par défaut et pour plus de commodité, le targetPort a la même valeur que le champ port.

Les définitions de port dans les pods ont des noms, et vous pouvez référencer ces noms dans l'attribut targetPort d'un service. Cela fonctionne même s'il existe un mélange de pods dans le service utilisant un seul nom configuré, avec le même protocole réseau disponible via différents numéros de port. Cela offre beaucoup de flexibilité pour déployer et faire évoluer vos services. Par exemple, vous pouvez modifier les numéros de port que les pods exposent dans la prochaine version de votre logiciel principal, sans casser les clients.

Le protocole par défaut pour les services est TCP; vous pouvez également utiliser tout autre protocole pris en charge.

Comme de nombreux services doivent exposer plus d'un port, Kubernetes prend en charge plusieurs définitions de port sur un objet Service. Chaque définition de port peut avoir le même protocole, ou un autre.

Services sans sélecteurs

Les services abritent le plus souvent l'accès aux pods Kubernetes, mais ils peuvent également abstraire d'autres types de backends. Par exemple:

  • Vous voulez avoir un cluster de base de données externe en production, mais dans votre environnement de test, vous utilisez vos propres bases de données.
  • Vous souhaitez pointer votre service vers un service dans un autre Namespace ou sur un autre cluster.
  • Vous migrez une charge de travail vers Kubernetes. Lors de l'évaluation de l'approche, vous exécutez uniquement une partie de vos backends dans Kubernetes.

Dans n'importe lequel de ces scénarios, vous pouvez définir un service sans un sélecteur de pod. Par exemple:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

Étant donné que ce service n'a pas de sélecteur, l'objet Endpoint correspondant n'est pas créé automatiquement. Vous pouvez mapper manuellement le service à l'adresse réseau et au port où il s'exécute, en ajoutant manuellement un objet Endpoint:

apiVersion: v1
kind: Endpoints
metadata:
  name: my-service
subsets:
  - addresses:
      - ip: 192.0.2.42
    ports:
      - port: 9376

Note:

Les IP de noeud final ne doivent pas être: loopback (127.0.0.0/8 pour IPv4, ::1/128 pour IPv6), ou link-local (169.254.0.0/16 et 224.0.0.0/24 pour IPv4, fe80::/64 pour IPv6).

Les adresses IP de noeud final ne peuvent pas être les adresses IP de cluster d'autres services Kubernetes, car kube-proxy ne prend pas en charge les adresses IP virtuelles en tant que destination.

L'accès à un service sans sélecteur fonctionne de la même manière que s'il avait un sélecteur. Dans l'exemple ci-dessus, le trafic est routé vers le Endpoint unique défini dans le YAML: 192.0.2.42:9376 (TCP).

Un service ExternalName est un cas spécial de service qui n'a pas de sélecteurs et utilise des noms DNS à la place. Pour plus d'informations, consultez la section ExternalName plus loin dans ce document.

Endpoint Slices

Feature state: Beta since Kubernetes v1.17

Un Endpoint Slices est une ressource API qui peut fournir une alternative plus évolutive au Endpoints. Bien que conceptuellement assez similaire aux Endpoints, les Endpoint Slices permettent la distribution des endpoints réseau sur plusieurs ressources. Par défaut, un Endpoint Slice est considéré comme "plein" une fois qu'il atteint 100 endpoints, au delà, des Endpoint Slices addtionnels seront crées pour stocker tout autre endpoints.

Les Endpoint Slices fournissent des attributs et des fonctionnalités supplémentaires qui sont décrits en détail dans Endpoint Slices.

IP virtuelles et proxy de service

Chaque nœud d'un cluster Kubernetes exécute un kube-proxy. kube-proxy est responsable de l'implémentation d'une forme d'IP virtuelle pour les Services qui ne sont pas de type ExternalName.

Pourquoi ne pas utiliser le DNS round-robin ?

Une question qui apparaît de temps en temps est pourquoi Kubernetes s'appuie sur le proxy pour transférer le trafic entrant vers les backends. Et les autres approches? Par exemple, serait-il possible de configurer des enregistrements DNS qui ont plusieurs valeurs A (ou AAAA pour IPv6), et de s'appuyer sur la résolution de nom à tour de rôle (round-robin)?

Il existe plusieurs raisons d'utiliser le proxy pour les services:

  • Il existe une longue histoire d'implémentations DNS ne respectant pas les TTL d'enregistrement et mettant en cache les résultats des recherches de noms après leur expiration.
  • Certaines applications n'effectuent des recherches DNS qu'une seule fois et mettent en cache les résultats indéfiniment.
  • Même si les applications et les bibliothèques ont fait une bonne résolution, les TTL faibles ou nuls sur les enregistrements DNS pourraient imposer une charge élevée sur DNS qui devient alors difficile à gérer.

User space proxy mode

Dans ce mode, kube-proxy surveille le maître Kubernetes pour l'ajout et la suppression d'objets Service et Endpoint. Pour chaque service, il ouvre un port (choisi au hasard) sur le nœud local. Toutes les connexions à ce "port proxy" sont transmises par proxy à l'un des modules backend du service (comme indiqué via les Endpoints). kube-proxy prend en compte le paramètre SessionAffinity du service pour décider quel pod backend utiliser.

Enfin, le proxy de l'espace utilisateur installe des règles iptables qui capturent le trafic vers le service clusterIP (qui est virtuel) et port. Les règles redirigent ce trafic vers le port proxy qui fait office de proxy pour le Pod de backend.

Par défaut, kube-proxy en mode espace utilisateur choisit un backend via un algorithme round-robin.

Diagramme de vue d'ensemble des services pour le proxy de l'espace utilisateur

iptables proxy mode

Dans ce mode, kube-proxy surveille le plan de contrôle Kubernetes pour l'ajout et la suppression d'objets Service et Endpoint. Pour chaque service, il installe des règles iptables, qui capturent le trafic vers le «clusterIP» et le «port» du service, et redirigent ce trafic vers l'un des ensembles principaux du service. Pour chaque objet Endpoint, il installe des règles iptables qui sélectionnent un Pod de backend.

Par défaut, kube-proxy en mode iptables choisit un backend au hasard.

L'utilisation d'iptables pour gérer le trafic a un coût système inférieur, car le trafic est géré par Linux netfilter sans avoir besoin de basculer entre l'espace utilisateur et l'espace noyau. Cette approche est également susceptible d'être plus fiable.

Si kube-proxy s'exécute en mode iptables et que le premier pod sélectionné ne répond pas, la connexion échoue. C'est différent du mode espace utilisateur: dans ce scénario, kube-proxy détecterait que la connexion au premier pod avait échoué et réessayerait automatiquement avec un pod backend différent.

Vous pouvez utiliser les readiness probes d'un Pod pour vérifier que les pods backend fonctionnent correctement, de sorte que kube-proxy en mode iptables ne voit que les backends testés comme sains. Cela signifie que vous évitez d'envoyer du trafic via kube-proxy vers un pod connu pour avoir échoué.

Diagramme de présentation des services pour le proxy iptables

IPVS proxy mode

Feature state: Stable since Kubernetes v1.11

En mode ipvs, kube-proxy surveille les Services et Endpoints Kubernetes. kube-proxy appelle l'interface netlink pour créer les règles IPVS en conséquence et synchronise périodiquement les règles IPVS avec les Services et Endpoints Kubernetes. Cette boucle de contrôle garantit que l'état IPVS correspond à l'état souhaité. Lors de l'accès à un service, IPVS dirige le trafic vers l'un des pods backend.

Le mode proxy IPVS est basé sur des fonctions hooks de netfilter qui est similaire au mode iptables, mais utilise la table de hachage comme structure de données sous-jacente et fonctionne dans l'espace du noyau. Cela signifie que kube-proxy en mode IPVS redirige le trafic avec une latence plus faible que kube-proxy en mode iptables, avec de bien meilleures performances lors de la synchronisation des règles de proxy. Par rapport aux autres modes proxy, le mode IPVS prend également en charge un débit plus élevé de trafic réseau.

IPVS offre plus d'options pour équilibrer le trafic vers les pods d'arrière-plan; ceux-ci sont:

  • rr: round-robin
  • lc: least connection (plus petit nombre de connexions ouvertes)
  • dh: destination hashing
  • sh: source hashing
  • sed: shortest expected delay
  • nq: never queue

Note:

Pour exécuter kube-proxy en mode IPVS, vous devez rendre IPVS Linux disponible sur le nœud avant de démarrer kube-proxy.

Lorsque kube-proxy démarre en mode proxy IPVS, il vérifie si les modules du noyau IPVS sont disponibles. Si les modules du noyau IPVS ne sont pas détectés, alors kube-proxy revient à fonctionner en mode proxy iptables.

Diagramme de vue d'ensemble des services pour le proxy IPVS

Dans ces modèles de proxy, le trafic lié à l'IP: Port du service est dirigé vers un backend approprié sans que les clients ne sachent quoi que ce soit sur Kubernetes, les services ou les pods.

Si vous souhaitez vous assurer que les connexions d'un client particulier sont transmises à chaque fois au même pod, vous pouvez sélectionner l'affinité de session en fonction des adresses IP du client en définissant service.spec.sessionAffinity sur" ClientIP "(la valeur par défaut est" None"). Vous pouvez également définir la durée maximale de session persistante en définissant service.spec.sessionAffinityConfig.clientIP.timeoutSeconds de manière appropriée (la valeur par défaut est 10800, ce qui correspond à 3 heures).

Services multi-ports

Pour certains services, vous devez exposer plusieurs ports. Kubernetes vous permet de configurer plusieurs définitions de port sur un objet Service. Lorsque vous utilisez plusieurs ports pour un service, vous devez donner tous vos noms de ports afin qu'ils ne soient pas ambigus. Par exemple:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app.kubernetes.io/name: MyApp
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 9376
    - name: https
      protocol: TCP
      port: 443
      targetPort: 9377

Note:

Comme pour tous les names Kubernetes en général, les noms de ports ne doivent contenir que des caractères alphanumériques en minuscules et -. Les noms de port doivent également commencer et se terminer par un caractère alphanumérique.

Par exemple, les noms 123-abc et web sont valides, mais 123_abc et -web ne le sont pas.

Choisir sa propre adresse IP

Vous pouvez spécifier votre propre adresse IP de cluster dans le cadre d'une demande de création de Service. Pour ce faire, définissez le champ .spec.clusterIP. Par exemple, si vous avez déjà une entrée DNS existante que vous souhaitez réutiliser, ou des systèmes existants qui sont configurés pour une adresse IP spécifique et difficiles à reconfigurer.

L'adresse IP que vous choisissez doit être une adresse IPv4 ou IPv6 valide dans la plage CIDR service-cluster-ip-range configurée pour le serveur API. Si vous essayez de créer un service avec une valeur d'adresse de clusterIP non valide, le serveur API retournera un code d'état HTTP 422 pour indiquer qu'il y a un problème.

Découvrir les services

Kubernetes prend en charge 2 modes principaux de recherche d'un service: les variables d'environnement et DNS.

Variables d'environnement

Lorsqu'un pod est exécuté sur un nœud, le kubelet ajoute un ensemble de variables d'environnement pour chaque service actif. Il prend en charge à la fois les variables Docker links (voir makeLinkVariables) et plus simplement les variables {SVCNAME}_SERVICE_HOST et {SVCNAME}_SERVICE_PORT, où le nom du service est en majuscules et les tirets sont convertis en underscore.

Par exemple, le service redis-master qui expose le port TCP 6379 et a reçu l'adresse IP de cluster 10.0.0.11, produit les variables d'environnement suivantes:

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

Note:

Lorsque vous avez un pod qui doit accéder à un service et que vous utilisez la méthode des variables d'environnement pour publier le port et l'IP du cluster sur les pods clients, vous devez créer le service avant que les pods clients n'existent. Sinon, ces pods clients n'auront pas leurs variables d'environnement remplies.

Si vous utilisez uniquement DNS pour découvrir l'IP du cluster pour un service, vous n'avez pas à vous soucier de ce problème de commande.

DNS

Vous pouvez (et devriez presque toujours) configurer un service DNS pour votre cluster Kubernetes à l'aide d'un add-on.

Un serveur DNS prenant en charge les clusters, tel que CoreDNS, surveille l'API Kubernetes pour les nouveaux services et crée un ensemble d'enregistrements DNS pour chacun. Si le DNS a été activé dans votre cluster, tous les pods devraient automatiquement être en mesure de résoudre les services par leur nom DNS.

Par exemple, si vous avez un service appelé "my-service" dans un namespace Kubernetes "my-ns", le plan de contrôle et le service DNS agissant ensemble et créent un enregistrement DNS pour "my-service.my-ns". Les Pods dans le Namespace "my-ns" devrait être en mesure de le trouver en faisant simplement une recherche de nom pour my-service ("my-service.my-ns" fonctionnerait également).

Les pods dans d'autres namespaces doivent utiliser le nom de my-service.my-ns. Ces noms seront résolus en IP de cluster attribuée pour le service.

Kubernetes prend également en charge les enregistrements DNS SRV (Service) pour les ports nommés. Si le service "my-service.my-ns" a un port nommé http avec un protocole défini sur TCP, vous pouvez effectuer une requête DNS SRV pour _http._tcp.my-service.my-ns pour découvrir le numéro de port de http, ainsi que l'adresse IP.

Le serveur DNS Kubernetes est le seul moyen d'accéder aux services ExternalName. Vous pouvez trouver plus d'informations sur la résolution de ExternalName dans DNS Pods et Services.

Headless Services

Parfois, vous n'avez pas besoin de load-balancing et d'une seule IP de Service. Dans ce cas, vous pouvez créer ce que l'on appelle des services "headless", en spécifiant explicitement "None" pour l'IP du cluster (.spec.clusterIP).

Vous pouvez utiliser un service headless pour interfacer avec d'autres mécanismes de découverte de service, sans être lié à l'implémentation de Kubernetes.

Pour les services headless, une IP de cluster n'est pas allouée, kube-proxy ne gère pas ces services et aucun load-balancing ou proxy n'est effectué par la plateforme pour eux. La configuration automatique de DNS dépend de la définition ou non de sélecteurs par le service:

Avec sélecteurs

Pour les services headless qui définissent des sélecteurs, le controlleur des Endpoints crée des enregistrements Endpoints dans l'API, et modifie la configuration DNS pour renvoyer des enregistrements (adresses) qui pointent directement vers les Pods visés par le Service.

Sans sélecteurs

Pour les services headless qui ne définissent pas de sélecteurs, le contrôleur des Endpoints ne crée pas d'enregistrements Endpoints. Cependant, le système DNS recherche et configure soit:

  • Enregistrements CNAME pour les services de type ExternalName.
  • Un enregistrement pour tous les «Endpoints» qui partagent un nom avec le Service, pour tous les autres types.

Services de publication (ServiceTypes)

Pour certaines parties de votre application (par exemple, les frontaux), vous souhaiterez peut-être exposer un service sur une adresse IP externe, qui est en dehors de votre cluster.

Les «ServiceTypes» de Kubernetes vous permettent de spécifier le type de service que vous souhaitez. La valeur par défaut est «ClusterIP».

Les valeurs de Type et leurs comportements sont:

  • ClusterIP: Expose le service sur une IP interne au cluster. Le choix de cette valeur rend le service uniquement accessible à partir du cluster. Il s'agit du ServiceType par défaut.
  • NodePort: Expose le service sur l'IP de chaque nœud sur un port statique (le NodePort). Un service ClusterIP, vers lequel le service NodePort est automatiquement créé. Vous pourrez contacter le service NodePort, depuis l'extérieur du cluster, en demandant <NodeIP>: <NodePort>.
  • LoadBalancer: Expose le service en externe à l'aide de l'équilibreur de charge d'un fournisseur de cloud. Les services NodePort et ClusterIP, vers lesquels les itinéraires de l'équilibreur de charge externe, sont automatiquement créés.
  • ExternalName: Mappe le service au contenu du champ externalName (par exemple foo.bar.example.com), en renvoyant un enregistrement CNAME avec sa valeur. Aucun proxy d'aucune sorte n'est mis en place.

    Note:

     Vous avez besoin de CoreDNS version 1.7 ou supérieure pour utiliser le type `ExternalName`.
    

Vous pouvez également utiliser Ingress pour exposer votre service. Ingress n'est pas un type de service, mais il sert de point d'entrée pour votre cluster. Il vous permet de consolider vos règles de routage en une seule ressource car il peut exposer plusieurs services sous la même adresse IP.

Type NodePort

Si vous définissez le champ type sur NodePort, le plan de contrôle Kubernetes alloue un port à partir d'une plage spécifiée par l'indicateur --service-node-port-range (par défaut: 30000-32767). Chaque nœud assure le proxy de ce port (le même numéro de port sur chaque nœud) vers votre service. Votre service signale le port alloué dans son champ .spec.ports[*].nodePort.

Si vous souhaitez spécifier une ou des adresses IP particulières pour proxyfier le port, vous pouvez définir l'indicateur --nodeport-addresses dans kube-proxy sur des blocs IP particuliers; cela est pris en charge depuis Kubernetes v1.10. Cet indicateur prend une liste délimitée par des virgules de blocs IP (par exemple 10.0.0.0/8, 192.0.2.0/25) pour spécifier les plages d'adresses IP que kube-proxy doit considérer comme locales pour ce nœud.

Par exemple, si vous démarrez kube-proxy avec l'indicateur --nodeport-addresses=127.0.0.0/8, kube-proxy sélectionne uniquement l'interface de boucle locale pour les services NodePort. La valeur par défaut pour --nodeport-addresses est une liste vide. Cela signifie que kube-proxy doit prendre en compte toutes les interfaces réseau disponibles pour NodePort (qui est également compatible avec les versions antérieures de Kubernetes).

Si vous voulez un numéro de port spécifique, vous pouvez spécifier une valeur dans le champ nodePort. Le plan de contrôle vous attribuera ce port ou signalera l'échec de la transaction API. Cela signifie que vous devez vous occuper vous-même des éventuelles collisions de ports. Vous devez également utiliser un numéro de port valide, celui qui se trouve dans la plage configurée pour l'utilisation de NodePort.

L'utilisation d'un NodePort vous donne la liberté de configurer votre propre solution d'équilibrage de charge, de configurer des environnements qui ne sont pas entièrement pris en charge par Kubernetes, ou même d'exposer directement les adresses IP d'un ou plusieurs nœuds.

Notez que ce service est visible en tant que <NodeIP>: spec.ports[*].nodePort et .spec.clusterIP: spec.ports[*].Port. (Si l'indicateur --nodeport-addresses dans kube-proxy est défini, serait filtré NodeIP(s).)

Type LoadBalancer

Sur les fournisseurs de cloud qui prennent en charge les load balancers externes, la définition du champ type sur LoadBalancer provisionne un load balancer pour votre service. La création réelle du load balancer se produit de manière asynchrone et les informations sur le load balancer provisionné sont publiées dans le champ .status.loadBalancer. Par exemple:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app.kubernetes.io/name: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
  clusterIP: 10.0.171.239
  type: LoadBalancer
status:
  loadBalancer:
    ingress:
    - ip: 192.0.2.127

Le trafic provenant du load balancer externe est dirigé vers les Pods backend. Le fournisseur de cloud décide de la répartition de la charge.

Certains fournisseurs de cloud vous permettent de spécifier le loadBalancerIP. Dans ces cas, le load balancer est créé avec le loadBalancerIP spécifié par l'utilisateur. Si le champ loadBalancerIP n'est pas spécifié, le loadBalancer est configuré avec une adresse IP éphémère. Si vous spécifiez un loadBalancerIP mais que votre fournisseur de cloud ne prend pas en charge la fonctionnalité, le champ loadBalancerIP que vous définissez est ignoré.

Note:

Si vous utilisez SCTP, voir le caveat ci-dessous sur le type de service LoadBalancer.

Note:

Sur Azure, si vous souhaitez utiliser un type public spécifié par l'utilisateur loadBalancerIP, vous devez d'abord créer une ressource d'adresse IP publique de type statique. Cette ressource d'adresse IP publique doit se trouver dans le même groupe de ressources que les autres ressources créées automatiquement du cluster. Par exemple, MC_myResourceGroup_myAKSCluster_eastus.

Spécifiez l'adresse IP attribuée en tant que loadBalancerIP. Assurez-vous d'avoir mis à jour le securityGroupName dans le fichier de configuration du fournisseur de cloud. Pour plus d'informations sur le dépannage CreatingLoadBalancerFailed relatif aux permissions consultez: Use a static IP address with the Azure Kubernetes Service (AKS) load balancer ou CreatingLoadBalancerFailed on AKS cluster with advanced networking.

Load Balancer interne

Dans un environnement mixte, il est parfois nécessaire d'acheminer le trafic des services à l'intérieur du même bloc d'adresse réseau (virtuel).

Dans un environnement DNS à horizon divisé, vous auriez besoin de deux services pour pouvoir acheminer le trafic externe et interne vers vos endpoints.

Vous pouvez y parvenir en ajoutant une des annotations suivantes à un service. L'annotation à ajouter dépend du fournisseur de services cloud que vous utilisez.

Sélectionnez l'un des onglets.

[...]
metadata:
    name: my-service
    annotations:
        networking.gke.io/load-balancer-type: "Internal"
[...]
[...]
metadata:
    name: my-service
    annotations:
      service.beta.kubernetes.io/aws-load-balancer-scheme: "internal"
[...]
[...]
metadata:
    name: my-service
    annotations:
        service.beta.kubernetes.io/azure-load-balancer-internal: "true"
[...]
[...]
metadata:
    name: my-service
    annotations:
        service.beta.kubernetes.io/openstack-internal-load-balancer: "true"
[...]
[...]
metadata:
    name: my-service
    annotations:
        service.beta.kubernetes.io/cce-load-balancer-internal-vpc: "true"
[...]
[...]
metadata:
  annotations:
    service.kubernetes.io/qcloud-loadbalancer-internal-subnetid: subnet-xxxxx
[...]

Prise en charge TLS sur AWS

Pour une prise en charge partielle de TLS / SSL sur des clusters exécutés sur AWS, vous pouvez ajouter trois annotations à un service LoadBalancer:

metadata:
  name: my-service
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012

Le premier spécifie l'ARN du certificat à utiliser. Il peut s'agir soit d'un certificat d'un émetteur tiers qui a été téléchargé sur IAM, soit d'un certificat créé dans AWS Certificate Manager.

metadata:
  name: my-service
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: (https|http|ssl|tcp)

La deuxième annotation spécifie le protocole utilisé par un pod. Pour HTTPS et SSL, l'ELB s'attend à ce que le pod s'authentifie sur la connexion chiffrée, à l'aide d'un certificat.

HTTP et HTTPS sélectionnent le proxy de couche 7: l'ELB met fin à la connexion avec l'utilisateur, analyse les en-têtes et injecte l'en-tête X-Forwarded-For avec l'adresse IP de l'utilisateur (les pods ne voient que l'adresse IP de l'ELB à l'autre extrémité de sa connexion) lors du transfert des demandes.

TCP et SSL sélectionnent le proxy de couche 4: l'ELB transfère le trafic sans modifier les en-têtes.

Dans un environnement à usage mixte où certains ports sont sécurisés et d'autres non chiffrés, vous pouvez utiliser les annotations suivantes:

    metadata:
      name: my-service
      annotations:
        service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
        service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443,8443"

Dans l'exemple ci-dessus, si le service contenait trois ports, «80», «443» et «8443», alors «443» et «8443» utiliseraient le certificat SSL, mais «80» serait simplement un proxy HTTP.

A partir de Kubernetes v1.9, vous pouvez utiliser des stratégies SSL AWS prédéfinies avec des écouteurs HTTPS ou SSL pour vos services. Pour voir quelles politiques sont disponibles, vous pouvez utiliser l'outil de ligne de commande aws:

aws elb describe-load-balancer-policies --query 'PolicyDescriptions[].PolicyName'

Vous pouvez ensuite spécifier l'une de ces stratégies à l'aide de l'annotation "service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy"; par exemple:

    metadata:
      name: my-service
      annotations:
        service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy: "ELBSecurityPolicy-TLS-1-2-2017-01"

Prise en charge du protocole PROXY sur AWS

Pour activer protocole PROXY prise en charge des clusters exécutés sur AWS, vous pouvez utiliser l'annotation de service suivante:

    metadata:
      name: my-service
      annotations:
        service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"

Depuis la version 1.3.0, l'utilisation de cette annotation s'applique à tous les ports mandatés par l'ELB et ne peut pas être configurée autrement.

Journaux d'accès ELB sur AWS

Il existe plusieurs annotations pour gérer les journaux d'accès aux services ELB sur AWS.

L'annotation service.beta.kubernetes.io/aws-load-balancer-access-log-enabled contrôle si les journaux d'accès sont activés.

L'annotation service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval contrôle l'intervalle en minutes pour la publication des journaux d'accès. Vous pouvez spécifier un intervalle de 5 ou 60 minutes.

L'annotation service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name contrôle le nom du bucket Amazon S3 où les journaux d'accès au load balancer sont stockés.

L'annotation service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix spécifie la hiérarchie logique que vous avez créée pour votre bucket Amazon S3.

    metadata:
      name: my-service
      annotations:
        service.beta.kubernetes.io/aws-load-balancer-access-log-enabled: "true"
        # Spécifie si les journaux d'accès sont activés pour le load balancer

        service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval: "60"
        # L'intervalle de publication des journaux d'accès.
        # Vous pouvez spécifier un intervalle de 5 ou 60 (minutes).

        service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name: "my-bucket"
        # Le nom du bucket Amazon S3 où les journaux d'accès sont stockés

        service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix: "my-bucket-prefix/prod"
        # La hiérarchie logique que vous avez créée pour votre bucket Amazon S3, par exemple `my-bucket-prefix/prod`

Drainage de connexion sur AWS

Le drainage des connexions pour les ELB classiques peut être géré avec l'annotation service.beta.kubernetes.io / aws-load-balancer-connection-draining-enabled définie sur la valeur true. L'annotation service.beta.kubernetes.io / aws-load-balancer-connection-draining-timeout peut également être utilisée pour définir la durée maximale, en secondes, pour garder les connexions existantes ouvertes avant de désenregistrer les instances.

    metadata:
      name: my-service
      annotations:
        service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled: "true"
        service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout: "60"

Autres annotations ELB

Il existe d'autres annotations pour gérer les Elastic Load Balancers décrits ci-dessous.

    metadata:
      name: my-service
      annotations:
        service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "60"
        # Délai, en secondes, pendant lequel la connexion peut être inactive (aucune donnée n'a été envoyée via la connexion) avant d'être fermée par le load balancer

        service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
        # Spécifie si le load balancing inter-zones est activé pour le load balancer

        service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags: "environment=prod,owner=devops"
        # Une liste de paires clé-valeur séparées par des virgules qui seront enregistrées en tant que balises supplémentaires dans l'ELB.

        service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: ""
        # Nombre de contrôles de santé successifs réussis requis pour qu'un backend soit considéré comme sain pour le trafic.
        # La valeur par défaut est 2, doit être comprise entre 2 et 10

        service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: "3"
        # Nombre de contrôles de santé infructueux requis pour qu'un backend soit considéré comme inapte pour le trafic.
        # La valeur par défaut est 6, doit être comprise entre 2 et 10

        service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "20"
        # Intervalle approximatif, en secondes, entre les contrôles d'intégrité d'une instance individuelle.
        # La valeur par défaut est 10, doit être comprise entre 5 et 300

        service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "5"
        # Durée, en secondes, pendant laquelle aucune réponse ne signifie l'échec d'un contrôle de santé.
        # Cette valeur doit être inférieure à la valeur service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval.
        # La valeur par défaut est 5, doit être comprise entre 2 et 60

        service.beta.kubernetes.io/aws-load-balancer-extra-security-groups: "sg-53fae93f,sg-42efd82e"
        # Une liste de groupes de sécurité supplémentaires à ajouter à l'ELB

Prise en charge du load balancer réseau sur AWS

Feature state: Beta since Kubernetes v1.15

Pour utiliser un load balancer réseau sur AWS, utilisez l'annotation service.beta.kubernetes.io/aws-load-balancer-type avec la valeur définie sur nlb.

    metadata:
      name: my-service
      annotations:
        service.beta.kubernetes.io/aws-load-balancer-type: "nlb"

Note:

NLB ne fonctionne qu'avec certaines classes d'instance; voir la documentation AWS sur Elastic Load Balancing pour une liste des types d'instances pris en charge.

Contrairement aux équilibreurs de charge élastiques classiques, les équilibreurs de charge réseau (NLB) transfèrent l'adresse IP du client jusqu'au nœud. Si un service est .spec.externalTrafficPolicy est réglé sur Cluster, l'adresse IP du client n'est pas propagée aux pods finaux.

En définissant .spec.externalTrafficPolicy à Local, les adresses IP des clients sont propagées aux pods finaux, mais cela peut entraîner une répartition inégale du trafic. Les nœuds sans pods pour un service LoadBalancer particulier échoueront au contrôle de santé du groupe cible NLB sur le .spec.healthCheckNodePort attribué automatiquement et ne recevront aucun trafic.

Pour obtenir un trafic uniforme, utilisez un DaemonSet ou spécifiez un pod anti-affinity pour ne pas localiser sur le même noeud.

Vous pouvez également utiliser les services NLB avec l'annotation load balancer internal.

Pour que le trafic client atteigne des instances derrière un NLB, les groupes de sécurité du nœud sont modifiés avec les règles IP suivantes:

RuleProtocolPort(s)IpRange(s)IpRange Description
Health CheckTCPNodePort(s) (.spec.healthCheckNodePort for .spec.externalTrafficPolicy = Local)VPC CIDRkubernetes.io/rule/nlb/health=<loadBalancerName>
Client TrafficTCPNodePort(s).spec.loadBalancerSourceRanges (defaults to 0.0.0.0/0)kubernetes.io/rule/nlb/client=<loadBalancerName>
MTU DiscoveryICMP3,4.spec.loadBalancerSourceRanges (defaults to 0.0.0.0/0)kubernetes.io/rule/nlb/mtu=<loadBalancerName>

Afin de limiter les IP clientes pouvant accéder à l'équilibreur de charge réseau, spécifiez loadBalancerSourceRanges.

spec:
  loadBalancerSourceRanges:
    - "143.231.0.0/16"

Note:

Si .spec.loadBalancerSourceRanges n'est pas défini, Kubernetes autorise le trafic de 0.0.0.0/0 vers les groupes de sécurité des nœuds. Si les nœuds ont des adresses IP publiques, sachez que le trafic non NLB peut également atteindre toutes les instances de ces groupes de sécurité modifiés.

Autres annotations CLB sur Tencent Kubernetes Engine (TKE)

Il existe d'autres annotations pour la gestion des équilibreurs de charge cloud sur TKE, comme indiqué ci-dessous.

    metadata:
      name: my-service
      annotations:
        # Lier des load balancers avec des nœuds spécifiques
        service.kubernetes.io/qcloud-loadbalancer-backends-label: key in (value1, value2)

        # ID d'un load balancer existant
        service.kubernetes.io/tke-existed-lbid:lb-6swtxxxx

        # Paramètres personnalisés pour le load balancer (LB), ne prend pas encore en charge la modification du type LB
        service.kubernetes.io/service.extensiveParameters: ""

        # Paramètres personnalisés pour le listener LB
        service.kubernetes.io/service.listenerParameters: ""

        # Spécifie le type de Load balancer;
        # valeurs valides: classic (Classic Cloud Load Balancer) ou application (Application Cloud Load Balancer)
        service.kubernetes.io/loadbalance-type: xxxxx

        # Spécifie la méthode de facturation de la bande passante du réseau public;
        # valid values: TRAFFIC_POSTPAID_BY_HOUR(bill-by-traffic) and BANDWIDTH_POSTPAID_BY_HOUR (bill-by-bandwidth).
        service.kubernetes.io/qcloud-loadbalancer-internet-charge-type: xxxxxx

        # Spécifie la valeur de bande passante (plage de valeurs: [1,2000] Mbps).
        service.kubernetes.io/qcloud-loadbalancer-internet-max-bandwidth-out: "10"

        # Lorsque cette annotation est définie, les équilibreurs de charge n'enregistrent que les nœuds sur lesquels le pod s'exécute, sinon tous les nœuds seront enregistrés.
        service.kubernetes.io/local-svc-only-bind-node-with-pod: true

Type ExternalName

Les services de type ExternalName mappent un service à un nom DNS, et non à un sélecteur standard tel que my-service ou cassandra. Vous spécifiez ces services avec le paramètre spec.externalName.

Cette définition de service, par exemple, mappe le service my-service dans l'espace de noms prod à my.database.example.com:

apiVersion: v1
kind: Service
metadata:
  name: my-service
  namespace: prod
spec:
  type: ExternalName
  externalName: my.database.example.com

Note:

ExternalName accepte une chaîne d'adresse IPv4, mais en tant que noms DNS composés de chiffres, et non en tant qu'adresse IP. Les noms externes qui ressemblent aux adresses IPv4 ne sont pas résolus par CoreDNS ou ingress-nginx car ExternalName est destiné à spécifier un nom DNS canonique. Pour coder en dur une adresse IP, pensez à utiliser des Services headless.

Lors de la recherche de l'hôte my-service.prod.svc.cluster.local, le service DNS du cluster renvoie un enregistrement CNAME avec la valeur my.database.example.com. L'accès à «mon-service» fonctionne de la même manière que les autres services, mais avec la différence cruciale que la redirection se produit au niveau DNS plutôt que via un proxy ou un transfert. Si vous décidez ultérieurement de déplacer votre base de données dans votre cluster, vous pouvez démarrer ses pods, ajouter des sélecteurs ou des Endpoints appropriés et modifier le type du service.

Attention:

Vous pouvez rencontrer des difficultés à utiliser ExternalName pour certains protocoles courants, notamment HTTP et HTTPS. Si vous utilisez ExternalName, le nom d'hôte utilisé par les clients à l'intérieur de votre cluster est différent du nom référencé par ExternalName.

Pour les protocoles qui utilisent des noms d'hôtes, cette différence peut entraîner des erreurs ou des réponses inattendues. Les requêtes HTTP auront un en-tête Host: que le serveur d'origine ne reconnaît pas; Les serveurs TLS ne pourront pas fournir de certificat correspondant au nom d'hôte auquel le client s'est connecté.

Note:

Cette section est redevable à l'article Kubernetes Tips - Part 1 d'Alen Komljen.

IP externes

S'il existe des adresses IP externes qui acheminent vers un ou plusieurs nœuds de cluster, les services Kubernetes peuvent être exposés sur ces "IP externes". Le trafic qui pénètre dans le cluster avec l'IP externe (en tant qu'IP de destination), sur le port de service, sera routé vers l'un des Endpoints de service. Les externalIPs ne sont pas gérées par Kubernetes et relèvent de la responsabilité de l'administrateur du cluster.

Dans la spécification de service, «externalIPs» peut être spécifié avec n'importe lequel des «ServiceTypes». Dans l'exemple ci-dessous, "my-service" peut être consulté par les clients sur "198.51.100.32:80" (externalIP:port)

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app.kubernetes.io/name: MyApp
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 9376
  externalIPs:
    - 80.11.12.10

Lacunes

Le proxy fonctionnant dans l'espace utilisateur pour les VIP peut fonctionner à petite ou moyenne échelle, mais montrera ses limites dans de très grands clusters avec des milliers de services. La proposition de conception originale pour les portails a plus de détails à ce sujet.

L'utilisation du proxy de l'espace utilisateur masque l'adresse IP source d'un paquet accédant à un service. Cela rend certains types de filtrage réseau (pare-feu) impossibles. Le mode proxy iptables n'obscurcit pas les adresses IP source dans le cluster, mais il affecte toujours les clients passant par un LoadBalancer ou un NodePort.

Le champ Type est conçu comme une fonctionnalité imbriquée - chaque niveau s'ajoute au précédent. Cela n'est pas strictement requis sur tous les fournisseurs de cloud (par exemple, Google Compute Engine n'a pas besoin d'allouer un NodePort pour faire fonctionner LoadBalancer, mais AWS le fait) mais l'API actuelle le requiert.

Implémentation IP virtuelle

Les informations précédentes devraient être suffisantes pour de nombreuses personnes qui souhaitent simplement utiliser les Services. Cependant, il se passe beaucoup de choses dans les coulisses qui méritent d'être comprises.

Éviter les collisions

L'une des principales philosophies de Kubernetes est que vous ne devez pas être exposé à des situations qui pourraient entraîner l'échec de vos actions sans aucune faute de votre part. Pour la conception de la ressource Service, cela signifie de ne pas vous faire choisir votre propre numéro de port si ce choix pourrait entrer en collision avec le choix de quelqu'un d'autre. C'est un échec d'isolement.

Afin de vous permettre de choisir un numéro de port pour vos Services, nous devons nous assurer qu'aucun deux Services ne peuvent entrer en collision. Kubernetes le fait en attribuant à chaque service sa propre adresse IP.

Pour garantir que chaque service reçoit une adresse IP unique, un allocateur interne met à jour atomiquement une carte d'allocation globale dans etcd avant de créer chaque service. L'objet de mappage doit exister dans le registre pour que les services obtiennent des affectations d'adresse IP, sinon les créations échoueront avec un message indiquant qu'une adresse IP n'a pas pu être allouée.

Dans le plan de contrôle, un contrôleur d'arrière-plan est responsable de la création de cette carte (nécessaire pour prendre en charge la migration à partir d'anciennes versions de Kubernetes qui utilisaient le verrouillage en mémoire). Kubernetes utilise également des contrôleurs pour vérifier les affectations non valides (par exemple en raison d'une intervention de l'administrateur) et pour nettoyer les adresses IP allouées qui ne sont plus utilisées par aucun service.

Service IP addresses

Contrairement aux adresses IP des pods, qui acheminent réellement vers une destination fixe, les adresses IP des services ne sont pas réellement répondues par un seul hôte. Au lieu de cela, kube-proxy utilise iptables (logique de traitement des paquets sous Linux) pour définir les adresses IP virtual qui sont redirigées de manière transparente selon les besoins. Lorsque les clients se connectent au VIP, leur trafic est automatiquement transporté vers un Endpoint approprié. Les variables d'environnement et DNS pour les services sont en fait remplis en termes d'adresse IP virtuelle (et de port) du service.

kube-proxy prend en charge trois modes proxy — espace utilisateur, iptables et IPVS — qui fonctionnent chacun légèrement différemment.

Userspace

À titre d'exemple, considérons l'application de traitement d'image décrite ci-dessus. Lorsque le service backend est créé, le maître Kubernetes attribue une adresse IP virtuelle, par exemple 10.0.0.1. En supposant que le port de service est 1234, le service est observé par toutes les instances kube-proxy dans le cluster. Lorsqu'un proxy voit un nouveau service, il ouvre un nouveau port aléatoire, établit une redirection iptables de l'adresse IP virtuelle vers ce nouveau port et commence à accepter les connexions sur celui-ci.

Lorsqu'un client se connecte à l'adresse IP virtuelle du service, la règle iptables entre en jeu et redirige les paquets vers le propre port du proxy. Le “Service proxy” choisit un backend, et commence le proxy du trafic du client vers le backend.

Cela signifie que les propriétaires de services peuvent choisir le port de leur choix sans risque de collision. Les clients peuvent simplement se connecter à une adresse IP et à un port, sans savoir à quels pods ils accèdent réellement.

iptables

Considérons à nouveau l'application de traitement d'image décrite ci-dessus. Lorsque le service backend est créé, le plan de contrôle Kubernetes attribue une adresse IP virtuelle, par exemple 10.0.0.1. En supposant que le port de service est 1234, le service est observé par toutes les instances de kube-proxy dans le cluster. Lorsqu'un proxy voit un nouveau service, il installe une série de règles iptables qui redirigent de l'adresse IP virtuelle vers des règles par service. Les règles par service sont liées aux règles des Endpoints qui redirigent le trafic (à l'aide du NAT de destination) vers les backends.

Lorsqu'un client se connecte à l'adresse IP virtuelle du service, la règle iptables entre en jeu. Un backend est choisi (soit en fonction de l'affinité de la session, soit au hasard) et les paquets sont redirigés vers le backend. Contrairement au proxy de l'espace utilisateur, les paquets ne sont jamais copiés dans l'espace utilisateur, le proxy de kube n'a pas besoin d'être exécuté pour que l'adresse IP virtuelle fonctionne et les nœuds voient le trafic provenant de l'adresse IP du client non modifiée.

Ce même flux de base s'exécute lorsque le trafic arrive via un port de nœud ou via un load balancer, bien que dans ces cas, l'adresse IP du client soit modifiée.

IPVS

Les opérations iptables ralentissent considérablement dans un cluster à grande échelle, par exemple 10000 services. IPVS est conçu pour l'équilibrage de charge et basé sur des tables de hachage dans le noyau. Ainsi, vous pouvez obtenir une cohérence des performances dans un grand nombre de services à partir d'un kube-proxy basé sur IPVS. De plus, kube-proxy basé sur IPVS a des algorithmes d'équilibrage de charge plus sophistiqués (le moins de connexions, localité, pondéré, persistance).

Objet API

Le service est une ressource de niveau supérieur dans l'API REST Kubernetes. Vous pouvez trouver plus de détails sur l'objet API sur: Service API object.

Protocoles pris en charge

TCP

Feature state: Stable since Kubernetes v1.0

Vous pouvez utiliser TCP pour tout type de service, et c'est le protocole réseau par défaut.

UDP

Feature state: Stable since Kubernetes v1.0

Vous pouvez utiliser UDP pour la plupart des services. Pour Services de type LoadBalancer, la prise en charge UDP dépend du fournisseur de cloud offrant cette fonctionnalité.

HTTP

Feature state: Stable since Kubernetes v1.1

Si votre fournisseur de cloud le prend en charge, vous pouvez utiliser un service dans le mode LoadBalancer pour configurer le proxy inverse HTTP / HTTPS externe, transmis au Endpoints du Service.

Note:

Vous pouvez aussi utiliser Ingress à la place du service pour exposer les services HTTP/HTTPS.

Protocole PROXY

Feature state: Stable since Kubernetes v1.1

Si votre fournisseur de cloud le prend en charge(eg, AWS), vous pouvez utiliser un service en mode LoadBalancer pour configurer un load balancer en dehors de Kubernetes lui-même, qui transmettra les connexions préfixées par PROXY protocol.

Le load balancer enverra une première série d'octets décrivant la connexion entrante, similaire à cet exemple

PROXY TCP4 192.0.2.202 10.0.42.7 12345 7\r\n

suivi des données du client.

SCTP

Feature state: Alpha since Kubernetes v1.12

Kubernetes prend en charge SCTP en tant que valeur de «protocole» dans les définitions de Service, Endpoint, NetworkPolicy et Pod en tant que fonctionnalité alpha. Pour activer cette fonction, l'administrateur du cluster doit activer le flag SCTPSupport sur l'apiserver, par exemple, --feature-gates=SCTPSupport=true,….

When the feature gate is enabled, you can set the protocol field of a Service, Endpoint, NetworkPolicy or Pod to SCTP. Kubernetes sets up the network accordingly for the SCTP associations, just like it does for TCP connections.

Avertissements

Prise en charge des associations SCTP multi-hôtes

Attention:

La prise en charge des associations SCTP multi-hôtes nécessite que le plug-in CNI puisse prendre en charge l'attribution de plusieurs interfaces et adresses IP à un pod.

Le NAT pour les associations SCTP multi-hôtes nécessite une logique spéciale dans les modules de noyau correspondants.

Service avec type=LoadBalancer

Attention:

Vous ne pouvez créer un service de type LoadBalancer avec SCTP que si le fournisseur de load balancer supporte SCTP comme protocole. Sinon, la demande de création de service est rejetée. L'ensemble actuel de fournisseurs de load balancer cloud (Azure, AWS, CloudStack, GCE, OpenStack) ne prennent pas en charge SCTP.
Windows

Attention:

SCTP n'est pas pris en charge sur les nœuds Windows.
Userspace kube-proxy

Attention:

Le kube-proxy ne prend pas en charge la gestion des associations SCTP lorsqu'il est en mode userspace.

Futurs développements

À l'avenir, la stratégie de proxy pour les services peut devenir plus nuancée que le simple équilibrage alterné, par exemple master-elected ou sharded. Nous prévoyons également que certains services auront des load balancer «réels», auquel cas l'adresse IP virtuelle y transportera simplement les paquets.

Le projet Kubernetes vise à améliorer la prise en charge des services L7 (HTTP).

Le projet Kubernetes prévoit d'avoir des modes d'entrée plus flexibles pour les services, qui englobent les modes ClusterIP, NodePort et LoadBalancer actuels et plus encore.

A suivre

2 - Ingress

Rendez votre service réseau HTTP (ou HTTPS) accessible grâce à un mécanisme de configuration sensible au protocole et comprend les concepts du web comme les URI, les noms d'hôte, les chemins, etc. Le concept d'Ingress vous permet d'acheminer le trafic vers différents backends selon des règles que vous définissez via l'API Kubernetes.

Feature state: Stable since Kubernetes v1.19

Un objet API qui gère l'accès externe aux services d'un cluster, typiquement HTTP.

L'Ingress peut fournir un équilibrage de charge, une terminaison SSL ainsi qu'un hébergement virtuel basé sur un nom.

Note:

Le projet Kubernetes recommande d'utiliser Gateway plutôt qu'Ingress. L'API Ingress est figée.

Cela signifie que :

  • L'API Ingress est en disponibilité générale (GA) et soumise aux garanties de stabilité prévues pour les API GA. Le projet Kubernetes n'a pas l'intention de retirer Ingress de Kubernetes.
  • L'API Ingress n'est plus développée et ne recevra plus aucune modification ni mise à jour.

Terminologie

Par souci de clarté, ce guide définit les termes suivants :

  • Nœud (Node) : une machine de travail de Kubernetes, qui fait partie d'un cluster.
  • Cluster : un ensemble de nœuds qui exécutent des applications conteneurisées gérées par Kubernetes. Dans cet exemple, comme dans la plupart des déploiements Kubernetes courants, les nœuds du cluster ne sont pas exposés sur l'Internet public.
  • Routeur de bordure (edge router) : un routeur qui applique la politique de pare-feu de votre cluster. Il peut s'agir d'une passerelle gérée par un fournisseur de cloud ou d'un équipement physique.
  • Réseau du cluster : un ensemble de liens, logiques ou physiques, qui permettent la communication au sein d'un cluster selon le modèle réseau de Kubernetes.
  • Service : un Service Kubernetes qui identifie un ensemble de Pods à l'aide de sélecteurs de labels. Sauf mention contraire, on suppose que les Services ont des adresses IP virtuelles routables uniquement dans le réseau du cluster.

Qu'est-ce qu'un Ingress ?

Un Ingress expose des routes HTTP et HTTPS depuis l'extérieur du cluster vers des services du cluster. Le routage du trafic est contrôlé par des règles définies sur la ressource Ingress.

Voici un exemple simple dans lequel un Ingress envoie tout son trafic vers un seul Service :

ingress-diagram

Figure. Ingress

Un Ingress peut être configuré pour donner aux Services des URL accessibles de l'extérieur, équilibrer la charge du trafic, assurer la terminaison SSL / TLS et proposer un hébergement virtuel basé sur le nom. Un contrôleur d'Ingress est chargé de mettre en œuvre l'Ingress, généralement avec un équilibreur de charge (load balancer), mais il peut aussi configurer votre routeur de bordure ou des frontaux supplémentaires pour aider à gérer le trafic.

Un Ingress n'expose pas de ports ni de protocoles arbitraires. Pour exposer à Internet des services autres que HTTP et HTTPS, on utilise généralement un Service de type Service.Type=NodePort ou Service.Type=LoadBalancer.

Prérequis

Vous devez disposer d'un contrôleur d'Ingress pour qu'un Ingress soit pris en compte. La simple création d'une ressource Ingress n'a aucun effet.

Vous avez le choix entre de nombreux contrôleurs d'Ingress.

Idéalement, tous les contrôleurs d'Ingress devraient respecter la spécification de référence. En pratique, les différents contrôleurs d'Ingress fonctionnent de manière légèrement différente.

Note:

Consultez bien la documentation de votre contrôleur d'Ingress pour connaître les limites à prendre en compte avant de le choisir.

La ressource Ingress

Exemple de ressource Ingress minimale :

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: minimal-ingress
spec:
  ingressClassName: nginx-example
  rules:
  - http:
      paths:
      - path: /testpath
        pathType: Prefix
        backend:
          service:
            name: test
            port:
              number: 80

Un Ingress a besoin des champs apiVersion, kind, metadata et spec. Le nom d'un objet Ingress doit être un nom de sous-domaine DNS valide. Pour des informations générales sur l'utilisation des fichiers de configuration, consultez déployer des applications, configurer des conteneurs et gérer des ressources. Les contrôleurs d'Ingress utilisent souvent des annotations pour configurer leur comportement. Consultez la documentation du contrôleur d'Ingress que vous avez choisi pour savoir quelles annotations sont attendues ou prises en charge.

La spec de l'Ingress contient toutes les informations nécessaires pour configurer un équilibreur de charge ou un serveur proxy. Elle contient surtout une liste de règles comparées à toutes les requêtes entrantes. La ressource Ingress ne prend en charge que des règles pour diriger du trafic HTTP(S).

Si ingressClassName est omis, une classe d'Ingress par défaut devrait être définie.

Certains contrôleurs d'Ingress fonctionnent même sans définition d'une IngressClass par défaut. Même si vous utilisez un contrôleur d'Ingress capable de fonctionner sans IngressClass, le projet Kubernetes recommande tout de même de définir une IngressClass par défaut.

Règles d'Ingress

Chaque règle HTTP contient les informations suivantes :

  • Un hôte facultatif. Dans cet exemple, aucun hôte n'est indiqué : la règle s'applique donc à tout le trafic HTTP entrant par l'adresse IP indiquée. Si un hôte est fourni (par exemple foo.bar.com), les règles s'appliquent à cet hôte.
  • Une liste de chemins (par exemple /testpath), chacun associé à un backend défini par un service.name et un service.port.name ou un service.port.number. L'hôte et le chemin doivent tous deux correspondre au contenu d'une requête entrante pour que l'équilibreur de charge dirige le trafic vers le Service référencé.
  • Un backend est une combinaison d'un nom de Service et d'un port, comme décrit dans la documentation des Services, ou un backend de ressource personnalisée défini au moyen d'une CRD. Les requêtes HTTP (et HTTPS) adressées à l'Ingress qui correspondent à l'hôte et au chemin de la règle sont envoyées au backend indiqué.

Un defaultBackend est souvent configuré dans un contrôleur d'Ingress pour traiter toutes les requêtes qui ne correspondent à aucun chemin de la spec.

DefaultBackend

Un Ingress sans règles envoie tout le trafic vers un unique backend par défaut, et .spec.defaultBackend est le backend qui doit traiter les requêtes dans ce cas. Le defaultBackend est habituellement une option de configuration du contrôleur d'Ingress et n'est pas indiqué dans vos ressources Ingress. Si .spec.rules n'est pas défini, .spec.defaultBackend doit l'être. Si defaultBackend n'est pas défini, c'est le contrôleur d'Ingress qui décide du traitement des requêtes qui ne correspondent à aucune règle (consultez la documentation de votre contrôleur d'Ingress pour savoir comment il gère ce cas).

Si aucun des hôtes ou chemins des objets Ingress ne correspond à la requête HTTP, le trafic est acheminé vers votre backend par défaut.

Backends de ressource

Un backend Resource est une référence (ObjectRef) vers une autre ressource Kubernetes du même namespace que l'objet Ingress. Resource et Service s'excluent mutuellement : la validation échoue si les deux sont indiqués. Un backend Resource sert couramment à faire entrer des données dans un backend de stockage objet contenant des ressources statiques.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-resource-backend
spec:
  defaultBackend:
    resource:
      apiGroup: k8s.example.com
      kind: StorageBucket
      name: static-assets
  rules:
    - http:
        paths:
          - path: /icons
            pathType: ImplementationSpecific
            backend:
              resource:
                apiGroup: k8s.example.com
                kind: StorageBucket
                name: icon-assets

Après avoir créé l'Ingress ci-dessus, vous pouvez l'afficher avec la commande suivante :

kubectl describe ingress ingress-resource-backend
Name:             ingress-resource-backend
Namespace:        default
Address:
Default backend:  APIGroup: k8s.example.com, Kind: StorageBucket, Name: static-assets
Rules:
  Host        Path  Backends
  ----        ----  --------
  *
              /icons   APIGroup: k8s.example.com, Kind: StorageBucket, Name: icon-assets
Annotations:  <none>
Events:       <none>

Types de chemins

Chaque chemin d'un Ingress doit avoir un type de chemin correspondant. Les chemins sans pathType explicite échouent à la validation. Trois types de chemins sont pris en charge :

  • ImplementationSpecific : avec ce type de chemin, la correspondance dépend de l'IngressClass. Les implémentations peuvent le traiter comme un pathType distinct ou de la même manière que les types de chemins Prefix ou Exact.

  • Exact : correspond exactement au chemin de l'URL, en tenant compte de la casse.

  • Prefix : correspond selon un préfixe du chemin de l'URL découpé par /. La correspondance tient compte de la casse et se fait élément par élément. Un élément de chemin désigne la liste des libellés du chemin découpé par le séparateur /. Une requête correspond au chemin p si chaque p est un préfixe, élément par élément, de p dans le chemin de la requête.

    Note:

    Si le dernier élément du chemin est une sous-chaîne du dernier élément du chemin de la requête, il n'y a pas de correspondance (par exemple, /foo/bar correspond à /foo/bar/baz, mais pas à /foo/barbaz).

Exemples

TypeChemin(s)Chemin(s) de la requêteCorrespondance ?
Prefix/(tous les chemins)Oui
Exact/foo/fooOui
Exact/foo/barNon
Exact/foo/foo/Non
Exact/foo//fooNon
Prefix/foo/foo, /foo/Oui
Prefix/foo//foo, /foo/Oui
Prefix/aaa/bb/aaa/bbbNon
Prefix/aaa/bbb/aaa/bbbOui
Prefix/aaa/bbb//aaa/bbbOui, ignore la barre oblique finale
Prefix/aaa/bbb/aaa/bbb/Oui, correspond avec la barre oblique finale
Prefix/aaa/bbb/aaa/bbb/cccOui, correspond au sous-chemin
Prefix/aaa/bbb/aaa/bbbxyzNon, ne correspond pas au préfixe de chaîne
Prefix/, /aaa/aaa/cccOui, correspond au préfixe /aaa
Prefix/, /aaa, /aaa/bbb/aaa/bbbOui, correspond au préfixe /aaa/bbb
Prefix/, /aaa, /aaa/bbb/cccOui, correspond au préfixe /
Prefix/aaa/cccNon, utilise le backend par défaut
Mixte/foo (Prefix), /foo (Exact)/fooOui, préfère Exact

Correspondances multiples

Dans certains cas, plusieurs chemins d'un Ingress correspondent à une requête. La priorité est alors donnée au chemin correspondant le plus long. Si deux chemins correspondent toujours à égalité, la priorité est donnée aux chemins de type exact plutôt qu'aux chemins de type préfixe.

Jokers dans les noms d'hôte

Les hôtes peuvent être des correspondances précises (par exemple « foo.bar.com ») ou un joker (par exemple « *.foo.com »). Une correspondance précise exige que l'en-tête HTTP host corresponde au champ host. Une correspondance avec joker exige que l'en-tête HTTP host soit égal au suffixe de la règle avec joker.

HôteEn-tête HostCorrespondance ?
*.foo.combar.foo.comCorrespond grâce au suffixe commun
*.foo.combaz.bar.foo.comPas de correspondance, le joker ne couvre qu'un seul libellé DNS
*.foo.comfoo.comPas de correspondance, le joker ne couvre qu'un seul libellé DNS
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-wildcard-host
spec:
  rules:
  - host: "foo.bar.com"
    http:
      paths:
      - pathType: Prefix
        path: "/bar"
        backend:
          service:
            name: service1
            port:
              number: 80
  - host: "*.foo.com"
    http:
      paths:
      - pathType: Prefix
        path: "/foo"
        backend:
          service:
            name: service2
            port:
              number: 80

Classe d'Ingress

Les Ingress peuvent être mis en œuvre par différents contrôleurs, souvent avec des configurations différentes. Chaque Ingress devrait indiquer une classe, c'est-à-dire une référence à une ressource IngressClass qui contient une configuration supplémentaire, dont le nom du contrôleur chargé de mettre en œuvre la classe.

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: external-lb
spec:
  controller: example.com/ingress-controller
  parameters:
    apiGroup: k8s.example.com
    kind: IngressParameters
    name: external-lb

Le champ .spec.parameters d'une IngressClass vous permet de référencer une autre ressource qui fournit la configuration associée à cette IngressClass.

Le type précis de paramètres à utiliser dépend du contrôleur d'Ingress que vous indiquez dans le champ .spec.controller de l'IngressClass.

Portée d'une IngressClass

Selon votre contrôleur d'Ingress, vous pourrez peut-être utiliser des paramètres définis pour tout le cluster, ou pour un seul namespace.

Par défaut, les paramètres d'une IngressClass ont une portée à l'échelle du cluster.

Si vous définissez le champ .spec.parameters sans définir .spec.parameters.scope, ou si vous définissez .spec.parameters.scope sur Cluster, l'IngressClass fait référence à une ressource à portée cluster. Le kind (combiné à l'apiGroup) des paramètres fait référence à une API à portée cluster (éventuellement une ressource personnalisée), et le name des paramètres identifie une ressource précise à portée cluster de cette API.

Par exemple :

---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: external-lb-1
spec:
  controller: example.com/ingress-controller
  parameters:
    # The parameters for this IngressClass are specified in a
    # ClusterIngressParameter (API group k8s.example.net) named
    # "external-config-1". This definition tells Kubernetes to
    # look for a cluster-scoped parameter resource.
    scope: Cluster
    apiGroup: k8s.example.net
    kind: ClusterIngressParameter
    name: external-config-1
Feature state: Stable since Kubernetes v1.23

Si vous définissez le champ .spec.parameters et définissez .spec.parameters.scope sur Namespace, l'IngressClass fait référence à une ressource à portée namespace. Vous devez aussi définir le champ namespace de .spec.parameters avec le namespace qui contient les paramètres que vous voulez utiliser.

Le kind (combiné à l'apiGroup) des paramètres fait référence à une API à portée namespace (par exemple : ConfigMap), et le name des paramètres identifie une ressource précise dans le namespace indiqué dans namespace.

Les paramètres à portée namespace aident l'opérateur du cluster à déléguer le contrôle de la configuration (par exemple : réglages de l'équilibreur de charge, définition de la passerelle d'API) utilisée pour une charge de travail. Avec un paramètre à portée cluster, soit :

  • l'équipe qui exploite le cluster doit approuver les modifications d'une autre équipe chaque fois qu'un nouveau changement de configuration est appliqué ;
  • l'équipe qui exploite le cluster doit définir des contrôles d'accès spécifiques, comme des rôles et des liaisons RBAC, qui permettent à l'équipe applicative de modifier la ressource de paramètres à portée cluster.

L'API IngressClass elle-même a toujours une portée cluster.

Voici un exemple d'IngressClass qui fait référence à des paramètres à portée namespace :

---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: external-lb-2
spec:
  controller: example.com/ingress-controller
  parameters:
    # The parameters for this IngressClass are specified in an
    # IngressParameter (API group k8s.example.com) named "external-config",
    # that's in the "external-configuration" namespace.
    scope: Namespace
    apiGroup: k8s.example.com
    kind: IngressParameter
    namespace: external-configuration
    name: external-config

Annotation obsolète

Avant l'ajout de la ressource IngressClass et du champ ingressClassName dans Kubernetes 1.18, les classes d'Ingress étaient indiquées par une annotation kubernetes.io/ingress.class sur l'Ingress. Cette annotation n'a jamais été définie formellement, mais elle était largement prise en charge par les contrôleurs d'Ingress.

Le champ ingressClassName, plus récent, remplace cette annotation, sans en être un équivalent direct. Alors que l'annotation servait généralement à référencer le nom du contrôleur d'Ingress chargé de mettre en œuvre l'Ingress, le champ est une référence à une ressource IngressClass qui contient une configuration d'Ingress supplémentaire, dont le nom du contrôleur d'Ingress.

IngressClass par défaut

Vous pouvez marquer une IngressClass donnée comme classe par défaut de votre cluster. Définir l'annotation ingressclass.kubernetes.io/is-default-class sur true sur une ressource IngressClass garantit que cette IngressClass par défaut sera attribuée aux nouveaux Ingress qui n'indiquent pas de champ ingressClassName.

Avertissement:

Si plusieurs IngressClass sont marquées comme classe par défaut de votre cluster, le contrôleur d'admission empêche la création de nouveaux objets Ingress qui n'indiquent pas d'ingressClassName. Pour résoudre ce problème, assurez-vous qu'au plus une IngressClass est marquée comme classe par défaut dans votre cluster.

Commencez par définir une IngressClass par défaut. Il est toutefois recommandé d'indiquer l'IngressClass par défaut :

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  labels:
    app.kubernetes.io/component: controller
  name: example-class
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"
spec:
  controller: k8s.io/example-class

Types d'Ingress

Ingress reposant sur un seul Service

Il existe déjà des concepts Kubernetes qui permettent d'exposer un seul Service (voir les alternatives). Vous pouvez aussi le faire avec un Ingress en indiquant un backend par défaut sans règles.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test-ingress
spec:
  defaultBackend:
    service:
      name: test
      port:
        number: 80

Si vous le créez avec kubectl apply -f, vous devriez pouvoir afficher l'état de l'Ingress que vous avez ajouté :

kubectl get ingress test-ingress
NAME           CLASS         HOSTS   ADDRESS         PORTS   AGE
test-ingress   external-lb   *       203.0.113.123   80      59s

203.0.113.123 est l'adresse IP allouée par le contrôleur d'Ingress pour satisfaire cet Ingress.

Note:

Les contrôleurs d'Ingress et les équilibreurs de charge peuvent mettre une minute ou deux à allouer une adresse IP. En attendant, l'adresse s'affiche souvent sous la forme <pending>.

Fanout simple

Une configuration en fanout achemine le trafic d'une seule adresse IP vers plusieurs Services, en fonction de l'URI HTTP demandée. Un Ingress vous permet de réduire au minimum le nombre d'équilibreurs de charge. Prenons par exemple la configuration suivante :

ingress-fanout-diagram

Figure. Fanout d'Ingress

Elle nécessiterait un Ingress comme celui-ci :

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: simple-fanout-example
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - path: /foo
        pathType: Prefix
        backend:
          service:
            name: service1
            port:
              number: 4200
      - path: /bar
        pathType: Prefix
        backend:
          service:
            name: service2
            port:
              number: 8080

Une fois l'Ingress créé avec kubectl apply -f :

kubectl describe ingress simple-fanout-example
Name:             simple-fanout-example
Namespace:        default
Address:          178.91.123.132
Default backend:  default-http-backend:80 (10.8.2.3:8080)
Rules:
  Host         Path  Backends
  ----         ----  --------
  foo.bar.com
               /foo   service1:4200 (10.8.0.90:4200)
               /bar   service2:8080 (10.8.0.91:8080)
Events:
  Type     Reason  Age                From                     Message
  ----     ------  ----               ----                     -------
  Normal   ADD     22s                loadbalancer-controller  default/test

Le contrôleur d'Ingress provisionne un équilibreur de charge propre à son implémentation qui satisfait l'Ingress, à condition que les Services (service1, service2) existent. Une fois que c'est fait, vous pouvez voir l'adresse de l'équilibreur de charge dans le champ Address.

Note:

Selon le contrôleur d'Ingress que vous utilisez, vous devrez peut-être créer un Service default-http-backend.

Hébergement virtuel basé sur le nom

Les hôtes virtuels basés sur le nom permettent d'acheminer le trafic HTTP vers plusieurs noms d'hôte partageant la même adresse IP.

ingress-namebase-diagram

Figure. Hébergement virtuel basé sur le nom

L'Ingress suivant indique à l'équilibreur de charge sous-jacent d'acheminer les requêtes en fonction de l'en-tête Host.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: name-virtual-host-ingress
spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: service1
            port:
              number: 80
  - host: bar.foo.com
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: service2
            port:
              number: 80

Si vous créez une ressource Ingress sans définir d'hôte dans les règles, tout trafic web adressé à l'adresse IP de votre contrôleur d'Ingress peut correspondre sans qu'un hôte virtuel basé sur le nom soit nécessaire.

Par exemple, l'Ingress suivant achemine le trafic demandé pour first.bar.com vers service1, celui pour second.bar.com vers service2, et tout trafic dont l'en-tête Host de la requête ne correspond ni à first.bar.com ni à second.bar.com vers service3.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: name-virtual-host-ingress-no-third-host
spec:
  rules:
  - host: first.bar.com
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: service1
            port:
              number: 80
  - host: second.bar.com
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: service2
            port:
              number: 80
  - http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: service3
            port:
              number: 80

TLS

Vous pouvez sécuriser un Ingress en indiquant un Secret qui contient une clé privée et un certificat TLS. La ressource Ingress ne prend en charge qu'un seul port TLS, le 443, et suppose que la terminaison TLS se fait au point d'entrée (le trafic vers le Service et ses Pods circule en clair). Si la section de configuration TLS d'un Ingress indique plusieurs hôtes, ils sont multiplexés sur le même port selon le nom d'hôte indiqué via l'extension TLS SNI (à condition que le contrôleur d'Ingress prenne en charge SNI). Le Secret TLS doit contenir des clés nommées tls.crt et tls.key, qui contiennent le certificat et la clé privée à utiliser pour TLS. Par exemple :

apiVersion: v1
kind: Secret
metadata:
  name: testsecret-tls
  namespace: default
data:
  tls.crt: base64 encoded cert
  tls.key: base64 encoded key
type: kubernetes.io/tls

Référencer ce Secret dans un Ingress indique au contrôleur d'Ingress de sécuriser avec TLS le canal entre le client et l'équilibreur de charge. Vous devez vous assurer que le Secret TLS que vous avez créé provient d'un certificat contenant un Common Name (CN), aussi appelé nom de domaine complet (FQDN), pour https-example.foo.com.

Note:

Gardez à l'esprit que TLS ne fonctionnera pas sur la règle par défaut, car les certificats devraient être émis pour tous les sous-domaines possibles. C'est pourquoi les hosts de la section tls doivent correspondre explicitement au host de la section rules.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: tls-example-ingress
spec:
  tls:
  - hosts:
      - https-example.foo.com
    secretName: testsecret-tls
  rules:
  - host: https-example.foo.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: service1
            port:
              number: 80

Note:

Les fonctionnalités TLS prises en charge varient d'un contrôleur d'Ingress à l'autre. Consultez la documentation du ou des contrôleurs d'Ingress que vous avez choisis pour comprendre le fonctionnement de TLS dans votre environnement.

Équilibrage de charge

Un contrôleur d'Ingress démarre avec des réglages de politique d'équilibrage de charge qu'il applique à tous les Ingress, comme l'algorithme d'équilibrage de charge, le schéma de pondération des backends, etc. Les concepts d'équilibrage de charge plus avancés (par exemple les sessions persistantes ou les pondérations dynamiques) ne sont pas encore exposés via l'Ingress. Vous pouvez en revanche obtenir ces fonctionnalités grâce à l'équilibreur de charge utilisé pour un Service.

Notez aussi que, même si les contrôles de santé (health checks) ne sont pas exposés directement via l'Ingress, il existe dans Kubernetes des concepts parallèles, comme les sondes de disponibilité (readiness probes), qui permettent d'obtenir le même résultat. Consultez la documentation propre à votre contrôleur pour savoir comment il gère les contrôles de santé.

Mettre à jour un Ingress

Pour mettre à jour un Ingress existant afin d'ajouter un nouvel hôte, vous pouvez modifier la ressource :

kubectl describe ingress test
Name:             test
Namespace:        default
Address:          178.91.123.132
Default backend:  default-http-backend:80 (10.8.2.3:8080)
Rules:
  Host         Path  Backends
  ----         ----  --------
  foo.bar.com
               /foo   service1:80 (10.8.0.90:80)
Events:
  Type     Reason  Age                From                     Message
  ----     ------  ----               ----                     -------
  Normal   ADD     35s                loadbalancer-controller  default/test
kubectl edit ingress test

Un éditeur s'ouvre avec la configuration existante au format YAML. Modifiez-la pour ajouter le nouvel hôte :

spec:
  rules:
  - host: foo.bar.com
    http:
      paths:
      - backend:
          service:
            name: service1
            port:
              number: 80
        path: /foo
        pathType: Prefix
  - host: bar.baz.com
    http:
      paths:
      - backend:
          service:
            name: service2
            port:
              number: 80
        path: /foo
        pathType: Prefix
..

Une fois vos modifications enregistrées, kubectl met à jour la ressource dans le serveur d'API, ce qui indique au contrôleur d'Ingress de reconfigurer l'équilibreur de charge.

Vérifiez-le :

kubectl describe ingress test
Name:             test
Namespace:        default
Address:          178.91.123.132
Default backend:  default-http-backend:80 (10.8.2.3:8080)
Rules:
  Host         Path  Backends
  ----         ----  --------
  foo.bar.com
               /foo   service1:80 (10.8.0.90:80)
  bar.baz.com
               /foo   service2:80 (10.8.0.91:80)
Events:
  Type     Reason  Age                From                     Message
  ----     ------  ----               ----                     -------
  Normal   ADD     45s                loadbalancer-controller  default/test

Vous pouvez obtenir le même résultat en exécutant kubectl replace -f sur un fichier YAML d'Ingress modifié.

Basculement entre zones de disponibilité

Les techniques de répartition du trafic entre domaines de défaillance diffèrent d'un fournisseur de cloud à l'autre. Consultez la documentation du contrôleur d'Ingress concerné pour plus de détails.

Alternatives

Vous pouvez exposer un Service de plusieurs manières qui n'impliquent pas directement la ressource Ingress :

A suivre

3 - Contrôleurs d'Ingress

Pour qu'un Ingress fonctionne dans votre cluster, un contrôleur d'Ingress doit être en cours d'exécution. Vous devez choisir au moins un contrôleur d'Ingress et vous assurer qu'il est mis en place dans votre cluster. Cette page liste des contrôleurs d'Ingress courants que vous pouvez déployer.

Note:

Le projet Kubernetes recommande d'utiliser Gateway plutôt qu'Ingress. L'API Ingress est figée.

Cela signifie que :

  • L'API Ingress est en disponibilité générale (GA) et soumise aux garanties de stabilité prévues pour les API GA. Le projet Kubernetes n'a pas l'intention de retirer Ingress de Kubernetes.
  • L'API Ingress n'est plus développée et ne recevra plus aucune modification ni mise à jour.

Contrôleurs d'Ingress

Le projet Kubernetes prend en charge et maintient les contrôleurs d'Ingress AWS et GCE.

Contrôleurs d'Ingress tiers

Note: Cette section renvoie à des projets tiers qui fournissent des fonctionnalités requises par Kubernetes. Les auteurs du projet Kubernetes ne sont pas responsables de ces projets, classés par ordre alphabétique. Pour ajouter un projet à cette liste, lisez le guide avant de soumettre une modification. Plus d'informations.

Utiliser plusieurs contrôleurs d'Ingress

Vous pouvez déployer autant de contrôleurs d'Ingress que vous le souhaitez dans un cluster, grâce aux classes d'Ingress. Notez le .metadata.name de votre ressource de classe d'Ingress. Lorsque vous créez un Ingress, vous avez besoin de ce nom pour renseigner le champ ingressClassName de votre objet Ingress (voir la référence IngressSpec v1). ingressClassName remplace l'ancienne méthode par annotation.

Si vous ne précisez pas d'IngressClass pour un Ingress et que votre cluster a exactement une IngressClass marquée comme classe par défaut, Kubernetes applique cette IngressClass par défaut du cluster à l'Ingress. Pour marquer une IngressClass comme classe par défaut, définissez l'annotation ingressclass.kubernetes.io/is-default-class sur cette IngressClass, avec la chaîne "true".

Idéalement, tous les contrôleurs d'Ingress devraient respecter cette spécification, mais les différents contrôleurs d'Ingress fonctionnent de manière légèrement différente.

Note:

Consultez bien la documentation de votre contrôleur d'Ingress pour connaître les points d'attention liés à ce choix.

A suivre

4 - Gateway API

Gateway API est une famille de types d'API qui fournissent un provisionnement dynamique de l'infrastructure et un routage avancé du trafic.

Rendez des services réseau accessibles grâce à un mécanisme de configuration extensible, orienté rôles et conscient des protocoles. Gateway API est un add-on qui contient des types d'API fournissant un provisionnement dynamique de l'infrastructure et un routage avancé du trafic.

Principes de conception

Les principes suivants ont guidé la conception et l'architecture de Gateway API :

  • Orientée rôles : les types de Gateway API sont modélisés d'après les rôles organisationnels chargés de gérer le réseau des services Kubernetes :
    • Fournisseur d'infrastructure : gère une infrastructure qui permet à plusieurs clusters isolés de servir plusieurs locataires, par exemple un fournisseur de cloud.
    • Opérateur de cluster : gère des clusters et s'occupe généralement des politiques, de l'accès réseau, des permissions des applications, etc.
    • Développeur d'applications : gère une application qui s'exécute dans un cluster et s'occupe généralement de la configuration au niveau de l'application et de la composition des Services.
  • Portable : les spécifications de Gateway API sont définies sous forme de ressources personnalisées et sont prises en charge par de nombreuses implémentations.
  • Expressive : les types de Gateway API prennent en charge des fonctionnalités couvrant les cas d'usage courants de routage du trafic, comme la correspondance sur les en-têtes ou la pondération du trafic, entre autres, qui n'étaient possibles avec Ingress qu'au moyen d'annotations personnalisées.
  • Extensible : Gateway permet de lier des ressources personnalisées à différents niveaux de l'API. Cela rend possible une personnalisation fine aux endroits appropriés de la structure de l'API.

Modèle de ressources

Gateway API comporte quatre types d'API stables :

  • GatewayClass : définit un ensemble de gateways partageant une configuration commune et gérées par un contrôleur qui implémente la classe.

  • Gateway : définit une instance d'infrastructure de traitement du trafic, comme un équilibreur de charge (load balancer) cloud.

  • HTTPRoute : définit des règles propres à HTTP pour acheminer le trafic d'un listener de Gateway vers une représentation de points de terminaison réseau de backend. Ces points de terminaison sont souvent représentés par un Service.

  • GRPCRoute : définit des règles propres à gRPC pour acheminer le trafic d'un listener de Gateway vers une représentation de points de terminaison réseau de backend. Ces points de terminaison sont souvent représentés par un Service.

Gateway API est organisée en différents types d'API liés par des relations d'interdépendance, afin de refléter l'organisation par rôles des entreprises. Un objet Gateway est associé à exactement une GatewayClass ; la GatewayClass décrit le contrôleur de gateway chargé de gérer les Gateways de cette classe. Un ou plusieurs types de routes, comme HTTPRoute, sont ensuite associés aux Gateways. Une Gateway peut filtrer les routes autorisées à se rattacher à ses listeners, ce qui forme un modèle de confiance bidirectionnel avec les routes.

La figure suivante illustre les relations entre les trois types stables de Gateway API :

Figure illustrant les relations entre les trois types stables de Gateway API

GatewayClass

Les Gateways peuvent être implémentées par différents contrôleurs, souvent avec des configurations différentes. Une Gateway doit référencer une GatewayClass qui contient le nom du contrôleur qui implémente la classe.

Exemple minimal de GatewayClass :

apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: example-class
spec:
  controllerName: example.com/gateway-controller

Dans cet exemple, un contrôleur qui implémente Gateway API est configuré pour gérer les GatewayClasses dont le nom de contrôleur est example.com/gateway-controller. Les Gateways de cette classe seront gérées par le contrôleur de l'implémentation.

Consultez la référence de GatewayClass pour la définition complète de ce type d'API.

Gateway

Une Gateway décrit une instance d'infrastructure de traitement du trafic. Elle définit un point de terminaison réseau qui peut servir à traiter le trafic, c'est-à-dire à le filtrer, à en équilibrer la charge, à le fractionner, etc., vers des backends comme un Service. Par exemple, une Gateway peut représenter un équilibreur de charge cloud ou un serveur proxy interne au cluster, configuré pour accepter du trafic HTTP.

Exemple typique de ressource Gateway :

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: example-gateway
  namespace: example-namespace
spec:
  gatewayClassName: example-class
  listeners:
  - name: http
    protocol: HTTP
    port: 80
    hostname: "www.example.com"
    allowedRoutes:
      namespaces:
        from: Same

Dans cet exemple, une instance d'infrastructure de traitement du trafic est programmée pour écouter le trafic HTTP sur le port 80. Comme le champ addresses n'est pas renseigné, une adresse ou un nom d'hôte est attribué à la Gateway par le contrôleur de l'implémentation. Cette adresse sert de point de terminaison réseau pour traiter le trafic destiné aux points de terminaison réseau de backend définis dans les routes.

Consultez la référence de Gateway pour la définition complète de ce type d'API. Pour configurer des listeners HTTPS/TLS, consultez le guide TLS de Gateway API.

Note:

Par défaut, une Gateway n'accepte que les routes du même namespace. Les routes situées dans d'autres namespaces nécessitent de configurer allowedRoutes.

HTTPRoute

Le type HTTPRoute définit le comportement de routage des requêtes HTTP, d'un listener de Gateway vers des points de terminaison réseau de backend. Pour un backend de type Service, une implémentation peut représenter le point de terminaison réseau du backend par l'IP du Service ou par les EndpointSlices associées au Service. Une HTTPRoute représente une configuration appliquée à l'implémentation de Gateway sous-jacente. Par exemple, définir une nouvelle HTTPRoute peut conduire à configurer des routes de trafic supplémentaires dans un équilibreur de charge cloud ou dans un serveur proxy du cluster.

Exemple typique d'HTTPRoute :

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: example-httproute
spec:
  parentRefs:
  - name: example-gateway
  hostnames:
  - "www.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /login
    backendRefs:
    - name: example-svc
      port: 8080

Dans cet exemple, le trafic HTTP provenant de la Gateway example-gateway, dont l'en-tête Host: vaut www.example.com et dont le chemin de la requête est /login, sera acheminé vers le Service example-svc sur le port 8080.

Consultez la référence de HTTPRoute pour la définition complète de ce type d'API.

GRPCRoute

Le type GRPCRoute définit le comportement de routage des requêtes gRPC, d'un listener de Gateway vers des points de terminaison réseau de backend. Pour un backend de type Service, une implémentation peut représenter le point de terminaison réseau du backend par l'IP du Service ou par les EndpointSlices associées au Service. Une GRPCRoute représente une configuration appliquée à l'implémentation de Gateway sous-jacente. Par exemple, définir une nouvelle GRPCRoute peut conduire à configurer des routes de trafic supplémentaires dans un équilibreur de charge cloud ou dans un serveur proxy du cluster.

Les Gateways qui prennent en charge GRPCRoute doivent prendre en charge HTTP/2 sans mise à niveau initiale depuis HTTP/1, afin de garantir que le trafic gRPC circule correctement.

Exemple typique de GRPCRoute :

apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
  name: example-grpcroute
spec:
  parentRefs:
  - name: example-gateway
  hostnames:
  - "svc.example.com"
  rules:
  - backendRefs:
    - name: example-svc
      port: 50051

Dans cet exemple, le trafic gRPC provenant de la Gateway example-gateway, dont l'hôte est svc.example.com, sera dirigé vers le Service example-svc sur le port 50051, dans le même namespace.

GRPCRoute permet de cibler des services gRPC précis, comme dans l'exemple suivant :

apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
  name: example-grpcroute
spec:
  parentRefs:
  - name: example-gateway
  hostnames:
  - "svc.example.com"
  rules:
  - matches:
    - method:
        service: com.example
        method: Login
    backendRefs:
    - name: foo-svc
      port: 50051

Dans ce cas, la GRPCRoute correspond à tout le trafic destiné à svc.example.com et applique ses règles de routage pour transmettre le trafic au bon backend. Comme une seule correspondance est définie, seules les requêtes vers la méthode com.example.User.Login sur svc.example.com seront transmises. Les RPC vers toute autre méthode ne correspondront pas à cette route.

Consultez la référence de GRPCRoute pour la définition complète de ce type d'API.

Flux des requêtes

Voici un exemple simple de trafic HTTP acheminé vers un Service à l'aide d'une Gateway et d'une HTTPRoute :

Schéma montrant un exemple de trafic HTTP acheminé vers un Service à l'aide d'une Gateway et d'une HTTPRoute

Dans cet exemple, le flux d'une requête pour une Gateway implémentée sous forme de proxy inverse est le suivant :

  1. Le client commence à préparer une requête HTTP pour l'URL http://www.example.com.
  2. Le résolveur DNS du client résout le nom de destination en une ou plusieurs adresses IP associées à la Gateway.
  3. Le client envoie une requête à l'adresse IP de la Gateway ; le proxy inverse reçoit la requête HTTP et utilise l'en-tête Host: pour trouver une configuration issue de la Gateway et de l'HTTPRoute rattachée.
  4. Le proxy inverse peut éventuellement vérifier la correspondance des en-têtes et/ou du chemin de la requête, selon les règles de correspondance de l'HTTPRoute.
  5. Le proxy inverse peut éventuellement modifier la requête, par exemple pour ajouter ou supprimer des en-têtes, selon les règles de filtrage de l'HTTPRoute.
  6. Enfin, le proxy inverse transmet la requête à un ou plusieurs backends.

Conformité

Gateway API couvre un large ensemble de fonctionnalités et elle est largement implémentée. Cette combinaison exige des définitions et des tests de conformité clairs pour garantir que l'API offre une expérience cohérente partout où elle est utilisée.

Consultez la documentation sur la conformité pour comprendre des notions comme les canaux de publication, les niveaux de prise en charge et l'exécution des tests de conformité.

Migrer depuis Ingress

Gateway API succède à l'API Ingress. Elle n'inclut cependant pas le type Ingress. Une conversion ponctuelle de vos ressources Ingress existantes en ressources Gateway API est donc nécessaire.

Consultez le guide de migration depuis Ingress pour savoir comment migrer des ressources Ingress vers des ressources Gateway API.

A suivre

Les ressources de Gateway API ne sont pas implémentées nativement par Kubernetes : leurs spécifications sont définies sous forme de ressources personnalisées prises en charge par un large éventail d'implémentations. Installez les CRD de Gateway API ou suivez les instructions d'installation de l'implémentation choisie. Une fois l'implémentation installée, utilisez le guide Getting Started pour prendre en main rapidement Gateway API.

Note:

Consultez la documentation de l'implémentation choisie pour bien en comprendre les éventuelles limites.

Consultez la spécification de l'API pour plus de détails sur tous les types de Gateway API.

5 - EndpointSlices

L'API EndpointSlice est le mécanisme que Kubernetes utilise pour permettre à votre Service de passer à l'échelle et de gérer un grand nombre de backends, et elle permet au cluster de mettre à jour efficacement sa liste de backends sains.
Feature state: Stable since Kubernetes v1.21
Les EndpointSlices suivent les adresses IP des points de terminaison backend.
Les EndpointSlices sont généralement associés à un
Service et les points de terminaison backend représentent typiquement des
Pods.

API EndpointSlice

Dans Kubernetes, un EndpointSlice contient des références à un ensemble de points de terminaison réseau. Le plan de contrôle crée automatiquement des EndpointSlices pour tout Service Kubernetes pour lequel un sélecteur est spécifié. Ces EndpointSlices contiennent des références à tous les Pods qui correspondent au sélecteur du Service. Les EndpointSlices regroupent les points de terminaison réseau par combinaison unique de famille d'adresses IP, de protocole, de numéro de port et de nom de Service. Le nom d'un objet EndpointSlice doit être un nom de sous-domaine DNS valide.

Voici par exemple un objet EndpointSlice dont le propriétaire est le Service Kubernetes example.

apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
  name: example-abc
  labels:
    kubernetes.io/service-name: example
addressType: IPv4
ports:
  - name: http
    protocol: TCP
    port: 80
endpoints:
  - addresses:
      - "10.1.2.3"
    conditions:
      ready: true
    hostname: pod-1
    nodeName: node-1
    zone: us-west2-a

Par défaut, le plan de contrôle crée et gère des EndpointSlices qui ne contiennent pas plus de 100 points de terminaison chacun. Vous pouvez modifier cette valeur avec l'option --max-endpoints-per-slice de kube-controller-manager, jusqu'à un maximum de 1000.

Les EndpointSlices constituent la source de vérité de kube-proxy pour déterminer comment acheminer le trafic interne.

Types d'adresses

Les EndpointSlices prennent en charge deux types d'adresses :

  • IPv4
  • IPv6

Chaque objet EndpointSlice correspond à un type d'adresse IP précis. Si vous avez un Service accessible en IPv4 et en IPv6, il existera au moins deux objets EndpointSlice (un pour IPv4 et un pour IPv6).

Conditions

L'API EndpointSlice enregistre, pour les points de terminaison, des conditions qui peuvent être utiles à ses consommateurs. Les trois conditions sont serving, terminating et ready.

Serving

Feature state: Stable since Kubernetes v1.26

La condition serving indique que le point de terminaison répond actuellement aux requêtes, et qu'il devrait donc être utilisé comme cible pour le trafic du Service. Pour les points de terminaison adossés à un Pod, elle correspond à la condition Ready du Pod.

Terminating

Feature state: Stable since Kubernetes v1.26

La condition terminating indique que le point de terminaison est en cours d'arrêt. Pour les points de terminaison adossés à un Pod, cette condition est définie dès que la suppression du Pod est demandée (c'est-à-dire lorsqu'il reçoit un horodatage de suppression, mais très probablement avant que les conteneurs du Pod ne s'arrêtent).

Les proxys de Service ignorent normalement les points de terminaison marqués terminating, mais ils peuvent acheminer le trafic vers des points de terminaison à la fois serving et terminating si tous les points de terminaison disponibles sont marqués terminating. (Cela contribue à garantir qu'aucun trafic du Service n'est perdu pendant les mises à jour progressives des Pods sous-jacents.)

Ready

La condition ready est essentiellement un raccourci pour vérifier « serving et non terminating » (elle vaut toutefois toujours true pour les Services dont spec.publishNotReadyAddresses est défini sur true).

Informations de topologie

Chaque point de terminaison d'un EndpointSlice peut contenir des informations de topologie pertinentes. Ces informations comprennent l'emplacement du point de terminaison ainsi que des informations sur le nœud et la zone correspondants. Elles sont disponibles dans les champs suivants, propres à chaque point de terminaison d'un EndpointSlice :

  • nodeName : le nom du nœud sur lequel se trouve ce point de terminaison.
  • zone : la zone dans laquelle se trouve ce point de terminaison.

Gestion

Le plus souvent, c'est le plan de contrôle (plus précisément, le contrôleur d'EndpointSlices) qui crée et gère les objets EndpointSlice. Les EndpointSlices ont de nombreux autres cas d'usage, comme les implémentations de service mesh, qui peuvent amener d'autres entités ou contrôleurs à gérer des ensembles supplémentaires d'EndpointSlices.

Pour que plusieurs entités puissent gérer des EndpointSlices sans se gêner mutuellement, Kubernetes définit le label endpointslice.kubernetes.io/managed-by, qui indique l'entité qui gère un EndpointSlice. Le contrôleur d'EndpointSlices attribue la valeur endpointslice-controller.k8s.io à ce label sur tous les EndpointSlices qu'il gère. Les autres entités qui gèrent des EndpointSlices doivent elles aussi attribuer une valeur unique à ce label.

Propriété

Dans la plupart des cas d'usage, le propriétaire d'un EndpointSlice est le Service dont l'objet EndpointSlice suit les points de terminaison. Cette propriété est indiquée par une référence de propriétaire (owner reference) sur chaque EndpointSlice, ainsi que par un label kubernetes.io/service-name qui permet de retrouver simplement tous les EndpointSlices d'un Service.

Répartition des EndpointSlices

Chaque EndpointSlice possède un ensemble de ports qui s'applique à tous les points de terminaison de la ressource. Lorsqu'un Service utilise des ports nommés, les Pods peuvent se retrouver avec des numéros de port cible différents pour un même port nommé, ce qui nécessite des EndpointSlices distincts.

Le plan de contrôle essaie de remplir les EndpointSlices autant que possible, mais ne les rééquilibre pas activement. La logique est assez simple :

  1. Parcourir les EndpointSlices existants, retirer les points de terminaison qui ne sont plus souhaités et mettre à jour les points de terminaison correspondants qui ont changé.
  2. Parcourir les EndpointSlices modifiés lors de la première étape et les compléter avec les nouveaux points de terminaison nécessaires.
  3. S'il reste encore de nouveaux points de terminaison à ajouter, essayer de les placer dans un EndpointSlice resté inchangé et/ou en créer de nouveaux.

Point important : la troisième étape privilégie la limitation des mises à jour d'EndpointSlices plutôt qu'un remplissage optimal des EndpointSlices. Par exemple, s'il y a 10 nouveaux points de terminaison à ajouter et 2 EndpointSlices pouvant chacun en accueillir 5 de plus, cette approche créera un nouvel EndpointSlice au lieu de remplir les 2 EndpointSlices existants. Autrement dit, une seule création d'EndpointSlice est préférable à plusieurs mises à jour d'EndpointSlices.

Comme kube-proxy s'exécute sur chaque nœud et surveille les EndpointSlices, chaque modification d'un EndpointSlice devient relativement coûteuse, puisqu'elle est transmise à chaque nœud du cluster. Cette approche vise à limiter le nombre de modifications à envoyer à chaque nœud, même si elle peut aboutir à plusieurs EndpointSlices partiellement remplis.

En pratique, cette répartition moins qu'idéale devrait être rare. La plupart des modifications traitées par le contrôleur d'EndpointSlices sont suffisamment petites pour tenir dans un EndpointSlice existant ; dans le cas contraire, un nouvel EndpointSlice sera de toute façon probablement nécessaire bientôt. Les mises à jour progressives des Deployments entraînent aussi un réagencement naturel des EndpointSlices, puisque tous les Pods et leurs points de terminaison correspondants sont remplacés.

Points de terminaison en double

En raison de la nature des modifications d'EndpointSlices, un point de terminaison peut figurer dans plusieurs EndpointSlices en même temps. Cela se produit naturellement, car les modifications de différents objets EndpointSlice peuvent parvenir au watch / cache du client Kubernetes à des moments différents.

Note:

Les clients de l'API EndpointSlice doivent parcourir tous les EndpointSlices existants associés à un Service et construire une liste complète de points de terminaison réseau uniques. Il est important de noter que des points de terminaison peuvent être dupliqués dans différents EndpointSlices.

Vous trouverez une implémentation de référence de cette agrégation et de cette déduplication des points de terminaison dans le code EndpointSliceCache de kube-proxy.

Mise en miroir des EndpointSlices

Feature state: Deprecated since Kubernetes v1.33

L'API EndpointSlice remplace l'ancienne API Endpoints. Pour préserver la compatibilité avec les anciens contrôleurs et les charges de travail des utilisateurs qui s'attendent à ce que kube-proxy achemine le trafic à partir des ressources Endpoints, le plan de contrôle du cluster reproduit (en miroir) la plupart des ressources Endpoints créées par les utilisateurs dans des EndpointSlices correspondants.

(Cette fonctionnalité est toutefois dépréciée, comme le reste de l'API Endpoints. Les utilisateurs qui définissent manuellement des points de terminaison pour des Services sans sélecteur doivent le faire en créant directement des ressources EndpointSlice, plutôt qu'en créant des ressources Endpoints et en laissant le plan de contrôle les reproduire en miroir.)

Le plan de contrôle reproduit les ressources Endpoints en miroir, sauf si :

  • la ressource Endpoints a un label endpointslice.kubernetes.io/skip-mirror défini sur true ;
  • la ressource Endpoints a une annotation control-plane.alpha.kubernetes.io/leader ;
  • la ressource Service correspondante n'existe pas ;
  • la ressource Service correspondante a un sélecteur non nul.

Une même ressource Endpoints peut donner lieu à plusieurs EndpointSlices. C'est le cas si une ressource Endpoints comporte plusieurs sous-ensembles (subsets) ou contient des points de terminaison de plusieurs familles d'adresses IP (IPv4 et IPv6). Au maximum 1000 adresses par sous-ensemble sont reproduites dans les EndpointSlices.

A suivre

6 - Politiques réseau

Si vous voulez contrôler les flux de trafic au niveau des adresses IP ou des ports (couches 3 ou 4 du modèle OSI), les NetworkPolicies vous permettent de définir des règles pour les flux de trafic au sein de votre cluster, ainsi qu'entre les Pods et le monde extérieur. Votre cluster doit utiliser un plugin réseau qui sait faire respecter les NetworkPolicies.

Si vous voulez contrôler les flux de trafic au niveau des adresses IP ou des ports pour les protocoles TCP, UDP et SCTP, vous pouvez envisager d'utiliser les NetworkPolicies de Kubernetes pour certaines applications de votre cluster. Les NetworkPolicies sont un mécanisme centré sur l'application, qui vous permet de définir comment un pod est autorisé à communiquer sur le réseau avec différentes « entités » (nous employons ici le mot « entité » pour ne pas donner un sens de plus à des termes courants comme « endpoints » et « services », qui ont un sens précis dans Kubernetes). Les NetworkPolicies s'appliquent à une connexion dont l'une des extrémités au moins est un pod, et ne concernent pas les autres connexions.

Les entités avec lesquelles un Pod peut communiquer sont identifiées par une combinaison des trois identifiants suivants :

  1. Les autres pods autorisés (exception : un pod ne peut pas bloquer l'accès à lui-même)
  2. Les namespaces autorisés
  3. Les blocs d'adresses IP (exception : le trafic depuis et vers le nœud sur lequel un Pod s'exécute est toujours autorisé, quelle que soit l'adresse IP du Pod ou du nœud)

Pour définir une NetworkPolicy basée sur les pods ou les namespaces, vous utilisez un sélecteur pour indiquer quel trafic est autorisé depuis et vers le ou les Pods qui correspondent à ce sélecteur.

Pour les NetworkPolicies basées sur les adresses IP, en revanche, on définit des politiques à partir de blocs d'adresses IP (plages CIDR).

Prérequis

Les politiques réseau sont mises en œuvre par le plugin réseau. Pour utiliser les politiques réseau, il vous faut une solution réseau qui prend en charge les NetworkPolicies. Créer une ressource NetworkPolicy sans contrôleur pour la mettre en œuvre n'aura aucun effet.

Les deux types d'isolation d'un pod

Un pod peut être isolé de deux façons : en sortie (egress) et en entrée (ingress). Ces isolations portent sur les connexions qui peuvent être établies. Ici, « isolé » n'a rien d'absolu : cela signifie que « certaines restrictions s'appliquent ». À l'inverse, « non isolé dans une direction » signifie qu'aucune restriction ne s'applique dans cette direction. Les deux types d'isolation (ou leur absence) sont déclarés indépendamment l'un de l'autre, et entrent tous deux en jeu pour une connexion d'un pod vers un autre.

Par défaut, un pod n'est pas isolé en sortie : toutes les connexions sortantes sont autorisées. Un pod est isolé en sortie s'il existe au moins une NetworkPolicy qui sélectionne ce pod et dont les policyTypes contiennent « Egress » ; on dit alors que cette politique s'applique au pod en sortie. Quand un pod est isolé en sortie, les seules connexions autorisées depuis ce pod sont celles qu'autorise la liste egress d'au moins une NetworkPolicy qui s'applique au pod en sortie. Le trafic de réponse de ces connexions autorisées est lui aussi autorisé implicitement. Les effets de ces listes egress s'additionnent.

Par défaut, un pod n'est pas isolé en entrée : toutes les connexions entrantes sont autorisées. Un pod est isolé en entrée s'il existe au moins une NetworkPolicy qui sélectionne ce pod et dont les policyTypes contiennent « Ingress » ; on dit alors que cette politique s'applique au pod en entrée. Quand un pod est isolé en entrée, les seules connexions autorisées vers ce pod sont celles qui viennent du nœud du pod et celles qu'autorise la liste ingress d'au moins une NetworkPolicy qui s'applique au pod en entrée. Le trafic de réponse de ces connexions autorisées est lui aussi autorisé implicitement. Les effets de ces listes ingress s'additionnent.

Les politiques réseau n'entrent pas en conflit : elles s'additionnent. Si une ou plusieurs politiques s'appliquent à un pod donné dans une direction donnée, les connexions autorisées dans cette direction pour ce pod sont l'union de ce qu'autorisent ces politiques. L'ordre d'évaluation n'a donc aucune influence sur le résultat.

Pour qu'une connexion d'un pod source vers un pod de destination soit autorisée, il faut que la politique en sortie du pod source et la politique en entrée du pod de destination l'autorisent toutes les deux. Si l'un des deux côtés ne l'autorise pas, la connexion n'a pas lieu.

La ressource NetworkPolicy

Consultez la référence NetworkPolicy pour la définition complète de la ressource.

Voici un exemple de NetworkPolicy :

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - ipBlock:
        cidr: 172.17.0.0/16
        except:
        - 172.17.1.0/24
    - namespaceSelector:
        matchLabels:
          project: myproject
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 6379
  egress:
  - to:
    - ipBlock:
        cidr: 10.0.0.0/24
    ports:
    - protocol: TCP
      port: 5978

Note:

Envoyer cette ressource (POST) au serveur d'API de votre cluster n'aura aucun effet si la solution réseau que vous avez choisie ne prend pas en charge les politiques réseau.

Champs obligatoires : comme toute autre configuration Kubernetes, une NetworkPolicy a besoin des champs apiVersion, kind et metadata. Pour des informations générales sur l'utilisation des fichiers de configuration, consultez Configurer un Pod pour utiliser une ConfigMap et Gestion des objets.

spec : la spec d'une NetworkPolicy contient toutes les informations nécessaires pour définir une politique réseau donnée dans le namespace concerné.

podSelector : chaque NetworkPolicy contient un podSelector qui sélectionne le groupe de pods auquel la politique s'applique. La politique de l'exemple sélectionne les pods qui ont le label « role=db ». Un podSelector vide sélectionne tous les pods du namespace.

policyTypes : chaque NetworkPolicy contient une liste policyTypes qui peut contenir Ingress, Egress ou les deux. Le champ policyTypes indique si la politique s'applique au trafic entrant vers les pods sélectionnés, au trafic sortant des pods sélectionnés, ou aux deux. Si aucun policyTypes n'est indiqué dans une NetworkPolicy, Ingress est toujours défini par défaut, et Egress l'est aussi si la NetworkPolicy contient des règles egress.

ingress : chaque NetworkPolicy peut contenir une liste de règles ingress autorisées. Chaque règle autorise le trafic qui correspond à la fois aux sections from et ports. La politique de l'exemple contient une seule règle, qui correspond au trafic sur un seul port, depuis l'une de trois sources : la première définie par un ipBlock, la deuxième par un namespaceSelector et la troisième par un podSelector.

egress : chaque NetworkPolicy peut contenir une liste de règles egress autorisées. Chaque règle autorise le trafic qui correspond à la fois aux sections to et ports. La politique de l'exemple contient une seule règle, qui correspond au trafic sur un seul port vers n'importe quelle destination dans 10.0.0.0/24.

La NetworkPolicy de l'exemple a donc les effets suivants :

  1. elle isole les pods role=db du namespace default pour le trafic entrant et sortant (s'ils n'étaient pas déjà isolés)

  2. (règles Ingress) elle autorise les connexions vers tous les pods du namespace default qui ont le label role=db, sur le port TCP 6379, depuis :

    • n'importe quel pod du namespace default qui a le label role=frontend
    • n'importe quel pod d'un namespace qui a le label project=myproject
    • les adresses IP des plages 172.17.0.0–172.17.0.255 et 172.17.2.0–172.17.255.255 (c'est-à-dire tout 172.17.0.0/16 sauf 172.17.1.0/24)
  3. (règles Egress) elle autorise les connexions depuis n'importe quel pod du namespace default qui a le label role=db vers le CIDR 10.0.0.0/24, sur le port TCP 5978

Consultez le tutoriel Déclarer une politique réseau pour d'autres exemples.

Comportement des sélecteurs to et from

Quatre types de sélecteurs peuvent être indiqués dans une section from d'ingress ou dans une section to d'egress :

podSelector : sélectionne certains Pods du même namespace que la NetworkPolicy, qui doivent être autorisés comme sources en entrée ou comme destinations en sortie.

namespaceSelector : sélectionne certains namespaces dont tous les Pods doivent être autorisés comme sources en entrée ou comme destinations en sortie.

namespaceSelector et podSelector : une même entrée to/from qui indique à la fois namespaceSelector et podSelector sélectionne certains Pods dans certains namespaces. Veillez à utiliser une syntaxe YAML correcte. Par exemple :

  ...
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          user: alice
      podSelector:
        matchLabels:
          role: client
  ...

Cette politique contient un seul élément from, qui autorise les connexions depuis les Pods qui ont le label role=client dans les namespaces qui ont le label user=alice. La politique suivante, elle, est différente :

  ...
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          user: alice
    - podSelector:
        matchLabels:
          role: client
  ...

Elle contient deux éléments dans le tableau from, et autorise les connexions depuis les Pods du namespace local qui ont le label role=client, ou depuis n'importe quel Pod de n'importe quel namespace qui a le label user=alice.

En cas de doute, utilisez kubectl describe pour voir comment Kubernetes a interprété la politique.

ipBlock : sélectionne certaines plages CIDR d'adresses IP à autoriser comme sources en entrée ou comme destinations en sortie. Il devrait s'agir d'adresses IP externes au cluster, car les adresses IP des Pods sont éphémères et imprévisibles.

Les mécanismes d'entrée et de sortie du cluster doivent souvent réécrire l'adresse IP source ou de destination des paquets. Lorsque c'est le cas, rien ne définit si cette réécriture a lieu avant ou après le traitement des NetworkPolicies, et le comportement peut varier selon la combinaison de plugin réseau, de fournisseur cloud, d'implémentation des Service, etc.

En entrée, cela veut dire que, dans certains cas, vous pourrez filtrer les paquets entrants selon leur véritable adresse IP source d'origine, alors que dans d'autres cas, l'« adresse IP source » prise en compte par la NetworkPolicy peut être celle d'un LoadBalancer, du nœud du Pod, etc.

En sortie, cela veut dire que les connexions des pods vers des adresses IP de Service réécrites en adresses IP externes au cluster peuvent être soumises ou non aux politiques basées sur ipBlock.

Politiques par défaut

Par défaut, s'il n'existe aucune politique dans un namespace, tout le trafic entrant et sortant est autorisé vers et depuis les pods de ce namespace. Les exemples suivants vous permettent de modifier ce comportement par défaut dans ce namespace.

Refuser par défaut tout le trafic entrant

Pour créer une politique d'isolation en entrée « par défaut » dans un namespace, créez une NetworkPolicy qui sélectionne tous les pods mais n'autorise aucun trafic entrant vers ces pods.

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
spec:
  podSelector: {}
  policyTypes:
  - Ingress

Ainsi, même les pods qui ne sont sélectionnés par aucune autre NetworkPolicy restent isolés en entrée. Cette politique n'a pas d'effet sur l'isolation en sortie des pods.

Autoriser tout le trafic entrant

Si vous voulez autoriser toutes les connexions entrantes vers tous les pods d'un namespace, vous pouvez créer une politique qui les autorise explicitement.

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-ingress
spec:
  podSelector: {}
  ingress:
  - {}
  policyTypes:
  - Ingress

Une fois cette politique en place, aucune autre politique ne peut entraîner le refus d'une connexion entrante vers ces pods. Cette politique n'a pas d'effet sur l'isolation en sortie des pods.

Refuser par défaut tout le trafic sortant

Pour créer une politique d'isolation en sortie « par défaut » dans un namespace, créez une NetworkPolicy qui sélectionne tous les pods mais n'autorise aucun trafic sortant depuis ces pods.

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
spec:
  podSelector: {}
  policyTypes:
  - Egress

Ainsi, même les pods qui ne sont sélectionnés par aucune autre NetworkPolicy n'ont droit à aucun trafic sortant. Cette politique ne change pas le comportement d'isolation en entrée des pods.

Avertissement:

Une politique qui refuse par défaut tout le trafic sortant bloque aussi le trafic DNS. Si vos charges de travail ont besoin de la résolution DNS, vous devez ajouter une NetworkPolicy distincte qui autorise le trafic sortant vers le service DNS de votre cluster.

Autoriser tout le trafic sortant

Si vous voulez autoriser toutes les connexions depuis tous les pods d'un namespace, vous pouvez créer une politique qui autorise explicitement toutes les connexions sortantes des pods de ce namespace.

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-egress
spec:
  podSelector: {}
  egress:
  - {}
  policyTypes:
  - Egress

Une fois cette politique en place, aucune autre politique ne peut entraîner le refus d'une connexion sortante depuis ces pods. Cette politique n'a pas d'effet sur l'isolation en entrée des pods.

Refuser par défaut tout le trafic entrant et sortant

Vous pouvez créer une politique « par défaut » pour un namespace qui empêche tout le trafic entrant ET sortant en créant la NetworkPolicy suivante dans ce namespace.

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

Ainsi, même les pods qui ne sont sélectionnés par aucune autre NetworkPolicy n'ont droit à aucun trafic entrant ni sortant.

Filtrage du trafic réseau

Les NetworkPolicies sont définies pour les connexions de couche 4 (TCP, UDP et, en option, SCTP). Pour tous les autres protocoles, le comportement peut varier d'un plugin réseau à l'autre.

Note:

Vous devez utiliser un plugin CNI qui prend en charge les NetworkPolicies pour le protocole SCTP.

Lorsqu'une politique réseau deny all est définie, elle ne garantit que le refus des connexions TCP, UDP et SCTP. Pour les autres protocoles, comme ARP ou ICMP, le comportement n'est pas défini. Il en va de même pour les règles d'autorisation : quand un pod précis est autorisé comme source en entrée ou comme destination en sortie, ce qu'il advient des paquets ICMP, par exemple, n'est pas défini. Des protocoles comme ICMP peuvent être autorisés par certains plugins réseau et refusés par d'autres.

Cibler une plage de ports

Feature state: Stable since Kubernetes v1.25

Quand vous écrivez une NetworkPolicy, vous pouvez cibler une plage de ports au lieu d'un seul port.

Pour cela, utilisez le champ endPort, comme dans l'exemple suivant :

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: multi-port-egress
  namespace: default
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
    - Egress
  egress:
    - to:
        - ipBlock:
            cidr: 10.0.0.0/24
      ports:
        - protocol: TCP
          port: 32000
          endPort: 32768

La règle ci-dessus autorise n'importe quel Pod qui a le label role=db dans le namespace default à communiquer avec n'importe quelle adresse IP de la plage 10.0.0.0/24 en TCP, à condition que le port de destination soit compris entre 32000 et 32768.

Les restrictions suivantes s'appliquent à l'utilisation de ce champ :

  • Le champ endPort doit être supérieur ou égal au champ port.
  • endPort ne peut être défini que si port est aussi défini.
  • Les deux ports doivent être numériques.

Note:

Votre cluster doit utiliser un plugin CNI qui prend en charge le champ endPort dans les spécifications des NetworkPolicies. Si votre plugin réseau ne prend pas en charge le champ endPort et que vous définissez une NetworkPolicy qui l'utilise, seul le champ port sera pris en compte.

Cibler plusieurs namespaces par label

Dans ce scénario, votre NetworkPolicy Egress cible plusieurs namespaces grâce à leurs labels. Pour que cela fonctionne, vous devez poser un label sur les namespaces cibles. Par exemple :

kubectl label namespace frontend namespace=frontend
kubectl label namespace backend namespace=backend

Ajoutez les labels sous namespaceSelector dans votre NetworkPolicy. Par exemple :

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: egress-namespaces
spec:
  podSelector:
    matchLabels:
      app: myapp
  policyTypes:
  - Egress
  egress:
  - to:
    - namespaceSelector:
        matchExpressions:
        - key: namespace
          operator: In
          values: ["frontend", "backend"]

Note:

Il n'est pas possible d'indiquer directement le nom des namespaces dans une NetworkPolicy. Vous devez utiliser un namespaceSelector avec matchLabels ou matchExpressions pour sélectionner les namespaces selon leurs labels.

Cibler un namespace par son nom

Le plan de contrôle de Kubernetes pose sur tous les namespaces un label immuable kubernetes.io/metadata.name, dont la valeur est le nom du namespace.

Une NetworkPolicy ne peut pas cibler un namespace par son nom via un champ de l'objet, mais vous pouvez utiliser ce label standardisé pour cibler un namespace précis.

Cycle de vie d'un pod

Note:

Ce qui suit s'applique aux clusters qui disposent d'un plugin réseau conforme et d'une implémentation conforme des NetworkPolicies.

Quand un nouvel objet NetworkPolicy est créé, le plugin réseau peut mettre un certain temps à le prendre en compte. Si un pod concerné par une NetworkPolicy est créé avant que le plugin réseau ait fini de traiter cette NetworkPolicy, ce pod peut démarrer sans protection, et les règles d'isolation seront appliquées une fois le traitement de la NetworkPolicy terminé.

Une fois la NetworkPolicy traitée par le plugin réseau :

  1. Tous les nouveaux pods concernés par une NetworkPolicy donnée seront isolés avant de démarrer. Les implémentations des NetworkPolicies doivent garantir que le filtrage est effectif pendant tout le cycle de vie du Pod, dès le tout premier instant où l'un des conteneurs de ce Pod démarre. Comme elles s'appliquent au niveau du Pod, les NetworkPolicies s'appliquent de la même façon aux conteneurs d'initialisation, aux conteneurs sidecar et aux conteneurs ordinaires.

  2. Les règles d'autorisation finiront par être appliquées après les règles d'isolation (ou peut-être en même temps). Dans le pire des cas, un pod nouvellement créé peut n'avoir aucune connectivité réseau au démarrage, si les règles d'isolation étaient déjà appliquées mais pas encore les règles d'autorisation.

Chaque NetworkPolicy créée finira par être traitée par un plugin réseau, mais l'API Kubernetes ne permet pas de savoir à quel moment précis.

Les pods doivent donc pouvoir démarrer avec une connectivité réseau différente de celle attendue. Si vous devez vous assurer que le pod peut joindre certaines destinations avant de démarrer, vous pouvez utiliser un conteneur d'initialisation qui attend que ces destinations soient joignables avant que le kubelet ne démarre les conteneurs de l'application.

Chaque NetworkPolicy finira par être appliquée à tous les pods sélectionnés. Comme le plugin réseau peut mettre en œuvre les NetworkPolicies de manière distribuée, il est possible que les pods aient une vue légèrement incohérente des politiques réseau au moment de la création d'un pod, ou quand des pods ou des politiques changent. Par exemple, un pod nouvellement créé qui doit pouvoir joindre à la fois le Pod A sur le Nœud 1 et le Pod B sur le Nœud 2 peut constater qu'il joint le Pod A immédiatement, mais qu'il ne peut joindre le Pod B que quelques secondes plus tard.

NetworkPolicy et pods hostNetwork

Le comportement des NetworkPolicies pour les pods hostNetwork n'est pas défini, mais il devrait se limiter à deux possibilités :

  • Le plugin réseau sait distinguer le trafic des pods hostNetwork de tout le reste du trafic (y compris distinguer le trafic de différents pods hostNetwork sur le même nœud), et applique les NetworkPolicies aux pods hostNetwork comme aux pods qui utilisent le réseau des pods.
  • Le plugin réseau ne sait pas distinguer correctement le trafic des pods hostNetwork, et ignore donc les pods hostNetwork lorsqu'il évalue podSelector et namespaceSelector. Le trafic depuis et vers les pods hostNetwork est traité comme tout autre trafic depuis et vers l'adresse IP du nœud. (C'est l'implémentation la plus courante.)

Cela s'applique quand

  1. un pod hostNetwork est sélectionné par spec.podSelector.

      ...
      spec:
        podSelector:
          matchLabels:
            role: client
      ...
    
  2. un pod hostNetwork est sélectionné par un podSelector ou un namespaceSelector dans une règle ingress ou egress.

      ...
      ingress:
        - from:
          - podSelector:
              matchLabels:
                role: client
      ...
    

Par ailleurs, comme les pods hostNetwork ont la même adresse IP que le nœud sur lequel ils s'exécutent, leurs connexions sont traitées comme des connexions du nœud. Par exemple, vous pouvez autoriser le trafic venant d'un Pod hostNetwork avec une règle ipBlock.

Ce que les politiques réseau ne permettent pas de faire (du moins, pas encore)

Dans Kubernetes 1.37, les fonctionnalités suivantes n'existent pas dans l'API NetworkPolicy, mais vous pouvez peut-être les contourner avec des composants du système d'exploitation (comme SELinux, OpenVSwitch, IPTables, etc.), des technologies de couche 7 (contrôleurs d'Ingress, implémentations de service mesh) ou des contrôleurs d'admission. Si vous découvrez la sécurité réseau dans Kubernetes, sachez que les cas d'usage suivants ne peuvent pas (encore) être mis en œuvre avec l'API NetworkPolicy.

  • Forcer le trafic interne du cluster à passer par une passerelle commune (un service mesh ou un autre proxy est sans doute plus adapté).
  • Tout ce qui touche à TLS (utilisez un service mesh ou un contrôleur d'Ingress pour cela).
  • Des politiques propres à un nœud (vous pouvez utiliser la notation CIDR, mais vous ne pouvez pas cibler les nœuds par leur identité Kubernetes).
  • Cibler des services par leur nom (vous pouvez en revanche cibler des pods ou des namespaces par leurs labels, ce qui est souvent un bon moyen de contourner le problème).
  • Créer ou gérer des « demandes de politique » traitées par un tiers.
  • Des politiques par défaut appliquées à tous les namespaces ou à tous les pods (certains projets et distributions Kubernetes tiers savent le faire).
  • Des outils avancés d'interrogation des politiques et d'analyse d'accessibilité.
  • Journaliser les événements de sécurité réseau (par exemple les connexions bloquées ou acceptées).
  • Refuser explicitement par une politique (actuellement, le modèle des NetworkPolicies refuse par défaut et permet seulement d'ajouter des règles d'autorisation).
  • Empêcher le trafic de loopback ou le trafic entrant venant de l'hôte (les Pods ne peuvent pas, à l'heure actuelle, bloquer l'accès à localhost, ni bloquer l'accès depuis le nœud sur lequel ils s'exécutent).

Impact des NetworkPolicies sur les connexions existantes

Quand l'ensemble des NetworkPolicies qui s'appliquent à une connexion existante change (parce que les NetworkPolicies sont modifiées, ou parce que les labels concernés des namespaces ou des pods sélectionnés par la politique, côté sujet comme côté pairs, changent pendant une connexion existante), c'est l'implémentation qui détermine si le changement s'applique ou non à cette connexion existante. Exemple : une politique est créée et refuse une connexion jusque-là autorisée. C'est l'implémentation du plugin réseau sous-jacent qui détermine si cette nouvelle politique ferme ou non les connexions existantes. Il est recommandé de ne pas modifier les politiques, les pods ou les namespaces d'une façon qui pourrait affecter les connexions existantes.

A suivre

7 - DNS pour les Services et les Pods

Vos charges de travail peuvent découvrir les Services de votre cluster grâce au DNS ; cette page explique comment cela fonctionne.

Kubernetes crée des enregistrements DNS pour les Services et les Pods. Vous pouvez joindre les Services avec des noms DNS stables plutôt qu'avec des adresses IP.

Kubernetes publie des informations sur les Pods et les Services, qui servent à configurer le DNS. Le kubelet configure le DNS des Pods pour que les conteneurs en cours d'exécution puissent trouver les Services par leur nom plutôt que par leur adresse IP.

Les Services définis dans le cluster reçoivent des noms DNS. Par défaut, la liste de recherche DNS d'un Pod client contient le namespace du Pod lui-même et le domaine par défaut du cluster.

Namespaces des Services

Une requête DNS peut renvoyer des résultats différents selon le namespace du Pod qui l'envoie. Les requêtes DNS qui n'indiquent pas de namespace sont limitées au namespace du Pod. Pour accéder aux Services d'autres namespaces, indiquez le namespace dans la requête DNS.

Prenons par exemple un Pod dans un namespace test, et un Service data dans le namespace prod.

Une requête pour data ne renvoie aucun résultat, car elle utilise le namespace test du Pod.

Une requête pour data.prod renvoie le résultat attendu, car elle indique le namespace.

Les requêtes DNS peuvent être complétées à l'aide du fichier /etc/resolv.conf du Pod. Le kubelet configure ce fichier pour chaque Pod. Par exemple, une requête pour data seul peut être complétée en data.test.svc.cluster.local. Ce sont les valeurs de l'option search qui servent à compléter les requêtes. Pour en savoir plus sur les requêtes DNS, consultez la page de manuel de resolv.conf.

nameserver 10.32.0.10
search <namespace>.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

En résumé, un Pod du namespace test peut résoudre aussi bien data.prod que data.prod.svc.cluster.local.

Enregistrements DNS

Quels objets reçoivent des enregistrements DNS ?

  1. Les Services
  2. Les Pods

Les sections suivantes décrivent en détail les types d'enregistrements DNS et la structure pris en charge. Toute autre structure, tout autre nom ou toute autre requête qui se trouverait fonctionner relève de détails d'implémentation susceptibles de changer sans préavis. Pour une spécification plus à jour, consultez Kubernetes DNS-Based Service Discovery.

Services

Enregistrements A/AAAA

Les Services « normaux » (non headless) reçoivent des enregistrements DNS A et/ou AAAA, selon la ou les familles d'adresses IP du Service, avec un nom de la forme my-svc.my-namespace.svc.cluster-domain.example. Ce nom est résolu en l'adresse IP de cluster du Service.

Les Services headless (sans IP de cluster) reçoivent eux aussi des enregistrements DNS A et/ou AAAA, avec un nom de la forme my-svc.my-namespace.svc.cluster-domain.example. Contrairement aux Services normaux, ce nom est résolu en l'ensemble des adresses IP de tous les Pods sélectionnés par le Service. Les clients sont censés utiliser cet ensemble, ou bien y choisir une adresse en round-robin standard.

Enregistrements SRV

Des enregistrements SRV sont créés pour les ports nommés des Services normaux ou headless.

  • Pour chaque port nommé, l'enregistrement SRV a la forme _port-name._port-protocol.my-svc.my-namespace.svc.cluster-domain.example.
  • Pour un Service normal, il est résolu en le numéro de port et le nom de domaine my-svc.my-namespace.svc.cluster-domain.example.
  • Pour un Service headless, il est résolu en plusieurs réponses, une pour chaque Pod sous-jacent au Service. Chaque réponse contient le numéro de port et le nom de domaine du Pod, de la forme hostname.my-svc.my-namespace.svc.cluster-domain.example.

Pods

Enregistrements A/AAAA

Avant la mise en œuvre de la spécification DNS, les versions de Kube-DNS utilisaient la résolution DNS suivante :

<pod-IPv4-address>.<namespace>.pod.<cluster-domain>

Par exemple, si un Pod du namespace default a l'adresse IP 172.17.0.3, et que le nom de domaine de votre cluster est cluster.local, le Pod a le nom DNS suivant :

172-17-0-3.default.pod.cluster.local

Certains mécanismes DNS de cluster, comme CoreDNS, fournissent aussi des enregistrements A de la forme :

<pod-ipv4-address>.<service-name>.<my-namespace>.svc.<cluster-domain.example>

Par exemple, si un Pod du namespace cafe a l'adresse IP 172.17.0.3, qu'il est un point de terminaison d'un Service nommé barista et que le nom de domaine de votre cluster est cluster.local, le Pod aura l'enregistrement DNS A suivant, propre au Service.

172-17-0-3.barista.cafe.svc.cluster.local

Champs hostname et subdomain d'un Pod

Actuellement, lorsqu'un Pod est créé, son nom d'hôte (vu depuis l'intérieur du Pod) est la valeur metadata.name du Pod.

La spec du Pod a un champ facultatif hostname, qui permet d'indiquer un autre nom d'hôte. Lorsqu'il est renseigné, il prime sur le nom du Pod pour définir le nom d'hôte du Pod (toujours vu depuis l'intérieur du Pod). Par exemple, un Pod dont spec.hostname vaut "my-host" aura pour nom d'hôte "my-host".

La spec du Pod a aussi un champ facultatif subdomain, qui permet d'indiquer que le Pod fait partie d'un sous-groupe du namespace. Par exemple, un Pod dont spec.hostname vaut "foo" et spec.subdomain vaut "bar", dans le namespace "my-namespace", aura pour nom d'hôte "foo" et pour nom de domaine pleinement qualifié (FQDN) "foo.bar.my-namespace.svc.cluster.local" (là encore, vu depuis l'intérieur du Pod).

S'il existe un Service headless dans le même namespace que le Pod, avec le même nom que le sous-domaine, le serveur DNS du cluster renvoie aussi des enregistrements A et/ou AAAA pour le nom d'hôte pleinement qualifié du Pod.

Exemple :

apiVersion: v1
kind: Service
metadata:
  name: busybox-subdomain
spec:
  selector:
    name: busybox
  clusterIP: None
  ports:
  - name: foo # name is not required for single-port Services
    port: 1234
---
apiVersion: v1
kind: Pod
metadata:
  name: busybox1
  labels:
    name: busybox
spec:
  hostname: busybox-1
  subdomain: busybox-subdomain
  containers:
  - image: busybox:1.28
    command:
      - sleep
      - "3600"
    name: busybox
---
apiVersion: v1
kind: Pod
metadata:
  name: busybox2
  labels:
    name: busybox
spec:
  hostname: busybox-2
  subdomain: busybox-subdomain
  containers:
  - image: busybox:1.28
    command:
      - sleep
      - "3600"
    name: busybox

Avec le Service "busybox-subdomain" ci-dessus et les Pods dont spec.subdomain vaut "busybox-subdomain", le premier Pod verra "busybox-1.busybox-subdomain.my-namespace.svc.cluster-domain.example" comme son propre FQDN. Le DNS fournit des enregistrements A et/ou AAAA pour ce nom, qui pointent vers l'adresse IP du Pod. Les deux Pods « busybox1 » et « busybox2 » auront chacun leurs propres enregistrements d'adresse.

Un EndpointSlice peut indiquer le nom d'hôte DNS de n'importe quelle adresse de point de terminaison, avec son adresse IP.

Note:

Aucun enregistrement A ou AAAA n'est créé pour les noms de Pods si le Pod n'a pas de hostname. Pour un Pod sans hostname mais avec un subdomain, seul l'enregistrement A ou AAAA du Service headless (busybox-subdomain.my-namespace.svc.cluster-domain.example) est créé ; il pointe vers les adresses IP des Pods. Par ailleurs, le Pod doit être prêt (ready) pour avoir un enregistrement, sauf si publishNotReadyAddresses=True est défini sur le Service.

Champ setHostnameAsFQDN d'un Pod

Feature state: Stable since Kubernetes v1.22

Lorsqu'un Pod est configuré pour avoir un nom de domaine pleinement qualifié (FQDN), son nom d'hôte est le nom d'hôte court. Par exemple, pour un Pod dont le nom de domaine pleinement qualifié est busybox-1.busybox-subdomain.my-namespace.svc.cluster-domain.example, la commande hostname exécutée dans ce Pod renvoie par défaut busybox-1, et la commande hostname --fqdn renvoie le FQDN.

Lorsque vous définissez setHostnameAsFQDN: true dans la spec du Pod, le kubelet écrit le FQDN du Pod comme nom d'hôte dans le namespace de ce Pod. Dans ce cas, hostname et hostname --fqdn renvoient tous les deux le FQDN du Pod.

Note:

Sous Linux, le champ hostname du noyau (le champ nodename de struct utsname) est limité à 64 caractères.

Si un Pod active cette fonctionnalité et que son FQDN dépasse 64 caractères, il ne pourra pas démarrer. Le Pod restera à l'état Pending (ContainerCreating dans kubectl) et générera des événements d'erreur, comme Failed to construct FQDN from Pod hostname and cluster domain, FQDN long-FQDN is too long (64 characters is the max, 70 characters requested). Pour améliorer l'expérience utilisateur dans ce cas, vous pouvez créer un contrôleur de webhook d'admission qui vérifie la taille du FQDN lorsque les utilisateurs créent des objets de premier niveau, par exemple un Deployment.

Politique DNS d'un Pod

Les politiques DNS se définissent Pod par Pod. Actuellement, Kubernetes prend en charge les politiques DNS suivantes pour les Pods. Elles sont indiquées dans le champ dnsPolicy de la spec du Pod.

  • « Default » : le Pod hérite de la configuration de résolution de noms du nœud sur lequel il s'exécute. Consultez la discussion à ce sujet pour plus de détails.

  • « ClusterFirst » : toute requête DNS qui ne correspond pas au suffixe de domaine configuré pour le cluster, comme « www.kubernetes.io », est transmise par le serveur DNS à un serveur de noms en amont. Les administrateurs du cluster peuvent avoir configuré des serveurs DNS supplémentaires, pour des domaines de délégation (stub domains) ou en amont. Consultez la discussion à ce sujet pour savoir comment les requêtes DNS sont traitées dans ces cas.

  • « ClusterFirstWithHostNet » : pour les Pods qui s'exécutent avec hostNetwork, il faut régler explicitement leur politique DNS sur « ClusterFirstWithHostNet ». Sinon, les Pods qui s'exécutent avec hostNetwork et "ClusterFirst" se comporteront comme avec la politique "Default".

    Note:

    Ce n'est pas pris en charge sous Windows. Pour plus de détails, voir ci-dessous.
  • « None » : permet à un Pod d'ignorer les paramètres DNS de l'environnement Kubernetes. Tous les paramètres DNS sont alors censés être fournis par le champ dnsConfig de la spec du Pod. Consultez la sous-section Configuration DNS d'un Pod ci-dessous.

Note:

« Default » n'est pas la politique DNS par défaut. Si dnsPolicy n'est pas indiqué explicitement, c'est « ClusterFirst » qui est utilisé.

L'exemple ci-dessous montre un Pod dont la politique DNS est « ClusterFirstWithHostNet », car son champ hostNetwork vaut true.

apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
  - image: busybox:1.28
    command:
      - sleep
      - "3600"
    imagePullPolicy: IfNotPresent
    name: busybox
  restartPolicy: Always
  hostNetwork: true
  dnsPolicy: ClusterFirstWithHostNet

Configuration DNS d'un Pod

Feature state: Stable since Kubernetes v1.14

La configuration DNS d'un Pod donne aux utilisateurs plus de contrôle sur les paramètres DNS de ce Pod.

Le champ dnsConfig est facultatif et fonctionne avec n'importe quelle valeur de dnsPolicy. En revanche, lorsque le dnsPolicy d'un Pod vaut « None », le champ dnsConfig doit être renseigné.

Voici les propriétés qu'un utilisateur peut indiquer dans le champ dnsConfig :

  • nameservers : une liste d'adresses IP qui serviront de serveurs DNS au Pod. On peut indiquer au maximum 3 adresses IP. Lorsque le dnsPolicy du Pod vaut « None », la liste doit contenir au moins une adresse IP ; dans les autres cas, cette propriété est facultative. Les serveurs indiqués sont ajoutés aux serveurs de noms de base générés à partir de la politique DNS choisie, les adresses en double étant supprimées.
  • searches : une liste de domaines de recherche DNS pour la résolution des noms d'hôte dans le Pod. Cette propriété est facultative. Si elle est renseignée, la liste fournie est fusionnée avec les domaines de recherche de base générés à partir de la politique DNS choisie. Les noms de domaine en double sont supprimés. Kubernetes accepte jusqu'à 32 domaines de recherche.
  • options : une liste facultative d'objets, dont chacun peut avoir une propriété name (obligatoire) et une propriété value (facultative). Le contenu de cette propriété est fusionné avec les options générées à partir de la politique DNS choisie. Les entrées en double sont supprimées.

Voici un exemple de Pod avec des paramètres DNS personnalisés :

apiVersion: v1
kind: Pod
metadata:
  namespace: default
  name: dns-example
spec:
  containers:
    - name: test
      image: nginx
  dnsPolicy: "None"
  dnsConfig:
    nameservers:
      - 192.0.2.1 # this is an example
    searches:
      - ns1.svc.cluster-domain.example
      - my.dns.search.suffix
    options:
      - name: ndots
        value: "2"
      - name: edns0

Une fois le Pod ci-dessus créé, le fichier /etc/resolv.conf du conteneur test a le contenu suivant :

nameserver 192.0.2.1
search ns1.svc.cluster-domain.example my.dns.search.suffix
options ndots:2 edns0

Pour une configuration IPv6, le chemin de recherche et le serveur de noms devraient se présenter ainsi :

kubectl exec -it dns-example -- cat /etc/resolv.conf

Le résultat ressemble à ceci :

nameserver 2001:db8:30::a
search default.svc.cluster-domain.example svc.cluster-domain.example cluster-domain.example
options ndots:5

Limites de la liste des domaines de recherche DNS

Feature state: Stable since Kubernetes v1.28

Kubernetes lui-même ne limite la configuration DNS que lorsque la liste des domaines de recherche dépasse 32 éléments, ou que la longueur totale de tous les domaines de recherche dépasse 2048. Cette limite s'applique respectivement au fichier de configuration du résolveur du nœud, à la configuration DNS du Pod et à la configuration DNS fusionnée.

Note:

Les anciennes versions de certains environnements d'exécution de conteneurs peuvent imposer leurs propres restrictions sur le nombre de domaines de recherche DNS. Selon l'environnement d'exécution de conteneurs, les Pods qui ont beaucoup de domaines de recherche DNS peuvent rester bloqués à l'état Pending.

Ce problème est connu pour containerd v1.5.5 et versions antérieures, ainsi que pour CRI-O v1.21 et versions antérieures.

Résolution DNS sur les nœuds Windows

  • ClusterFirstWithHostNet n'est pas pris en charge pour les Pods qui s'exécutent sur des nœuds Windows. Windows considère tout nom qui contient un . comme un FQDN et ne fait pas de résolution FQDN.
  • Sous Windows, plusieurs résolveurs DNS peuvent être utilisés. Comme leurs comportements diffèrent légèrement, il est recommandé d'utiliser la cmdlet PowerShell Resolve-DNSName pour résoudre les noms.
  • Sous Linux, vous disposez d'une liste de suffixes DNS, utilisée lorsque la résolution d'un nom en tant que nom pleinement qualifié a échoué. Sous Windows, vous ne pouvez avoir qu'un seul suffixe DNS : celui associé au namespace du Pod (par exemple mydns.svc.cluster.local). Windows peut résoudre les FQDN, les Services ou les noms réseau résolubles avec ce seul suffixe. Par exemple, un Pod lancé dans le namespace default aura le suffixe DNS default.svc.cluster.local. Dans un Pod Windows, vous pouvez résoudre à la fois kubernetes.default.svc.cluster.local et kubernetes, mais pas les noms partiellement qualifiés (kubernetes.default ou kubernetes.default.svc).

A suivre

Pour savoir comment administrer les configurations DNS, consultez Configurer le service DNS.

8 - Routage tenant compte de la topologie

Le routage tenant compte de la topologie (Topology Aware Routing) fournit un mécanisme qui aide à garder le trafic réseau dans sa zone d'origine. Privilégier le trafic dans la même zone entre les Pods de votre cluster peut améliorer la fiabilité, les performances (latence et débit réseau) ou réduire les coûts.
Feature state: Beta since Kubernetes v1.23

Note:

Avant Kubernetes 1.27, cette fonctionnalité s'appelait Topology Aware Hints.

Le routage tenant compte de la topologie adapte le comportement du routage pour garder de préférence le trafic dans sa zone d'origine. Dans certains cas, cela peut réduire les coûts ou améliorer les performances réseau.

Motivation

Les clusters Kubernetes sont de plus en plus souvent déployés dans des environnements multizones. Le routage tenant compte de la topologie fournit un mécanisme qui aide à garder le trafic dans sa zone d'origine. Lorsqu'il calcule les points de terminaison d'un Service, le contrôleur EndpointSlice tient compte de la topologie (région et zone) de chaque point de terminaison et renseigne le champ hints (indications) pour l'attribuer à une zone. Les composants du cluster, comme kube-proxy, peuvent ensuite utiliser ces indications pour orienter le routage du trafic (en privilégiant les points de terminaison topologiquement les plus proches).

Activer le routage tenant compte de la topologie

Note:

Avant Kubernetes 1.27, ce comportement était contrôlé par l'annotation service.kubernetes.io/topology-aware-hints.

Vous pouvez activer le routage tenant compte de la topologie pour un Service en définissant l'annotation service.kubernetes.io/topology-mode sur Auto. Lorsque chaque zone dispose de suffisamment de points de terminaison disponibles, des indications de topologie sont ajoutées aux EndpointSlices pour attribuer chaque point de terminaison à une zone précise. Le trafic est alors acheminé plus près de son origine.

Quand cela fonctionne le mieux

Cette fonctionnalité donne les meilleurs résultats lorsque :

1. Le trafic entrant est réparti uniformément

Si une grande partie du trafic provient d'une seule zone, ce trafic risque de surcharger le sous-ensemble de points de terminaison attribués à cette zone. Cette fonctionnalité est déconseillée lorsque le trafic entrant est censé provenir d'une seule zone.

2. Le Service a au moins 3 points de terminaison par zone

Dans un cluster à trois zones, cela représente au moins 9 points de terminaison. Avec moins de 3 points de terminaison par zone, il y a une forte probabilité (environ 50 %) que le contrôleur EndpointSlice ne parvienne pas à répartir les points de terminaison de manière équilibrée et se rabatte sur le routage par défaut à l'échelle du cluster.

Fonctionnement

L'heuristique « Auto » tente d'attribuer à chaque zone un nombre de points de terminaison proportionnel. Cette heuristique donne de meilleurs résultats pour les Services qui ont un nombre important de points de terminaison.

Contrôleur EndpointSlice

Le contrôleur EndpointSlice est chargé de définir les indications sur les EndpointSlices lorsque cette heuristique est activée. Le contrôleur attribue à chaque zone une part proportionnelle des points de terminaison. Cette proportion dépend du nombre de cœurs CPU allouables des nœuds de cette zone. Par exemple, si une zone dispose de 2 cœurs CPU et une autre d'un seul, le contrôleur attribuera deux fois plus de points de terminaison à la zone qui dispose de 2 cœurs.

L'exemple suivant montre un EndpointSlice dont les indications ont été renseignées :

apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
  name: example-hints
  labels:
    kubernetes.io/service-name: example-svc
addressType: IPv4
ports:
  - name: http
    protocol: TCP
    port: 80
endpoints:
  - addresses:
      - "10.1.2.3"
    conditions:
      ready: true
    hostname: pod-1
    zone: zone-a
    hints:
      forZones:
        - name: "zone-a"

kube-proxy

Le composant kube-proxy filtre les points de terminaison vers lesquels il achemine le trafic en fonction des indications définies par le contrôleur EndpointSlice. Dans la plupart des cas, kube-proxy peut ainsi acheminer le trafic vers des points de terminaison de la même zone. Il arrive que le contrôleur attribue des points de terminaison d'une autre zone pour mieux équilibrer leur répartition entre les zones. Une partie du trafic est alors acheminée vers d'autres zones.

Garde-fous

Le plan de contrôle de Kubernetes et le kube-proxy de chaque nœud appliquent certains garde-fous avant d'utiliser les indications de topologie. Si l'un d'eux n'est pas respecté, kube-proxy choisit des points de terminaison n'importe où dans le cluster, quelle que soit la zone.

  1. Nombre insuffisant de points de terminaison : s'il y a moins de points de terminaison que de zones dans le cluster, le contrôleur n'attribue aucune indication.

  2. Répartition équilibrée impossible : dans certains cas, il est impossible de répartir les points de terminaison de manière équilibrée entre les zones. Par exemple, si la zone-a est deux fois plus grande que la zone-b mais qu'il n'y a que 2 points de terminaison, celui attribué à la zone-a risque de recevoir deux fois plus de trafic que celui de la zone-b. Le contrôleur n'attribue pas d'indications s'il ne parvient pas à ramener cette valeur de « surcharge estimée » sous un seuil acceptable pour chaque zone. À noter : ce calcul ne repose pas sur des mesures en temps réel. Un point de terminaison peut toujours se retrouver surchargé.

  3. Informations insuffisantes sur un ou plusieurs nœuds : si un nœud n'a pas de label topology.kubernetes.io/zone ou ne remonte pas de valeur de CPU allouable, le plan de contrôle ne définit aucune indication de topologie sur les points de terminaison, et kube-proxy ne filtre donc pas les points de terminaison par zone.

  4. Un ou plusieurs points de terminaison n'ont pas d'indication de zone : kube-proxy considère alors qu'un passage aux indications de topologie, ou leur abandon, est en cours. Filtrer les points de terminaison d'un Service dans cet état serait risqué : kube-proxy se rabat donc sur tous les points de terminaison.

  5. Une zone n'apparaît dans aucune indication : si kube-proxy ne trouve aucun point de terminaison dont l'indication désigne la zone dans laquelle il s'exécute, il utilise les points de terminaison de toutes les zones. Cette situation se produit surtout lorsque vous ajoutez une nouvelle zone à un cluster existant.

Contraintes

  • Les indications de topologie ne sont pas utilisées lorsque internalTrafficPolicy vaut Local sur un Service. Les deux fonctionnalités peuvent coexister dans un même cluster sur des Services différents, mais pas sur le même Service.

  • Cette approche fonctionne mal pour les Services dont une grande partie du trafic provient d'un sous-ensemble de zones. Elle suppose au contraire que le trafic entrant est à peu près proportionnel à la capacité des nœuds de chaque zone.

  • Le contrôleur EndpointSlice ignore les nœuds qui ne sont pas prêts lorsqu'il calcule la part de chaque zone. Cela peut avoir des conséquences inattendues si une grande partie des nœuds ne sont pas prêts.

  • Le contrôleur EndpointSlice ignore les nœuds qui portent le label node-role.kubernetes.io/control-plane ou node-role.kubernetes.io/master. Cela peut poser problème si des charges de travail s'exécutent aussi sur ces nœuds.

  • Le contrôleur EndpointSlice ne tient pas compte des tolérances lors du déploiement ni lors du calcul de la part de chaque zone. Si les Pods d'un Service sont limités à un sous-ensemble des nœuds du cluster, cela n'est pas pris en compte.

  • Cela peut mal fonctionner avec la mise à l'échelle automatique. Par exemple, si une grande partie du trafic provient d'une seule zone, seuls les points de terminaison attribués à cette zone traiteront ce trafic. Il se peut alors que l'Horizontal Pod Autoscaler ne détecte pas cette situation, ou que les nouveaux pods démarrent dans une autre zone.

Heuristiques personnalisées

Kubernetes est déployé de bien des façons, et aucune heuristique unique d'attribution des points de terminaison aux zones ne convient à tous les cas d'usage. L'un des principaux objectifs de cette fonctionnalité est de permettre le développement d'heuristiques personnalisées lorsque l'heuristique intégrée ne convient pas à votre cas d'usage. Les premiers éléments permettant des heuristiques personnalisées ont été intégrés dans la version 1.27. Il s'agit d'une implémentation limitée, qui ne couvre peut-être pas encore certaines situations pertinentes et plausibles.

A suivre

9 - Allocation des ClusterIP des Services

Dans Kubernetes, les Services sont un moyen abstrait d'exposer une application qui s'exécute sur un ensemble de Pods. Les Services peuvent disposer d'une adresse IP virtuelle à l'échelle du cluster (avec un Service de type: ClusterIP). Les clients peuvent se connecter à cette adresse IP virtuelle, et Kubernetes répartit alors le trafic destiné à ce Service entre les différents Pods sous-jacents.

Comment les ClusterIP des Services sont-elles allouées ?

Lorsque Kubernetes doit attribuer une adresse IP virtuelle à un Service, cette attribution se fait de l'une des deux manières suivantes :

dynamiquement
le plan de contrôle du cluster choisit automatiquement une adresse IP libre dans la plage configurée pour les Services de type: ClusterIP.
statiquement
vous indiquez l'adresse IP de votre choix, dans la plage configurée pour les Services.

Dans tout le cluster, chaque ClusterIP de Service doit être unique. Tenter de créer un Service avec une ClusterIP précise qui a déjà été allouée renvoie une erreur.

Pourquoi réserver des ClusterIP des Services ?

Il peut arriver que vous souhaitiez que des Services soient joignables à des adresses IP bien connues, afin que d'autres composants et utilisateurs du cluster puissent les utiliser.

Le meilleur exemple est le Service DNS du cluster. Par convention informelle, certains outils d'installation de Kubernetes attribuent la 10e adresse IP de la plage des Services au Service DNS. Si vous avez configuré votre cluster avec la plage d'adresses IP des Services 10.96.0.0/16 et que vous voulez que l'adresse IP de votre Service DNS soit 10.96.0.10, il vous faudrait créer un Service comme celui-ci :

apiVersion: v1
kind: Service
metadata:
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: CoreDNS
  name: kube-dns
  namespace: kube-system
spec:
  clusterIP: 10.96.0.10
  ports:
  - name: dns
    port: 53
    protocol: UDP
    targetPort: 53
  - name: dns-tcp
    port: 53
    protocol: TCP
    targetPort: 53
  selector:
    k8s-app: kube-dns
  type: ClusterIP

Mais, comme expliqué précédemment, l'adresse IP 10.96.0.10 n'a pas été réservée. Si d'autres Services sont créés avec une allocation dynamique avant lui ou en même temps que lui, l'un d'eux risque de se voir attribuer cette adresse IP. Vous ne pourrez alors pas créer le Service DNS, car sa création échouera avec une erreur de conflit.

Comment éviter les conflits de ClusterIP des Services ?

La stratégie d'allocation mise en œuvre dans Kubernetes pour attribuer des ClusterIP aux Services réduit le risque de collision.

La plage de ClusterIP est divisée selon la formule min(max(16, cidrSize / 16), 256), que l'on peut décrire ainsi : jamais moins de 16 ni plus de 256, avec une progression graduelle entre les deux.

L'attribution dynamique d'adresses IP puise par défaut dans la bande supérieure ; une fois celle-ci épuisée, elle puise dans la bande inférieure. Les utilisateurs peuvent ainsi recourir à des allocations statiques dans la bande inférieure avec un faible risque de collision.

Exemples

Exemple 1

Cet exemple utilise la plage d'adresses IP 10.96.0.0/24 (notation CIDR) pour les adresses IP des Services.

Taille de la plage : 28 - 2 = 254
Décalage de la bande : min(max(16, 256/16), 256) = min(16, 256) = 16
Début de la bande statique : 10.96.0.1
Fin de la bande statique : 10.96.0.16
Fin de la plage : 10.96.0.254

pie showData
    title 10.96.0.0/24
    "Statique" : 16
    "Dynamique" : 238

Exemple 2

Cet exemple utilise la plage d'adresses IP 10.96.0.0/20 (notation CIDR) pour les adresses IP des Services.

Taille de la plage : 212 - 2 = 4094
Décalage de la bande : min(max(16, 4096/16), 256) = min(256, 256) = 256
Début de la bande statique : 10.96.0.1
Fin de la bande statique : 10.96.1.0
Fin de la plage : 10.96.15.254

pie showData
    title 10.96.0.0/20
    "Statique" : 256
    "Dynamique" : 3838

Exemple 3

Cet exemple utilise la plage d'adresses IP 10.96.0.0/16 (notation CIDR) pour les adresses IP des Services.

Taille de la plage : 216 - 2 = 65534
Décalage de la bande : min(max(16, 65536/16), 256) = min(4096, 256) = 256
Début de la bande statique : 10.96.0.1
Fin de la bande statique : 10.96.1.0
Fin de la plage : 10.96.255.254

pie showData
    title 10.96.0.0/16
    "Statique" : 256
    "Dynamique" : 65278

A suivre

10 - Politique de trafic interne des Services

Si deux Pods de votre cluster veulent communiquer et qu'ils s'exécutent effectivement tous les deux sur le même nœud, utilisez la politique de trafic interne des Services pour que le trafic réseau reste sur ce nœud. Éviter un aller-retour par le réseau du cluster peut contribuer à la fiabilité, aux performances (latence et débit réseau) ou à la réduction des coûts.
Feature state: Stable since Kubernetes v1.26

La politique de trafic interne des Services (Service Internal Traffic Policy) permet de restreindre le trafic interne afin qu'il ne soit acheminé que vers les points de terminaison situés sur le nœud d'où provient ce trafic. Le trafic « interne » désigne ici le trafic émis par les Pods du cluster courant. Cela peut aider à réduire les coûts et à améliorer les performances.

Utiliser la politique de trafic interne des Services

Vous pouvez activer la politique de trafic « interne uniquement » pour un Service en définissant son champ .spec.internalTrafficPolicy sur Local. Cela indique à kube-proxy de n'utiliser que les points de terminaison locaux au nœud pour le trafic interne au cluster.

Note:

Pour les Pods situés sur des nœuds qui n'ont aucun point de terminaison pour un Service donné, le Service se comporte comme s'il n'avait aucun point de terminaison (pour les Pods de ce nœud), même s'il en possède bel et bien sur d'autres nœuds.

L'exemple suivant montre un Service dont le champ .spec.internalTrafficPolicy est défini sur Local :

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app.kubernetes.io/name: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
  internalTrafficPolicy: Local

Fonctionnement

kube-proxy filtre les points de terminaison vers lesquels il achemine le trafic en fonction du paramètre spec.internalTrafficPolicy. Lorsqu'il vaut Local, seuls les points de terminaison locaux au nœud sont pris en compte. Lorsqu'il vaut Cluster (la valeur par défaut) ou qu'il n'est pas défini, Kubernetes prend en compte tous les points de terminaison.

A suivre