Skip to content

Blog


Rester dans la zone : Comment DoorDash a utilisé un maillage de services pour gérer le transfert de données, en réduisant les sauts et les dépenses en nuage.

16 janvier 2024

|
Hochuen Wong

Hochuen Wong

Levon Stepanian

Levon Stepanian

L'évolution de DoorDash d'une architecture d'application monolithique vers une architecture basée sur des cellules et des microservices a apporté de nombreux avantages. La nouvelle architecture a permis de réduire le temps nécessaire au développement, aux tests et au déploiement, tout en améliorant l'évolutivité et la résilience pour les utilisateurs finaux, notamment les commerçants, les Dashers et les consommateurs. Cependant, avec l'augmentation du nombre de microservices et de backends, DoorDash a observé une hausse des coûts de transfert de données entre les zones de disponibilité (AZ). Ces coûts de transfert de données - encourus à la fois à l'envoi et à la réception - permettent à DoorDash de fournir à ses utilisateurs finaux un service hautement disponible qui peut résister aux dégradations d'une ou plusieurs zones de disponibilité.

L'augmentation des coûts a incité notre équipe d'ingénieurs à rechercher d'autres moyens de fournir le même niveau de service de manière plus efficace. Dans cet article de blog, nous décrivons le parcours de DoorDash en utilisant un maillage de services pour réaliser des économies sur les coûts de transfert de données sans sacrifier la qualité du service.

Architecture du trafic DoorDash

Avant de nous plonger dans notre solution, examinons l'infrastructure de trafic de DoorDash.

Architecture à base de cellules : Comme le montre la figure 1 ci-dessous, notre architecture de trafic suit une conception basée sur les cellules. Tous les pods de microservices sont déployés dans plusieurs cellules isolées. Chaque service a un déploiement Kubernetes par cellule. Pour garantir l'isolement entre les cellules, le trafic intercellulaire n'est pas autorisé. Cette approche nous permet de réduire le rayon d'action d'une défaillance d'une seule cellule. Pour les services uniques ou ceux qui n'ont pas migré vers l'architecture cellulaire, le déploiement s'effectue dans une seule cellule globale. Des routeurs Envoy internes permettent la communication entre la cellule globale et les cellules répliquées.

Déploiement de clusters Kubernetes : Chaque cellule est constituée de plusieurs clusters Kubernetes ; chaque microservice est déployé exclusivement sur un cluster au sein d'une cellule donnée. Cette conception garantit l'évolutivité et la fiabilité tout en s'alignant sur notre architecture basée sur les cellules.

Figure 1 : Déploiement de grappes multiples basées sur des cellules

Haute disponibilité: Pour améliorer la disponibilité et la tolérance aux pannes, chaque cluster Kubernetes est déployé sur plusieurs AZ. Cette pratique minimise les perturbations causées par une panne d'une ou plusieurs ZA.

Communication directe dans un réseau plat : Grâce à AWS-CNI, les pods de microservices situés dans des groupes distincts au sein d'une cellule peuvent communiquer directement les uns avec les autres. Cette architecture de réseau plat rationalise les voies de communication, ce qui facilite les interactions efficaces entre les microservices.

Découverte de services multi-cluster personnalisés : Notre solution personnalisée de découverte de services, DoorDash data center Service Discovery, ou DDSD, fournit un domaine DNS personnalisé pour prendre en charge la communication multi-cluster. Les clients s'appuient sur les noms DNS pour découvrir dynamiquement toutes les adresses IP de pods des services souhaités. La fonctionnalité de DDSD est similaire aux services sans tête de Kubernetes, mais elle fonctionne également pour la communication entre clusters. Par exemple, un client situé dans un autre cluster peut utiliser payment-service.service.prod.ddsd pour récupérer toutes les adresses IP de pods associées au service de paiement.

Équilibrage de la charge côté client : Le service mesh est responsable de l'équilibrage de la charge côté client. Pour les services qui ne sont pas intégrés au service mesh, l'équilibrage de la charge se fait du côté de l'application client.

La figure 2 illustre les quatre caractéristiques décrites ci-dessus :

Figure 2 : Modèles de communication intra-cellule et inter-zones

Architecture de maillage de services : Le maillage de services de DoorDash, tel que décrit dans la figure 3, qui est déployé dans chaque cellule, adopte un modèle de conception de conteneur sidecar, en s'appuyant sur le proxy Envoy en tant que plan de données. Nous avons construit notre propre plan de contrôle basé sur xDS pour gérer les configurations Envoy. Le conteneur sidecar fonctionne comme une solution prête à l'emploi, interceptant, contrôlant et transformant de manière transparente tout le trafic HTTP1/HTTP2/gRPC entrant et sortant des microservices DoorDash, sans nécessiter aucune modification du code de l'application.

Figure 3 : Architecture de haut niveau du maillage de services

Bien que l'architecture du trafic de DoorDash ait des composants uniques, nous pensons que les défis et les leçons que nous avons rencontrés au cours de notre voyage d'efficacité lié au trafic du réseau peuvent être appliqués largement à d'autres architectures.

Modèles courants de transfert de données DoorDash

En ce qui concerne le trafic inter-zélandais, nous classons nos schémas de trafic comme suit :

Trafic HTTP1/HTTP2/gRPC : Communication directe de pod à pod entre microservices au sein de la même cellule ; le trafic entre microservices dans la cellule globale et ceux dans les cellules qui impliquent un saut supplémentaire dans le chemin d'appel - tels que les routeurs internes - augmente la probabilité de trafic inter-zones.

Trafic de stockage : Inclut le trafic des microservices vers les systèmes avec état tels que Aurora PostgreSQL, CockroachDB, Redis et Kafka.

Trafic d'infrastructure interne : Trafic Kubernetes interne tel que le trafic coredns ou la communication entre les composants du plan de contrôle Kubernetes. Ce type de trafic utilise généralement le DNS interne de Kubernetes au lieu du DDSD.

Première hypothèse

Nous pensions que le trafic HTTP1/HTTP2/gRPC au sein d'une même cellule était la source la plus importante de coûts de transfert de données entre zones en raison de notre architecture de microservices. Nous avons également déterminé que le maillage de services pouvait potentiellement permettre un routage adapté à la zone pour tous les microservices utilisant cette fonctionnalité dans Envoy. En comprenant ces deux éléments, nous avons donné la priorité à l'étude et à l'optimisation des modèles de trafic HTTP1/HTTP2/gRPC afin d'améliorer l'efficacité sans dégrader la qualité du service.

Aborder les coûts du trafic HTTP1/HTTP2/gRPC

À l'origine, le trafic entre les services était uniformément réparti entre différentes zones géographiques, comme le montre la figure 4. Avec la fonction de routage zone-aware d'Envoy, les services appelants préfèrent désormais diriger le trafic vers les services appelés dans la même zone, comme le montre la figure 5, réduisant ainsi les coûts de transfert de données entre zones. 

Figure 4 : Équilibrage simple de la charge entre les pods (round-robin) 
Figure 5 : Routage par zone

Pour activer la fonction de routage zone-aware d'Envoy, nous avons modifié notre plan de contrôle pour le maillage de services, en passant du type de découverte de services STRICT_DNS au service de découverte de points d'extrémité(EDS). Comme le montre la figure 6 ci-dessous, pour les domaines DDSD, le plan de contrôle envoie désormais dynamiquement les ressources EDS de chaque cluster Envoy vers les conteneurs Envoy sidecar. La ressource EDS comprend les adresses IP des pods et leurs informations AZ.

resources:
 - "@type": type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment
   cluster_name: payment-service.service.prod.ddsd
   endpoints:
     - locality:
         zone: us-west-2a
       lb_endpoints:
         - endpoint:
             address:
               socket_address:
                 address: 1.1.1.1
                 port_value: 80
     - locality:
         zone: us-west-2b
       lb_endpoints:
         - endpoint:
             address:
               socket_address:
                 address: 2.2.2.2
                 port_value: 80
     - locality:
         zone: us-west-2c
       lb_endpoints:
         - endpoint:
             address:
               socket_address:
                 address: 3.3.3.3
                 port_value: 80

Figure 6 : Exemple d'une réponse EDS

Grâce aux données fournies dans les réponses EDS, Envoy récupère la distribution AZ du service appelant et du service appelé. Ces informations permettent à Envoy de calculer les pondérations entre les pods. Bien que l'envoi du trafic via les AZ locaux soit toujours prioritaire, une partie du trafic peut être dirigée vers d'autres AZ afin d'assurer une distribution équilibrée et d'éviter la surcharge d'un seul pod, comme le montre la figure 7.

Figure 7 : Privilégier l'AZ local tout en assurant un trafic équilibré 

Cette solution de routage offre plusieurs avantages, notamment

  • Maintien de l'équilibre du trafic, même dans les scénarios où les services sont répartis de manière inégale entre les zones d'activité.
  • Possibilité de définir dynamiquement les poids du trafic entre les pods, ce qui élimine les opérations manuelles.
  • Réduire le rayon d'action d'une panne à zone unique ou à zones multiples
  • Réduction des temps de latence du trafic - les services de l'appelant se connectent à des appels plus proches.

Notre solution est encore plus efficace lorsque les modules de service sont déployés de manière homogène dans les zones d'activité. Pour ce faire, nous avons tiré parti de topologySpreadConstraints et fixé maxSkew à 1 avec whenUnsatisfiable : ScheduleAnyway, comme le montre la figure 8. Cela signifie que le planificateur Kubernetes planifiera les pods même si la condition n'est pas remplie, en donnant la priorité aux nœuds qui minimisent le skew.

Cette approche garantit que les pods sont toujours planifiés ; ne pas le faire réduirait la quantité de bin-packing, ce qui augmenterait le calcul inactif et finirait par grignoter les réductions de coûts liées au trafic dans les zones. Dans notre système de production, nous observons que 10 % du trafic est envoyé entre les ZA avec cette politique topologySpreadConstraints.

topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: ScheduleAnyway

Figure 8 : Configuration pour déployer les pods de manière homogène sur les AZ

Au fur et à mesure de la mise en œuvre des changements susmentionnés, nous avons constaté des améliorations dans les frais de transfert entre AWS et l'AZ. Néanmoins, nous nous attendions à des réductions plus importantes. Nous avons donc cherché à comprendre pourquoi nous n'économisions pas autant que nous l'avions prévu.

Restez informé grâce aux mises à jour hebdomadaires

Abonnez-vous à notre blog d'ingénierie pour recevoir régulièrement des informations sur les projets les plus intéressants sur lesquels notre équipe travaille.

Trouver l'aiguille dans la botte de foin : Une meilleure visibilité du trafic trans-zélandais

Nous avons confirmé que le routage zone-aware fonctionnait comme prévu en validant les métriques Envoy entre les ZA et les journaux des applications, mais nous avions besoin de plus de visibilité sur le trafic inter-ZA pour déterminer la cause première de la réduction des coûts de transfert de données plus faible que prévu. Nous avons donc utilisé un certain nombre d'outils d'observabilité, notamment les journaux de flux VPC, les mesures de l'agent ebpf et les mesures d'octets de mise en réseau Envoy pour rectifier la situation.

À l'aide des journaux de flux VPC, nous avons mis en correspondance les adresses IP srcaddr et dstaddr avec les sous-réseaux correspondants déployés dans la structure de notre compte AWS. Ce mappage a fourni une règle récapitulative qui nous a permis de comprendre les schémas globaux de trafic de sortie et d'entrée entre les différents sous-réseaux hébergeant diverses parties de notre empreinte cloud, y compris les sous-réseaux pour les clusters Kubernetes de production, les solutions de stockage, les caches et les courtiers de messages, comme le montre le tableau 1. Nous avons pu confirmer que notre plus grande source de coûts de transfert inter-zones est le trafic non lié au stockage au sein de chaque cellule. Environ 20 % de ces coûts sont dus au trafic vers un équilibreur de charge élastique (ELB) utilisé pour notre pipeline d'ingestion d'événements(Iguazu).

Tableau 1 : Attribution du trafic inter-zones à l'aide des journaux de flux VPC

En outre, nous avons examiné les mesures HTTP1/HTTP2/gRPC du service mesh, en particulier les octets de connexion pour toutes les requêtes et réponses, tant pour le trafic entrant que pour le trafic sortant. Les mesures les plus importantes du proxy Envoy du service mesh sont cx_rx_bytes_total et cx_tx_bytes_total pour les récepteurs Envoy et les clusters. Cependant, comme tous les services n'étaient pas encore intégrés au service mesh à l'époque, nous nous sommes appuyés sur les mesures de BPFAgent, qui est déployé globalement sur chaque nœud Kubernetes de production, pour améliorer la visibilité sur le nombre total d'octets du réseau. En utilisant les mesures de ces deux sources, nous avons découvert que 90 % de l'ensemble du trafic est du trafic HTTP1/HTTP2/gRPC ; 45 % de ce trafic avait déjà été intégré au service mesh et 91 % de cette portion de trafic est envoyée à Iguazu !

De houblon en houblon : Le flux de trafic d'Iguazu

Après avoir analysé le flux de trafic d'Iguazu, comme le montre la figure 9, nous avons réalisé qu'il y avait plusieurs sauts intermédiaires entre les services appelants et appelés, ou pods. Lorsque le trafic se déplace des services appelants vers Iguazu, il passe d'abord par les ELB, avant d'atterrir sur l'un des nœuds de travail d'un autre cluster Kubernetes dans la cellule globale. Comme la politique externalTrafficPolicy est configurée comme un cluster, iptables redirige le trafic vers un autre nœud pour assurer l'équilibrage de la charge.

Figure 9 : Flux de trafic initial d'Iguazu

Nous avons observé le comportement suivant en matière de transfert de données :

  • Les clients qui envoyaient du trafic aux ELB utilisaient encore directement les noms DNS des ELB. Comme les clients n'utilisaient pas les domaines DDSD internes, le type de découverte de service dans le cluster Envoy était toujours STRICT_DNS au lieu d'EDS, ce qui est une condition préalable à l'activation du routage adapté à la zone. Cela signifie que les conteneurs sidecar Envoy utilisaient une simple approche round-robin pour distribuer le trafic vers les ELB.   
  • L'équilibrage de charge inter-zones a été désactivé dans le cas du trafic entre les ELB et les nœuds de travail Kubernetes dans le cluster 2. 
  • Lorsque le trafic arrive à un nœud de travailleur Kubernetes depuis l'ELB d'Iguazu, il est ensuite redirigé par iptables vers un nœud aléatoire, ce qui augmente également la probabilité du trafic cross-AZ. 

Compte tenu de la complexité des multiples sauts dans le graphe d'appel d'Iguazu, nous avons décidé qu'il serait préférable de migrer le service d'Iguazu vers la même cellule que celle où les clients étaient déployés. 

Nous avons également configuré les règles de routage des sidecars Envoy de tous les clients pour acheminer le trafic vers les pods Iguazu dans le nouveau cluster au lieu de communiquer avec les ELB - sans que nos ingénieurs aient à modifier le code ou la configuration de leurs services. Cela nous a permis d'activer la communication directe de pod à pod pour le trafic Iguazu, permettant un routage adapté à la zone tout en réduisant simultanément le volume de trafic traité par les ELB, comme le montre la figure 10.  

Figure 10 : Nouveau flux de trafic à Iguazu

Ces actions ont eu un tel impact sur les coûts de transfert de données de DoorDash ainsi que sur nos coûts ELB qu'elles ont poussé notre fournisseur de cloud à nous contacter pour nous demander si nous étions en train de vivre un incident lié à la production.

Enseignements tirés

Voici quelques-unes des principales découvertes faites au cours de notre voyage :

  • La tarification du transfert de données des fournisseurs de services en nuage est plus complexe qu'il n'y paraît. Il vaut la peine d'investir du temps pour comprendre les modèles de tarification afin de mettre en place une solution efficace.
  • Il est difficile d'obtenir une vue d'ensemble de l'ensemble du trafic trans-zélandais. Néanmoins, la combinaison de mesures d'octets de réseau provenant de différentes sources peut suffire à identifier les points névralgiques qui, lorsqu'ils sont traités, peuvent avoir un impact important sur l'utilisation et les coûts.
  • Le routage zone-aware d'Envoy peut envoyer le trafic vers sa zone de disponibilité locale tout en assurant automatiquement la résilience grâce à un trafic équilibré.
  • Au fur et à mesure que le nombre de sauts augmente dans les graphes d'appels de microservices, la probabilité que les données soient transmises à travers les ZA s'accroît, ce qui augmente la complexité de garantir que tous les sauts prennent en charge le routage tenant compte de la zone.
  • Si vous envisagez d'utiliser un service maillé pour ses fonctions de gestion du trafic, vous pouvez également en tirer parti pour améliorer votre efficacité.
  • Pour en savoir plus sur le parcours de DoorDash en matière de maillage de services, consultez la session de Hochuen sur le maillage de services à la KubeCon + CloudNativeCon North America 2023.

Travaux futurs

Nous avons déjà dressé une liste des améliorations de la solution que nous aimerions mettre en œuvre, notamment : 

  • Rationaliser le processus de collecte des données de différentes sources et fournir une vue d'ensemble unifiée des coûts et de l'utilisation.
  • Activer la fonction de routage par zone pour les routeurs Envoy internes
  • Rendre la solution plus extensible pour prendre en charge le trafic HTTP1/HTTP2/gRPC en utilisant d'autres domaines que le DDSD.
  • Activez la fonction de routage tenant compte de la topologie pour le trafic de l'infrastructure interne à l'aide du proxy réseau Kubernetes kube-proxy. 
  • Explorer le routage optimisé pour les opérations de transfert de données volumineuses vers ou depuis les systèmes avec état de DoorDash, par exemple PostgreSQL, CRDB, Redis et Kafka. Cela permettra au trafic de rester dans des couloirs de nage AZ uniques chaque fois que cela est utile, ce qui réduira encore les coûts.

Remerciements

Nous remercions Darshit Gavhane et Allison Cheng pour avoir migré tous les services d'Iguazu vers la cellule où tournent les clients, ainsi que Steve Hoffman et Sebastian Yates pour leurs conseils et leur accompagnement tout au long du processus.

About the Authors

  • Hochuen Wong

    Hochuen Wong is a Software Engineer on the Core Infrastructure team at DoorDash

  • Levon Stepanian

    Levon Stepanian is a Cloud FinOps Lead on the Core Infrastructure team at DoorDash.

Emplois connexes

Localisation
Toronto, ON
Département
Ingénierie
Localisation
New York, NY; San Francisco, CA; Sunnyvale, CA; Los Angeles, CA; Seattle, WA
Département
Ingénierie
Localisation
San Francisco, CA ; Sunnyvale, CA
Département
Ingénierie
Localisation
Seattle, WA; San Francisco, CA; Sunnyvale, CA
Département
Ingénierie
Localisation
Seattle, WA; San Francisco, CA; Sunnyvale, CA
Département
Ingénierie