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éseauJusqu'à 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 externesSi 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ôteLa 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 NodePortDans 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?
