Conteneurs, microservices et maillages de service

Il y a des tonnes d' articles sur le maillage des services sur Internet, et en voici un autre. Hourra! Mais pourquoi? Ensuite, ce que je veux exprimer, c'est qu'il serait préférable que les maillages de service apparaissent il y a 10 ans, avant l'émergence de plates-formes de conteneurs telles que Docker et Kubernetes. Je ne prétends pas que mon point de vue est meilleur ou pire que les autres, mais comme les mailles de service sont des animaux assez complexes, la multiplicité des points de vue aidera à mieux les comprendre.

Je vais parler de la plate-forme dotCloud, qui a été construite sur plus d'une centaine de microservices et a pris en charge des milliers d'applications dans des conteneurs. Je vais vous expliquer les problèmes que nous avons rencontrés lors de son développement et de son lancement, et comment les maillages de service peuvent aider (ou pas).

Histoire de dotCloud


J'ai déjà écrit sur l'histoire de dotCloud et le choix de l'architecture de cette plate-forme, mais j'ai parlé un peu du niveau du réseau. Si vous ne voulez pas vous plonger dans la lecture de l' article précédent sur dotCloud, voici un bref résumé: c'est une plate-forme PaaS en tant que service qui permet aux clients de lancer une large gamme d'applications (Java, PHP, Python ...), avec la prise en charge d'une large gamme de services de données (MongoDB, MySQL, Redis ...) et un workflow comme Heroku: vous téléchargez votre code sur la plateforme, il construit des images de conteneurs et les déploie.

Je vais vous expliquer comment le trafic a été dirigé vers la plate-forme dotCloud. Non pas parce qu'il était particulièrement cool (bien que le système ait bien fonctionné pour l'époque!), Mais principalement parce qu'avec l'aide d'outils modernes, une telle conception peut facilement être mise en œuvre en peu de temps par une équipe modeste si elle a besoin d'un moyen d'acheminer le trafic entre un tas de microservices ou un tas d'applications. Ainsi, vous pouvez comparer les options: que se passe-t-il si vous développez tout vous-même ou utilisez le maillage de service existant. Choix standard: faites-le vous-même ou achetez.

Routage du trafic pour les applications hébergées


Les applications DotCloud peuvent fournir des points de terminaison HTTP et TCP.

Les points de terminaison HTTP sont ajoutés dynamiquement à la configuration du cluster d'équilibrage de charge Hipache . Ceci est similaire à ce que font les ressources Kubernetes Ingress et un équilibreur de charge comme Traefik aujourd'hui .

Les clients se connectent aux points de terminaison HTTP via leurs domaines respectifs, à condition que le nom de domaine pointe vers des équilibreurs de charge dotCloud. Rien de spécial.

Les points de terminaison TCP sont associés à un numéro de port, qui est ensuite transmis à tous les conteneurs de cette pile via des variables d'environnement.

Les clients peuvent se connecter aux points de terminaison TCP en utilisant le nom d'hôte approprié (quelque chose comme gateway-X.dotcloud.com) et le numéro de port.

Ce nom d'hôte se résout en cluster de serveurs «nats» (non lié à NATS ), qui acheminera les connexions TCP entrantes vers le conteneur approprié (ou, dans le cas de services à charge équilibrée, vers les conteneurs appropriés).

Si vous connaissez Kubernetes, cela vous rappellera probablement les services NodePort .

Il n'y avait pas d'équivalent des services ClusterIP sur la plate - forme dotCloud : pour simplifier, l'accès aux services était le même de l'intérieur et de l'extérieur de la plate-forme.

Tout était organisé tout simplement: les implémentations initiales des réseaux de routage HTTP et TCP, probablement quelques centaines de lignes de Python. Algorithmes simples (je dirais naïfs) qui ont été finalisés avec la croissance de la plate-forme et l'avènement d'exigences supplémentaires.

Une refactorisation approfondie du code existant n'était pas nécessaire. En particulier, les applications à 12 facteurs peuvent utiliser directement l'adresse obtenue via les variables d'environnement.

En quoi cela diffère-t-il d'un maillage de service moderne?


Visibilité limitée. Nous n'avions généralement pas de métriques pour la grille de routage TCP. En ce qui concerne le routage HTTP, les versions ultérieures ont des métriques HTTP détaillées avec des codes d'erreur et des temps de réponse, mais les maillages de service modernes vont encore plus loin, offrant une intégration avec des systèmes de collecte de métriques comme Prometheus, par exemple.

La visibilité est importante non seulement d'un point de vue opérationnel (pour aider à résoudre les problèmes), mais aussi lorsque de nouvelles fonctionnalités sont publiées. Il s'agit d'un déploiement bleu-vert sûr et d'un déploiement de canaris .

L'efficacité du routage est également limitée. Dans la grille de routage dotCloud, tout le trafic devait passer par un cluster de nœuds de routage dédiés. Cela signifiait un franchissement potentiel de plusieurs frontières AZ (zones d'accessibilité) et une augmentation significative des retards. Je me souviens comment j'ai résolu des problèmes avec du code qui faisait plus d'une centaine de requêtes SQL par page et pour chaque requête, j'ouvrais une nouvelle connexion au serveur SQL. Lorsqu'elle est lancée localement, la page se charge instantanément, mais dans dotCloud, le chargement prend quelques secondes, car cela prend des dizaines de millisecondes pour chaque connexion TCP (et la requête SQL suivante). Dans ce cas particulier, les connexions persistantes ont résolu le problème.

Les maillages de service modernes font mieux avec de tels problèmes. Tout d'abord, ils vérifient que les connexions sont acheminées à la source . Le flux logique est le même: → → , mais maintenant le mesh fonctionne localement et non sur des nœuds distants, donc la connexion est locale et très rapide (microsecondes au lieu de millisecondes).

Les maillages de service modernes implémentent également des algorithmes d'équilibrage de charge plus intelligents. En contrôlant les performances des backends, ils peuvent envoyer plus de trafic vers des backends plus rapides, ce qui entraîne une augmentation des performances globales.

La sécurité est également meilleure. La grille de routage dotCloud a fonctionné complètement sur EC2 Classic et n'a pas chiffré le trafic (en supposant que si quelqu'un a réussi à mettre un renifleur sur le trafic réseau EC2, vous avez déjà de gros problèmes). Les maillages de service modernes protègent de manière transparente tout notre trafic, par exemple, avec l'authentification TLS mutuelle et le cryptage ultérieur.

Routage du trafic pour les services de plate-forme


Ok, nous avons discuté du trafic entre les applications, mais qu'en est-il de la plate-forme dotCloud elle-même?

La plateforme elle-même était constituée d'une centaine de microservices chargés de différentes fonctions. Certains ont reçu des demandes d'autres, et certains étaient des travailleurs de fond qui se connectaient à d'autres services mais n'acceptaient pas les connexions. Dans tous les cas, chaque service doit connaître les noeuds finaux des adresses auxquelles il est nécessaire de se connecter.

De nombreux services de haut niveau peuvent utiliser la grille de routage décrite ci-dessus. En fait, bon nombre des centaines de microservices dotCloud ont été déployés en tant qu'applications régulières sur la plate-forme dotCloud elle-même. Mais un petit nombre de services de bas niveau (en particulier, qui implémentent cette grille de routage) avaient besoin de quelque chose de plus simple, avec moins de dépendances (car ils ne pouvaient pas dépendre d'eux-mêmes pour le travail - un bon vieux problème de poule et d'oeuf).

Ces services importants de bas niveau ont été déployés en exécutant des conteneurs directement sur plusieurs nœuds clés. Dans le même temps, les services de plate-forme standard n'étaient pas impliqués: l'éditeur de liens, le planificateur et le coureur. Si vous voulez comparer avec les plates-formes de conteneurs modernes, c'est comme lancer un plan de contrôle avec docker run directement sur les nœuds, au lieu de déléguer la tâche Kubernetes. Ceci est assez similaire au concept de modules statiques (foyers) que kubeadm ou bootkube utilise lors du chargement d'un cluster autonome.

Ces services ont été exposés de manière simple et grossière: leurs noms et adresses ont été répertoriés dans le fichier YAML; et chaque client devait prendre une copie de ce fichier YAML pour le déploiement.

D'une part, il est extrêmement fiable, car il ne nécessite pas de prise en charge d'un stockage de clé / valeur externe tel que Zookeeper (n'oubliez pas, à ce moment-là, etcd ou Consul n'existaient pas encore). D'un autre côté, cela a rendu difficile le transfert de services. À chaque déplacement, tous les clients doivent avoir reçu un fichier YAML mis à jour (et potentiellement redémarrer). Pas très pratique!

Par la suite, nous avons commencé à introduire un nouveau schéma, où chaque client se connectait à un serveur proxy local. Au lieu de l'adresse et du port, il lui suffit de connaître uniquement le numéro de port du service et de se connecter via localhost . Le serveur proxy local traite cette connexion et l'achemine vers le serveur réel. Maintenant, lorsque vous déplacez le backend vers une autre machine ou que vous faites évoluer au lieu de mettre à jour tous les clients, vous devez mettre à jour uniquement tous ces proxys locaux; et un redémarrage n'est plus nécessaire.

(Il était également prévu d'encapsuler le trafic dans les connexions TLS et de placer un autre serveur proxy du côté de la réception, ainsi que de vérifier les certificats TLS sans la participation du service de réception, qui est configuré pour accepter les connexions uniquement sur l' localhost . Plus d'informations à ce sujet plus tard).

Ceci est très similaire au SmartStack d'Airbnb, mais la différence significative est que SmartStack est implémenté et déployé en production, tandis que le système de routage interne dotCloud a été mis dans une boîte lorsque dotCloud est devenu Docker.

Personnellement, je considère SmartStack comme l'un des prédécesseurs de systèmes tels que Istio, Linkerd et Consul Connect, car ils suivent tous le même modèle:

  • Exécution de proxys sur chaque nœud.
  • Les clients se connectent au proxy.
  • Le plan de gestion met à jour la configuration du proxy lors du changement de backends.
  • ... Profit!

Implémentation moderne d'un maillage de service


Si nous devons mettre en œuvre une grille similaire aujourd'hui, nous pouvons utiliser des principes similaires. Par exemple, configurez la zone DNS interne en mappant les noms de service aux adresses dans 127.0.0.0/8 . Exécutez ensuite HAProxy sur chaque nœud du cluster, en acceptant les connexions à chaque adresse de service ( 127.0.0.0/8 dans ce sous-réseau) et en redirigeant / équilibrant la charge vers les backends correspondants. La configuration HAProxy peut être contrôlée par confd , vous permettant de stocker des informations de backend dans etcd ou Consul et de pousser automatiquement la configuration mise à jour vers HAProxy si nécessaire.

Voilà comment Istio fonctionne! Mais avec quelques différences:

  • Utilise Envoy Proxy au lieu de HAProxy.
  • Enregistre la configuration du backend via l'API Kubernetes au lieu de etcd ou Consul.
  • Les services se voient allouer des adresses sur le sous-réseau interne (adresses Kubernetes ClusterIP) au lieu de 127.0.0.0/8.
  • Il dispose d'un composant facultatif (Citadel) pour ajouter une authentification TLS mutuelle entre le client et les serveurs.
  • Prend en charge de nouvelles fonctionnalités telles que la coupure de circuit, le traçage distribué, le déploiement de canaris, etc.

Jetons un coup d'œil à certaines des différences.

Envoy proxy


Enftoy Proxy a été écrit par Lyft [concurrent Uber sur le marché des taxis - env. trans.]. Il est très similaire aux autres proxys à bien des égards (par exemple, HAProxy, Nginx, Traefik ...), mais Lyft a écrit la sienne car ils avaient besoin de fonctions qui ne sont pas dans d'autres proxys, et il semblait plus raisonnable d'en créer une nouvelle que de développer celle existante.

Envoy peut être utilisé seul. Si j'ai un service spécifique qui doit se connecter à d'autres services, je peux le configurer pour se connecter à Envoy, puis configurer et reconfigurer dynamiquement Envoy avec l'emplacement d'autres services, tout en recevant de nombreuses excellentes fonctionnalités supplémentaires, par exemple, la visibilité. Au lieu d'une bibliothèque cliente personnalisée ou de l'intégration du suivi des appels dans le code, nous dirigeons le trafic vers Envoy, et il recueille des métriques pour nous.

Mais Envoy est également capable de fonctionner comme un plan de données pour un maillage de service. Cela signifie que pour ce maillage de service, Envoy est maintenant configuré par le plan de contrôle.

Avion de contrôle


Dans le plan de gestion, Istio s'appuie sur l'API Kubernetes. Ce n'est pas très différent de l'utilisation de confd , qui s'appuie sur etcd ou Consul pour afficher un ensemble de clés dans un entrepôt de données. Istio, via l'API Kubernetes, affiche l'ensemble de ressources Kubernetes.

Entre les deux : j'ai personnellement trouvé cette description de l'API Kubernetes utile , qui se lit comme suit:

Le serveur API Kubernetes est un «serveur stupide» qui offre le stockage, la gestion des versions, la validation, la mise à jour et la sémantique des ressources API.

Istio est conçu pour fonctionner avec Kubernetes; et si vous souhaitez l'utiliser en dehors de Kubernetes, vous devez exécuter une instance du serveur API Kubernetes (et le service auxiliaire, etc.).

Adresses de service


Istio s'appuie sur les adresses ClusterIP que Kubernetes alloue, donc les services Istio obtiennent une adresse interne (pas dans la plage 127.0.0.0/8 ).

Le trafic vers l'adresse ClusterIP pour un service spécifique dans le cluster Kubernetes sans Istio est intercepté par kube-proxy et envoyé à la partie serveur de ce proxy. Si vous êtes intéressé par les détails techniques, alors kube-proxy définit les règles iptables (ou les équilibreurs de charge IPVS, selon la façon dont vous les configurez) pour réécrire les adresses IP de destination des connexions allant à l'adresse ClusterIP.

Après avoir installé Istio dans le cluster Kubernetes, rien ne change jusqu'à ce qu'il soit explicitement activé pour le consommateur donné ou même l'espace de noms entier en introduisant le conteneur sidecar dans des foyers personnalisés. Ce conteneur démarrera une instance d'Envoy et définira une série de règles iptables pour intercepter le trafic vers d'autres services et rediriger ce trafic vers Envoy.

Lorsqu'il est intégré à Kubernetes DNS, cela signifie que notre code peut se connecter par le nom du service, et tout "fonctionne simplement". En d'autres termes, notre code émet des requêtes comme http://api/v1/users/4242 , puis api résout la requête en 10.97.105.48 , les règles iptables interceptent les connexions de 10.97.105.48 et les redirigent vers le proxy Envoy local, et ce proxy local dirigera demande de l'API backend réelle. Fuh!

Petits trucs supplémentaires


Istio fournit également un chiffrement et une authentification de bout en bout via mTLS (Mutual TLS). Le composant appelé Citadelle en est responsable.

Il existe également un composant Mixer qu'Envoy peut demander pour chaque demande afin de prendre une décision spéciale à propos de cette demande, en fonction de divers facteurs, tels que les en-têtes, le chargement du backend, etc. ... (ne vous inquiétez pas: il existe de nombreuses façons de s'assurer que le Mixer fonctionne, et même s'il se bloque, Envoy continuera de fonctionner normalement en tant que proxy).

Et, bien sûr, nous avons mentionné la visibilité: Envoy collecte un grand nombre de métriques, tout en fournissant un suivi distribué. Dans l'architecture des microservices, si une demande d'API doit passer par les microservices A, B, C et D, puis lorsque vous vous connectez au système, la trace distribuée ajoutera un identifiant unique à la demande et enregistrera cet identifiant via des sous-requêtes à tous ces microservices, vous permettant d'enregistrer tous les appels associés, leurs retards, etc.

Développer ou acheter


Istio a la réputation d'être un système complexe. En revanche, la construction d'une grille de routage, que j'ai décrite au début de cet article, est relativement simple à l'aide des outils existants. Alors, est-il judicieux de créer votre propre maillage de service à la place?

Si nous avons des besoins modestes (vous n'avez pas besoin de visibilité, d'un disjoncteur et d'autres subtilités), alors des réflexions viennent sur le développement de votre propre outil. Mais si nous utilisons Kubernetes, cela peut même ne pas être nécessaire, car Kubernetes fournit déjà des outils de base pour la découverte de services et l'équilibrage de charge.

Mais si nous avons des exigences avancées, «acheter» un maillage de service semble être une bien meilleure option. (Ce n'est pas toujours un «achat», car Istio est livré avec du code open source, mais nous devons encore investir du temps d'ingénierie pour comprendre son travail, le déployer et le gérer).

Que choisir: Istio, Linkerd ou Consul Connect?


Jusqu'à présent, nous n'avons parlé que d'Istio, mais ce n'est pas le seul maillage de service. Linkerd est une alternative populaire et il existe également Consul Connect .

Que choisir?

Honnêtement, je ne sais pas. Pour le moment, je ne me considère pas assez compétent pour répondre à cette question. Il existe des articles intéressants comparant ces outils et même des références .

Une approche prometteuse consiste à utiliser un outil comme SuperGloo . Il implémente une couche d'abstraction pour simplifier et unifier les API fournies par les maillages de service. Au lieu d'étudier des API spécifiques (et, à mon avis, relativement complexes) de divers maillages de service, nous pouvons utiliser des constructions SuperGloo plus simples - et passer facilement de l'une à l'autre, comme si nous avions un format de configuration intermédiaire qui décrit les interfaces HTTP et backends capables de générer la configuration réelle pour Nginx, HAProxy, Traefik, Apache ...

Je me suis un peu laissé aller à Istio et SuperGloo, et dans l'article suivant, je veux montrer comment ajouter Istio ou Linkerd à un cluster existant à l'aide de SuperGloo, et dans quelle mesure ce dernier fera face à son travail, c'est-à-dire qu'il vous permet de passer d'un maillage de service à un autre sans réécrire les configurations.

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


All Articles