Réseaux Kubernetes: Ingress

Aujourd'hui, nous publions une traduction de la troisième partie du Guide de mise en réseau Kubernetes. La première partie portait sur les pods, la seconde sur les services, et aujourd'hui nous parlerons de l'équilibrage de charge et des ressources Kubernetes de type Ingress.

Le routage n'est pas l'équilibrage de charge


Dans l'article précédent de cette série, nous avons considéré une configuration composée d'une paire de foyers et d'un service auquel une adresse IP a été affectée appelée «IP de cluster». Des requêtes destinées aux foyers ont été envoyées à cette adresse. Ici, nous continuerons à travailler sur notre système de formation, en commençant là où nous avons obtenu notre diplôme la dernière fois. Rappelons que l'adresse IP de cluster du service, 10.3.241.152 , appartient à une plage d'adresses IP différente de celle utilisée dans le réseau de foyer et de celle utilisée dans le réseau dans lequel les nœuds sont situés. J'ai appelé le réseau défini par cet espace d'adressage «réseau de service», bien qu'il ne mérite guère un nom spécial, car aucun périphérique n'est connecté à ce réseau, et son espace d'adressage, en fait, est entièrement composé de règles de routage. Il a été précédemment démontré comment ce réseau est implémenté sur la base du composant Kubernetes appelé kube-proxy et interagit avec le module netfilter du noyau Linux pour intercepter et rediriger le trafic envoyé vers le cluster IP pour travailler sous.


Schéma de réseau

Jusqu'à présent, nous avons parlé de «connexions» et de «demandes» et nous avons même utilisé le concept difficile à interpréter de «trafic», mais pour comprendre les caractéristiques du mécanisme Kubernetes Ingress, nous devons utiliser des termes plus précis. Ainsi, les connexions et les requêtes fonctionnent au 4ème niveau du modèle OSI (tcp) ou au 7ème niveau (http, rpc, etc.). Les règles Netfilter sont des règles de routage, elles fonctionnent avec des paquets IP au troisième niveau. Tous les routeurs, y compris netfilter, prennent des décisions plus ou moins basées uniquement sur les informations contenues dans le paquet. En général, ils s'intéressent à la provenance et à la destination du paquet. Par conséquent, afin de décrire ce comportement en termes de troisième niveau du modèle OSI, il faut dire que chaque paquet destiné au service situé à 10.3.241.152:80 , qui arrive à l'interface du nœud eth0 , est traité par netfilter, et, conformément à les règles définies pour notre service sont redirigées vers l'adresse IP d'un foyer fonctionnel.

Il semble assez évident que tout mécanisme que nous utilisons pour permettre aux clients externes d'accéder aux pods doit utiliser la même infrastructure de routage. En conséquence, ces clients externes accéderont à l'adresse IP et au port du cluster, car ils sont le «point d'accès» à tous les mécanismes dont nous avons parlé jusqu'à présent. Ils nous permettent de ne pas nous soucier de l'endroit exact où il est exécuté à un certain moment. Cependant, il n'est pas du tout évident de savoir comment le faire fonctionner.

Le service IP de cluster est accessible uniquement avec l'interface Ethernet du nœud. Rien en dehors du cluster ne sait quoi faire des adresses de la plage à laquelle appartient cette adresse. Comment puis-je rediriger le trafic d'une adresse IP publique vers une adresse accessible uniquement si le paquet est déjà arrivé sur l'hôte?

Si nous essayons de trouver une solution à ce problème, alors l'une des choses qui peuvent être faites dans le processus de recherche d'une solution sera l'étude des règles de netfilter en utilisant l'utilitaire iptables . Si vous faites cela, vous pouvez découvrir quelque chose qui, à première vue, peut sembler inhabituel: les règles du service ne sont pas limitées à un réseau source spécifique. Cela signifie que tous les paquets générés n'importe où qui arrivent sur l'interface Ethernet du nœud et ont une adresse de destination de 10.3.241.152:80 seront reconnus comme conformes à la règle et seront redirigés vers le sous-marin. Pouvons-nous simplement donner aux clients un cluster IP, peut-être en le liant à un nom de domaine approprié, puis définir un itinéraire qui nous permet d'organiser la livraison de ces paquets à l'un des nœuds?


Client et cluster externes

Si tout est configuré de cette façon, une telle conception s'avérera efficace. Les clients accèdent à l'IP du cluster, les paquets suivent la route menant à l'hôte, puis ils sont redirigés vers le bas. En ce moment, il peut vous sembler qu'une telle solution peut être limitée, mais elle souffre de graves problèmes. Le premier est que les nœuds, en fait, le concept d'éphémère, ils ne sont pas particulièrement différents à cet égard des foyers. Ils sont, bien sûr, un peu plus proches du monde matériel que les pods, mais ils peuvent migrer vers de nouvelles machines virtuelles, les clusters peuvent évoluer vers le haut ou vers le bas, etc. Les routeurs fonctionnent au troisième niveau du modèle OSI et les paquets ne peuvent pas faire la distinction entre les services qui fonctionnent normalement et ceux qui ne fonctionnent pas correctement. Ils s'attendent à ce que la prochaine transition sur l'itinéraire soit accessible et stable. Si le nœud est inaccessible, l'itinéraire deviendra inopérant et le restera, dans la plupart des cas, beaucoup de temps. Même si la route résiste aux pannes, un tel schéma entraînera le fait que tout le trafic externe passera par un seul nœud, ce qui n'est probablement pas optimal.

Quelle que soit la façon dont nous apportons le trafic client au système, nous devons le faire pour qu'il ne dépende pas de l'état d'un nœud de cluster unique. Et, en fait, il n'y a aucun moyen fiable de le faire en utilisant uniquement le routage, sans certains moyens de gérer activement le routeur. En fait, c'est précisément ce rôle, le rôle du système de contrôle, que kube-proxy joue par rapport à netfilter. Étendre la responsabilité de Kubernetes à la gestion d'un routeur externe n'avait probablement pas beaucoup de sens pour les architectes système, d'autant plus que nous avons déjà des outils éprouvés pour distribuer le trafic client sur plusieurs serveurs. Ils sont appelés équilibreurs de charge, et il n'est pas surprenant qu'ils soient la solution vraiment fiable pour Kubernetes Ingress. Afin de comprendre exactement comment cela se produit, nous devons sortir du troisième niveau de l'OSI et parler à nouveau des connexions.

Afin d'utiliser l'équilibreur de charge pour répartir le trafic client entre les nœuds de cluster, nous avons besoin d'une adresse IP publique à laquelle les clients peuvent se connecter, et nous avons également besoin des adresses des nœuds eux-mêmes vers lesquels l'équilibreur de charge peut rediriger les demandes. Pour les raisons ci-dessus, nous ne pouvons pas simplement créer une route statique stable entre le routeur de passerelle et les nœuds à l'aide d'un réseau basé sur les services (cluster IP).

Parmi les autres adresses avec lesquelles vous pouvez travailler, seules les adresses du réseau auquel sont connectées les interfaces Ethernet des nœuds, c'est-à-dire, dans cet exemple, 10.100.0.0/24 , peuvent être notées. Le routeur sait déjà comment transférer les paquets vers ces interfaces, et les connexions envoyées de l'équilibreur de charge au routeur iront là où elles devraient aller. Mais si le client souhaite se connecter à notre service sur le port 80, nous ne pouvons pas simplement envoyer des paquets à ce port sur les interfaces réseau des nœuds.


Équilibreur de charge, tentative infructueuse d'accéder au port 80 de l'interface réseau hôte

La raison pour laquelle cela ne peut pas être fait est tout à fait évidente. À savoir, nous parlons du fait qu'il n'y a pas de processus en attente de connexions à 10.100.0.3:80 (et s'il y en a, ce n'est certainement pas le même processus), et les règles de netfilter, qui, comme nous l'espérions, intercepteraient la demande et ils le lui enverront, ils ne travailleront pas à cette adresse de destination. Ils ne répondent qu'à un réseau IP de cluster basé sur des services, c'est-à-dire à l'adresse 10.3.241.152:80 . En conséquence, ces paquets, à leur arrivée, ne peuvent pas être livrés à l'adresse de destination, et le noyau émettra une réponse ECONNREFUSED . Cela nous place dans une position déroutante: il n'est pas facile de travailler avec le réseau pour la redirection de paquets vers laquelle netfilter est configuré lors de la redirection des données de la passerelle vers les nœuds, et un réseau pour lequel le routage est facile à configurer n'est pas le réseau vers lequel netfilter redirige les paquets. Afin de résoudre ce problème, vous pouvez créer un pont entre ces réseaux. C'est exactement ce que fait Kubernetes en utilisant un service comme NodePort.

Des services comme NodePort


Le service que nous avons, par exemple, créé dans l'article précédent, n'a pas de type, il a donc adopté le type par défaut - ClusterIP . Il existe deux autres types de services qui diffèrent par des fonctionnalités supplémentaires, et celui qui nous intéresse actuellement est NodePort . Voici un exemple de description d'un service de ce type:

 kind: Service apiVersion: v1 metadata: name: service-test spec: type: NodePort selector:   app: service_test_pod ports: - port: 80   targetPort: http 

Les services de type NodePort sont des services de type ClusterIP qui ont une fonctionnalité supplémentaire: leur accès peut être obtenu à la fois par l'adresse IP attribuée à l'hôte et par l'adresse affectée au cluster dans le réseau de services. Ceci est réalisé de manière assez simple: lorsque Kubernetes crée un service NodePort, kube-proxy alloue un port dans la plage 30000-32767 et ouvre ce port sur l'interface eth0 de chaque nœud (d'où le nom du type de service - NodePort ). Les connexions établies à ce port (nous appellerons ces ports NodePort ) sont redirigées vers l'IP du cluster du service. Si nous créons le service décrit ci-dessus et kubectl get svc service-test la commande kubectl get svc service-test , nous pouvons voir le port qui lui est attribué.

 $ kubectl get svc service-test NAME           CLUSTER-IP EXTERNAL-IP   PORT(S) AGE service-test   10.3.241.152 <none>        80:32213/TCP 1m 

Dans ce cas, le service reçoit le NodePort 32213 . Cela signifie que nous pouvons maintenant nous connecter au service via n'importe quel nœud de notre cluster expérimental à 10.100.0.2:32213 ou à 10.100.0.3:32213 . Dans ce cas, le trafic sera redirigé vers le service.

Une fois que cette partie du système a pris sa place, nous avons tous les fragments du pipeline pour équilibrer la charge créée par les demandes des clients vers tous les nœuds du cluster.


Service NodePort

Dans la figure précédente, le client se connecte à l'équilibreur de charge via une adresse IP publique, l'équilibreur de charge sélectionne le nœud et s'y connecte à 10.100.0.3:32213 , kube-proxy accepte cette connexion et la redirige vers le service accessible via le cluster IP 10.3.241.152:80 . Ici, la demande est traitée avec succès selon les règles définies par netfilter et est redirigée vers le module serveur à l'adresse 10.0.2.2:8080 . Peut-être que tout cela peut sembler un peu compliqué, et dans une certaine mesure, mais il n'est pas facile de trouver une solution plus simple qui prend en charge toutes les merveilleuses fonctionnalités qui nous donnent des pods et des réseaux basés sur des services.

Ce mécanisme n'est cependant pas sans poser ses propres problèmes. L'utilisation de services tels que NodePort permet aux clients d'accéder aux services à l'aide d'un port non standard. Souvent, ce n'est pas un problème, car l'équilibreur de charge peut leur fournir un port normal et masquer NodePort aux utilisateurs finaux. Mais dans certains scénarios, par exemple, lors de l'utilisation d'un équilibreur de charge de plateforme Google Cloud externe, il peut être nécessaire de déployer NodePort clients. Il convient de noter que ces ports, en outre, représentent des ressources limitées, bien que 2768 ports soient probablement suffisants même pour les plus grands clusters. Dans la plupart des cas, vous pouvez laisser Kubernetes sélectionner des numéros de port au hasard, mais vous pouvez les définir vous-même si nécessaire. Un autre problème concerne certaines limitations concernant le stockage des adresses IP source dans les requêtes. Pour savoir comment résoudre ces problèmes, vous pouvez vous référer à ce matériel dans la documentation de Kubernetes.

Ports NodePorts est le mécanisme fondamental par lequel tout le trafic externe entre dans le cluster Kubernetes. Cependant, eux-mêmes ne nous présentent pas de solution toute faite. Pour les raisons ci-dessus, avant le cluster, que les clients soient des entités internes ou externes situées dans un réseau public, il est toujours nécessaire d'avoir une sorte d'équilibreur de charge.

Les architectes de la plate-forme, réalisant cela, ont fourni deux façons de configurer l'équilibreur de charge à partir de la plate-forme Kubernetes elle-même. Discutons-en.

Des services tels que LoadBalancer et des ressources de type Ingress


Des services comme LoadBalancer et des ressources de type Ingress sont parmi les mécanismes Kubernetes les plus complexes. Cependant, nous n'y consacrerons pas trop de temps, car leur utilisation n'entraîne pas de changements fondamentaux dans tout ce dont nous avons parlé jusqu'à présent. Tout le trafic externe, comme précédemment, entre dans le cluster via NodePort .

Les architectes pourraient s'arrêter là, permettant à ceux qui créent des clusters de se soucier uniquement des adresses IP publiques et des équilibreurs de charge. En fait, dans certaines situations, comme le démarrage d'un cluster sur des serveurs réguliers ou à domicile, c'est exactement ce qu'ils font. Mais dans les environnements qui prennent en charge les configurations de ressources réseau contrôlées par l'API, Kubernetes vous permet de configurer tout ce dont vous avez besoin en un seul endroit.

La première approche pour résoudre ce problème, la plus simple, consiste à utiliser les services Kubernetes tels que LoadBalancer . Ces services ont toutes les capacités de services tels que NodePort et, en outre, ont la possibilité de créer des chemins d'accès complets pour le trafic entrant, en supposant que le cluster s'exécute dans des environnements tels que GCP ou AWS, qui prennent en charge la configuration des ressources réseau via l'API.

 kind: Service apiVersion: v1 metadata: name: service-test spec: type: LoadBalancer selector:   app: service_test_pod ports: - port: 80   targetPort: http 

Si nous supprimons et recréons le service de notre exemple dans le moteur Google Kubernetes, peu de temps après, à l'aide de la commande kubectl get svc service-test , nous pouvons vérifier que l'IP externe est attribuée.

 $ kubectl get svc service-test NAME      CLUSTER-IP      EXTERNAL-IP PORT(S)          AGE openvpn   10.3.241.52     35.184.97.156 80:32213/TCP     5m 

Il est dit ci-dessus que nous pourrons vérifier le fait d'attribuer une adresse IP externe "bientôt", malgré le fait que l'attribution d'une adresse IP externe puisse prendre plusieurs minutes, ce qui n'est pas surprenant étant donné la quantité de ressources qui doivent être portées à un état sain. Sur la plate-forme GCP, par exemple, cela nécessite que le système crée une adresse IP externe, des règles de redirection du trafic, un serveur proxy cible, un service principal et, éventuellement, une instance de groupe. Après avoir attribué une adresse IP externe, vous pouvez vous connecter au service via cette adresse, lui attribuer un nom de domaine et informer les clients. Jusqu'à ce que le service soit détruit et recréé (pour ce faire, rarement lorsqu'il y a une bonne raison), l'adresse IP ne changera pas.

Des services comme LoadBalancer ont certaines limitations. Un tel service ne peut pas être configuré pour déchiffrer le trafic HTTPS. Vous ne pouvez pas créer d'hôtes virtuels ou configurer le routage en fonction de chemins, vous ne pouvez donc pas, à l'aide de configurations pratiques, utiliser un seul équilibreur de charge avec de nombreux services. Ces limitations ont conduit à l'introduction de Kubernetes 1.1. Une ressource spéciale pour configurer les équilibreurs de charge. Il s'agit d'une ressource de type Ingress . Des services tels que LoadBalancer visent à étendre les capacités d'un service unique pour prendre en charge des clients externes. En revanche, les ressources Ingress sont des ressources spéciales qui vous permettent de configurer de manière flexible les équilibreurs de charge. L'API Ingress prend en charge le déchiffrement du trafic TLS, des hôtes virtuels et du routage basé sur le chemin. À l'aide de cette API, l'équilibreur de charge peut facilement être configuré pour fonctionner avec plusieurs services principaux.

L'API de ressource de type Ingress est trop volumineuse pour discuter de ses fonctionnalités ici; en outre, elle n'affecte pas particulièrement le fonctionnement des ressources Ingress au niveau du réseau. L'implémentation de cette ressource suit le modèle Kubernetes habituel: il existe un type de ressource et un contrôleur pour contrôler ce type. Dans ce cas, la ressource est la ressource Ingress , qui décrit les demandes de ressources réseau. Voici à quoi pourrait ressembler la description d'une ressource Ingress .

 apiVersion: extensions/v1beta1 kind: Ingress metadata: name: test-ingress annotations:   kubernetes.io/ingress.class: "gce" spec: tls:   - secretName: my-ssl-secret rules: - host: testhost.com   http:     paths:     - path: /*       backend:         serviceName: service-test         servicePort: 80 

Le contrôleur Ingress est responsable de l'exécution de ces demandes en amenant les autres ressources à l'état souhaité. Lorsque vous utilisez Ingress, des services tels que NodePort sont créés, après quoi le contrôleur Ingress est autorisé à prendre des décisions sur la façon de diriger le trafic vers les nœuds. Il existe une implémentation du contrôleur Ingress pour les équilibreurs de charge GCE, pour les équilibreurs AWS, pour les serveurs proxy populaires tels que nginx et haproxy. Notez que le mélange de ressources et de services Ingress comme LoadBalancer peut provoquer des problèmes mineurs dans certains environnements. Ils sont faciles à manipuler, mais, en général, il est préférable d’utiliser Ingress même pour des services simples.

HostPort et HostNetwork


Ce dont nous allons parler maintenant, à savoir HostPort et HostNetwork , peut être attribué plutôt à la catégorie des raretés intéressantes et non à des outils utiles. En fait, je m'engage à affirmer que dans 99,99% des cas leur utilisation peut être considérée comme anti-pattern, et tout système dans lequel ils sont utilisés doit subir une vérification obligatoire de son architecture.

Je pensais que cela ne valait pas la peine d’en parler, mais c’est quelque chose comme les outils utilisés par les ressources Ingress pour traiter le trafic entrant, alors j’ai décidé qu’il valait la peine de les mentionner, du moins brièvement.

Parlons d' HostPort de HostPort . Il s'agit d'une propriété de conteneur (déclarée dans la structure ContainerPort ). Lorsqu'un certain numéro de port y est écrit, cela conduit à l'ouverture de ce port sur le nœud et à sa redirection directement vers le conteneur. Il n'y a aucun mécanisme de proxy et le port s'ouvre uniquement sur les nœuds sur lesquels le conteneur s'exécute. Au début de la plate-forme, avant que les mécanismes DaemonSet et StatefulSet n'y apparaissent, HostPort était une astuce qui permettait de lancer un seul conteneur d'un certain type sur n'importe quel nœud. Par exemple, j'ai déjà utilisé cela pour créer un cluster Elasticsearch en définissant HostPort sur 9200 et en spécifiant autant de répliques qu'il y avait de nœuds. , Kubernetes, - HostPort .

NostNetwork , , Kubernetes , HostPort . true , - network=host docker run . , . eth0 . , . , , , Kubernetes, - .

Résumé


Kubernetes, , Ingress. , , , Kubernetes.

Chers lecteurs! Ingress?

Source: https://habr.com/ru/post/fr442646/


All Articles