Skip to content

Blog


Atténuation des défaillances pour les microservices : Une introduction à Aperture

14 mars 2023

|
Cong Ma

Cong Ma

Matt Ranney

Matt Ranney

Pour faire face aux défaillances dans un système de microservices, des mécanismes d'atténuation localisés tels que le délestage et les disjoncteurs ont toujours été utilisés, mais ils peuvent ne pas être aussi efficaces qu'une approche plus globale. Ces mécanismes localisés(comme le démontre une étude systématique sur le sujet publiée à SoCC 2022) sont utiles pour empêcher que des services individuels ne soient surchargés, mais ils ne sont pas très efficaces pour traiter les défaillances complexes qui impliquent des interactions entre les services, ce qui est caractéristique des défaillances des microservices. 

Une nouvelle façon de gérer ces défaillances complexes consiste à adopter une vision globale du système : lorsqu'un problème survient, un plan d'atténuation global est automatiquement activé et coordonne les actions d'atténuation entre les services. Dans ce billet, nous évaluons le projet open-source Aperture et la manière dont il permet de mettre en place un plan global d'atténuation des défaillances pour nos services. Nous décrivons tout d'abord les types de pannes les plus courants que nous avons rencontrés chez DoorDash. Ensuite, nous nous penchons sur les mécanismes existants qui nous ont aidés à pallier les défaillances. Nous expliquerons pourquoi les mécanismes localisés ne sont peut-être pas la solution la plus efficace et nous plaiderons en faveur d'une approche globale d'atténuation des défaillances. En outre, nous partagerons nos premières expériences avec Aperture, qui offre une approche globale pour relever ces défis.

Catégories de défaillances de l'architecture des microservices

Avant d'expliquer ce que nous avons fait pour gérer les défaillances, explorons les types de défaillances de microservices que les organisations rencontrent. Nous examinerons quatre types de défaillances que DoorDash et d'autres entreprises ont rencontrées. 

Chez DoorDash, nous considérons chaque échec comme une opportunité d'apprentissage et nous partageons parfois nos idées et les leçons apprises dans des articles de blog publics pour montrer notre engagement à la fiabilité et au partage des connaissances. Dans cette section, nous aborderons quelques modèles de défaillance courants que nous avons connus. Chaque section est accompagnée de pannes réelles tirées de nos anciens articles de blog qui peuvent être explorées plus en détail.

Voici les défaillances que nous allons détailler :

  • Défaillance en cascade: réaction en chaîne de différents services interconnectés qui tombent en panne.
  • Tempête de tentatives : lorsque les tentatives exercent une pression supplémentaire sur un service dégradé.
  • Spirale de la mort : certains nœuds tombent en panne, ce qui entraîne l'acheminement d'une plus grande quantité de trafic vers les nœuds sains, qui tombent à leur tour en panne.
  • Défaillance métastable : terme général décrivant les défaillances qui ne peuvent pas se rétablir d'elles-mêmes en raison de l'existence d'une boucle de rétroaction positive.

Défaillance en cascade

La défaillance en cascade désigne le phénomène selon lequel la défaillance d'un seul service entraîne une réaction en chaîne de défaillances dans d'autres services. Nous avons documenté une panne grave de ce type dans notre blog. Dans ce cas, la chaîne de défaillances est partie d'une maintenance apparemment anodine de la base de données, qui a augmenté la latence de la base de données. Cette latence s'est ensuite répercutée sur les services en amont, provoquant des erreurs dues à des dépassements de délai et à l'épuisement des ressources. L'augmentation des taux d'erreur a déclenché un disjoncteur mal configuré, qui a interrompu le trafic entre un grand nombre de services non liés, ce qui a entraîné une panne avec un large rayon d'action.

La défaillance en cascade décrit un phénomène général où la défaillance se propage à travers les services, et il y a un large éventail de façons dont une défaillance peut se transmettre à une autre. La tempête de tentatives est un mode de transmission courant parmi d'autres, que nous examinerons plus loin.

Figure 1 : Le diagramme illustre un système de microservices subissant des défaillances en cascade. Le problème commence avec une base de données dégradée qui connaît une latence accrue. Cette latence se propage à travers les chaînes d'appels RPC et déclenche un bogue dans le service 2. Ce bogue fait que le service 2 répond par des erreurs à certaines requêtes, ce qui a un impact sur le service 1.
Figure 1: Le diagramme illustre un système de microservices subissant des défaillances en cascade. Le problème commence avec une base de données dégradée qui connaît une latence accrue. Cette latence se propage à travers les chaînes d'appels RPC et déclenche un bogue dans le service 2. Ce bogue fait que le service 2 répond par des erreurs à certaines requêtes, ce qui a un impact sur le service 1.

Réessayer la tempête

En raison de la nature peu fiable des appels de procédure à distance (RPC), les sites d'appel RPC sont souvent instrumentés avec des délais d'attente et des tentatives pour augmenter les chances de succès de chaque appel. Réessayer une requête est très efficace lorsque l'échec est transitoire. Cependant, ces tentatives aggravent le problème lorsque le service en aval est indisponible ou lent, car dans ce cas, la plupart des requêtes sont relancées plusieurs fois et finissent toujours par échouer. Ce scénario, dans lequel des tentatives excessives et inefficaces sont effectuées, s'appelle l'amplification de la charge de travail, et il entraîne une dégradation supplémentaire d'un service déjà dégradé. À titre d'exemple, ce type de panne s'est produit à un stade précoce de notre transition vers les microservices : une augmentation soudaine de la latence de notre service de paiement a entraîné un comportement de relance de l'application Dasher et de son système dorsal, ce qui a exacerbé la situation.

Figure 2 : Ce diagramme illustre un système de microservices qui subit une tempête de tentatives. Le problème commence avec une base de données dégradée qui subit une latence accrue. Cette latence se propage à travers les chaînes d'appels RPC et déclenche des tentatives dans les services 2, 3 et 5, ce qui augmente encore la pression sur la base de données.
Figure 2: Ce diagramme illustre un système de microservices qui subit une tempête de tentatives. Le problème commence avec une base de données dégradée qui subit une latence accrue. Cette latence se propage à travers les chaînes d'appels RPC et déclenche des tentatives dans les services 2, 3 et 5, ce qui augmente encore la pression sur la base de données.

La spirale de la mort

Les défaillances peuvent fréquemment se propager verticalement dans un graphe d'appels RPC entre les services, mais elles peuvent également se propager horizontalement entre les nœuds qui appartiennent au même service. Une spirale de la mort est une panne qui commence par un modèle de trafic qui fait qu'un nœud tombe en panne ou devient très lent, de sorte que l'équilibreur de charge achemine les nouvelles demandes vers les nœuds sains restants, ce qui les rend plus susceptibles de tomber en panne ou d'être surchargés. Ce billet de blog décrit une panne qui a commencé par l'échec de certains pods à la sonde de préparation et qui a donc été retirée du cluster, et les nœuds restants sont tombés en panne parce qu'ils n'étaient pas en mesure de gérer seuls les charges massives. 

Figure 3 : Ce diagramme illustre une grappe de services en proie à une spirale de la mort. Les nœuds 1 et 2 se dégradent et sont remplacés par des nœuds nouvellement lancés, qui ne sont pas prêts à absorber le trafic. L'équilibreur de charge achemine toutes les demandes entrantes vers le nœud 3, ce qui le rend plus susceptible de se dégrader également.
Figure 3 : Ce diagramme illustre une grappe de services en proie à une spirale de la mort. Les nœuds 1 et 2 se dégradent et sont remplacés par des nœuds nouvellement lancés, qui ne sont pas prêts à absorber le trafic. L'équilibreur de charge achemine toutes les demandes entrantes vers le nœud 3, ce qui le rend plus susceptible de se dégrader également.

Défaillances métastables

Un article récent propose un nouveau cadre pour étudier les défaillances des systèmes distribués, appelé "défaillance métastable". Bon nombre des pannes que nous avons connues appartiennent à cette catégorie. Ce type de défaillance se caractérise par une boucle de rétroaction positive au sein du système qui fournit une charge élevée durable en raison de l'amplification du travail, même après la disparition du déclencheur initial (par exemple, un mauvais déploiement ou un afflux d'utilisateurs). La défaillance métastable est particulièrement grave car elle ne se rétablit pas d'elle-même et les ingénieurs doivent intervenir pour arrêter la boucle de rétroaction positive, ce qui augmente le temps nécessaire pour se rétablir.

Figure 4 : Ce diagramme illustre le cycle de vie d'une défaillance métastable. Le système fonctionne à la fois dans des états stables et vulnérables au fur et à mesure que les charges fluctuent. Lorsque le système est dans un état vulnérable, un déclencheur tel qu'un afflux d'utilisateurs peut le faire basculer dans un état métastable, caractérisé par une charge élevée soutenue résultant d'une boucle de rétroaction positive au sein du système. Une intervention manuelle est alors nécessaire pour ramener le système à un état stable.
Figure 4 : Ce diagramme illustre le cycle de vie d'une défaillance métastable. Le système fonctionne à la fois dans des états stables et vulnérables au fur et à mesure que les charges fluctuent. Lorsque le système est dans un état vulnérable, un déclencheur tel qu'un afflux d'utilisateurs peut le faire basculer dans un état métastable, caractérisé par une charge élevée soutenue résultant d'une boucle de rétroaction positive au sein du système. Une intervention manuelle est alors nécessaire pour ramener le système à un état stable.

Contre-mesures locales

Toutes les défaillances documentées dans la section ci-dessus sont des types de contre-mesures qui tentent de limiter l'impact de la défaillance localement au sein d'une instance d'un service, mais aucune de ces solutions ne permet une atténuation coordonnée entre les services pour assurer le rétablissement global du système. Pour le démontrer, nous allons nous pencher sur chaque mécanisme d'atténuation existant que nous avons déployé, puis nous discuterons de leurs limites.

Les contre-mesures dont nous parlerons sont les suivantes :

  • Le délestage : qui empêche les services dégradés d'accepter davantage de demandes. 
  • Disjoncteur : qui arrête les demandes sortantes en cas de dégradation
  • Mise à l'échelle automatique : elle peut aider à gérer une charge élevée lors des pics de trafic, mais elle n'est utile que si elle est configurée pour être prédictive plutôt que réactive.

Nous expliquerons ensuite le fonctionnement de toutes ces stratégies de tolérance aux pannes, puis nous discuterons de leurs inconvénients et de leurs compromis.  

Délestage de charge

Le délestage est un mécanisme de fiabilité qui rejette les demandes entrantes à l'entrée du service lorsque le nombre de demandes en vol ou simultanées dépasse une limite. En ne rejetant qu'une partie du trafic, nous maximisons le débit du service, au lieu de permettre au service d'être complètement surchargé et de ne plus être en mesure d'effectuer un travail utile. Chez DoorDash, nous avons instrumenté chaque serveur avec une "limite de concurrence adaptative" issue de la bibliothèque concurrency-limit de Netflix. Elle fonctionne comme un intercepteur gRPC et ajuste automatiquement le nombre maximum de requêtes simultanées en fonction du changement de latence qu'elle observe : lorsque la latence augmente, la bibliothèque réduit la limite de concurrence pour donner à chaque requête plus de ressources de calcul. En outre, le délesteur peut être configuré pour reconnaître les priorités des demandes à partir de leur en-tête et n'accepter que les demandes de haute priorité pendant une période de surcharge.

Figure 5 : Ce diagramme montre un délesteur à l'œuvre. Le service 4 est dégradé et ne peut pas traiter toutes les demandes qui lui parviennent. À l'entrée du service, il rejette les demandes provenant du service 3 en raison de sa faible priorité.
Figure 5 : Ce diagramme montre un délesteur à l'œuvre. Le service 4 est dégradé et ne peut pas traiter toutes les demandes qui lui parviennent. À l'entrée du service, il rejette les demandes provenant du service 3 en raison de sa faible priorité.

Le délestage peut être efficace pour éviter qu'un service ne soit surchargé. Cependant, comme le délesteur est installé au niveau local, il ne peut gérer que les pannes de service locales. Comme nous l'avons vu dans la section précédente, les défaillances dans un système de microservices résultent souvent d'une interaction entre les services. Par conséquent, il serait avantageux de disposer d'une solution coordonnée en cas de panne. Par exemple, lorsqu'un service aval important (A) devient lent, un service amont (B) devrait commencer à bloquer les demandes avant qu'elles n'atteignent A. Cela empêche la latence accrue de A de se propager à l'intérieur du sous-graphe, ce qui pourrait provoquer une défaillance en cascade.

Outre les limites liées au manque de coordination, le délestage est également difficile à configurer et à tester. Pour configurer correctement un délesteur, il faut effectuer des tests de charge soigneusement orchestrés afin de comprendre la limite de concurrence optimale d'un service, ce qui n'est pas une tâche facile car dans l'environnement de production, certaines requêtes sont plus coûteuses que d'autres, et certaines requêtes sont plus importantes que d'autres pour le système. A titre d'exemple d'un délesteur mal configuré, nous avons eu un jour un service dont la limite de concurrence initiale était trop élevée, ce qui a entraîné une surcharge temporaire pendant la période de démarrage du service. Bien que le délesteur ait été en mesure de réduire la limite par la suite, l'instabilité initiale était grave et a montré à quel point il est important de configurer correctement le délesteur. Néanmoins, les ingénieurs laissent souvent ces paramètres à leurs valeurs par défaut, ce qui n'est souvent pas optimal pour les caractéristiques des services individuels.

Disjoncteur

Alors que le délestage est un mécanisme qui permet de rejeter le trafic entrant, un disjoncteur rejette le trafic sortant, mais, comme le délestage, il n'a qu'une vue localisée. Les coupe-circuits sont généralement mis en œuvre sous la forme d'un proxy interne qui gère les demandes sortantes vers les services en aval. Lorsque le taux d'erreur du service en aval dépasse un certain seuil, le coupe-circuit s'ouvre et rejette rapidement toutes les demandes vers le service en difficulté sans amplifier le travail. Au bout d'un certain temps, le disjoncteur laisse progressivement passer plus de trafic, pour finalement revenir à un fonctionnement normal. Chez DoorDash, nous avons intégré un coupe-circuit dans notre client interne gRPC.

Figure 6 : Le diagramme montre un disjoncteur en action. Le service 4 est dégradé et renvoie des erreurs. Le disjoncteur instrumenté du côté du client du service 3 s'ouvre et arrête toutes les requêtes provenant du service 3.
Figure 6 : le diagramme montre un disjoncteur en action. Le service 4 est dégradé et renvoie des erreurs. Le disjoncteur instrumenté du côté du client du service 3 s'ouvre et arrête toutes les requêtes provenant du service 3.

Dans les situations où le service en aval subit une défaillance mais a la capacité de se rétablir si le trafic est réduit, un coupe-circuit peut être utile. Par exemple, lors d'une spirale de la mort dans la formation, les nœuds malsains sont remplacés par des nœuds nouvellement démarrés qui ne sont pas prêts à prendre le trafic, de sorte que le trafic est acheminé vers les nœuds sains restants, ce qui les rend plus susceptibles d'être surchargés. Dans ce cas, un disjoncteur ouvert donne du temps et des ressources supplémentaires à tous les nœuds pour qu'ils redeviennent sains.

Les disjoncteurs ont le même problème de réglage que le délestage : il n'y a pas de bonne façon pour les auteurs de service de déterminer le seuil de déclenchement. De nombreuses sources en ligne sur ce sujet utilisent un "taux d'erreur de 50 %" comme règle empirique. Toutefois, pour certains services, un taux d'erreur de 50 % peut être tolérable. Lorsqu'un service appelé renvoie une erreur, cela peut être dû au fait que le service lui-même n'est pas en bonne santé ou qu'un service situé plus en aval rencontre des problèmes. Lorsqu'un disjoncteur s'ouvre, le service en aval devient effectivement inaccessible pendant un certain temps, ce qui peut être considéré comme encore moins souhaitable. Le seuil de déclenchement dépend de l'accord de niveau de service du service et des implications en aval des demandes, qui doivent tous être examinés avec soin.

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.

Mise à l'échelle automatique

Tous les orchestrateurs de clusters peuvent être configurés avec une mise à l'échelle automatique pour gérer les augmentations de charge. Lorsqu'il est activé, un contrôleur vérifie périodiquement la consommation de ressources de chaque nœud (par exemple, le processeur ou la mémoire), et lorsqu'il détecte une utilisation élevée, il lance de nouveaux nœuds pour répartir la charge de travail. Bien que cette fonctionnalité puisse sembler attrayante, chez DoorDash nous recommandons aux équipes de ne pas utiliser l'auto-scaling réactif (qui augmente la taille du cluster en temps réel lors d'un pic de charge). Comme cela est contre-intuitif, nous listons ci-dessous les inconvénients de l'auto-scaling réactif.

  • Les nœuds nouvellement lancés ont besoin de temps pour s'échauffer (remplir les caches, compiler le code, etc.) et présenteront une latence plus élevée, ce qui réduit temporairement la capacité de la grappe. En outre, les nouveaux nœuds exécutent des tâches de démarrage coûteuses, telles que l'ouverture de connexions à des bases de données et le déclenchement de protocoles d'adhésion. Ces comportements sont peu fréquents, de sorte qu'une augmentation soudaine peut entraîner des résultats inattendus.
  • Lors d'une panne impliquant une charge élevée, l'augmentation de la capacité d'un service ne fera souvent que déplacer le goulot d'étranglement vers un autre endroit. Cela ne résout généralement pas le problème.
  • L'auto-scaling réactif rend plus difficile l'analyse post-mortem, car la chronologie des mesures s'ajuste de diverses manières à l'incident, aux mesures prises par les humains pour l'atténuer et à l' auto-scaler.

Par conséquent, nous conseillons aux équipes d'éviter d'utiliser l'auto-scaling réactif et de préférer l'auto-scaling prédictif tel que le cron de KEDA qui ajuste la taille d'un cluster en fonction des niveaux de trafic attendus tout au long de la journée.

Tous ces mécanismes localisés sont efficaces pour traiter les différents types de défaillance. Cependant, la localisation a ses propres inconvénients. Nous allons maintenant examiner les raisons pour lesquelles les solutions localisées n'ont qu'une portée limitée et pourquoi une observation et une intervention à l'échelle mondiale seraient préférables. 

Lacunes des contre-mesures existantes

Toutes les techniques de fiabilité que nous employons ont une structure similaire composée de trois éléments : la mesure des conditions opérationnelles, l'identification des problèmes par le biais de règles et de paramètres, et les mesures à prendre lorsque des problèmes surviennent. Par exemple, dans le cas du délestage, les trois composantes sont les suivantes :

  • Mesure : calcule l'historique récent de la latence du service ou des erreurs.
  • Identifier : utilise des formules mathématiques et des paramètres prédéfinis pour déterminer si le service risque d'être surchargé.
  • Action : refuse les demandes entrantes excessives

Pour les disjoncteurs, il s'agit de

  • Mesure : évalue le taux d'erreur du service en aval
  • Identifier : vérifie s'il dépasse un seuil
  • Action : arrête tout le trafic sortant vers ce service

Cependant, les mécanismes localisés existants souffrent de lacunes similaires :

  • Ils utilisent les paramètres locaux du service pour mesurer les conditions d'exploitation ; cependant, de nombreuses catégories de pannes impliquent une interaction entre de nombreux composants, et il est nécessaire d'avoir une vue d'ensemble du système pour prendre de bonnes décisions sur la manière d'atténuer les effets d'une condition de surcharge. 
  • Ils utilisent des heuristiques très générales pour déterminer l'état du système, ce qui n'est souvent pas assez précis. Par exemple, la latence seule ne permet pas de savoir si un service est surchargé ; une latence élevée peut être due à un service lent en aval.
  • Leurs actions de remédiation sont limitées. Comme les mécanismes sont instrumentés localement, ils ne peuvent prendre que des mesures locales. Les actions locales ne sont généralement pas optimales pour rétablir le système dans un état sain, car la véritable source du problème peut se trouver ailleurs.

Nous allons examiner comment surmonter ces lacunes et rendre l'atténuation plus efficace.

Utilisation de contrôles globalisés : Aperture pour la gestion de la fiabilité

Un projet qui va au-delà des contre-mesures locales pour mettre en œuvre un contrôle de charge globalisé est mis en œuvre par Aperture, un système de gestion de la fiabilité à code source ouvert. Il fournit une couche d'abstraction de fiabilité qui facilite la gestion de la fiabilité dans une architecture distribuée de microservices. Contrairement aux mécanismes de fiabilité existants qui ne peuvent réagir qu'à des anomalies locales, Aperture offre un système centralisé de gestion de la charge qui lui permet de coordonner de nombreux services en réponse à une panne en cours. 

Conception d'Aperture

Comme les contre-mesures existantes, Aperture surveille et contrôle la fiabilité du système à l'aide de trois éléments clés. 

  1. Observer: Aperture recueille des mesures liées à la fiabilité de chaque nœud et les regroupe dans Prometheus. 
  2. Analyser: Un contrôleur Aperture indépendant surveille en permanence les paramètres et suit les écarts par rapport au SLO.
  3. Actionner: En cas d'anomalie, le contrôleur Aperture activera les politiques correspondant au modèle observé et appliquera des actions à chaque nœud, comme le délestage ou la limitation du débit distribué. 

Notre expérience de l'utilisation d'Aperture

Aperture est hautement configurable dans sa manière de détecter et d'agir face aux anomalies du système. Il prend en compte des politiques écrites dans des fichiers YAML qui guident ses actions pendant une panne. Par exemple, le code ci-dessous, extrait de la documentation d' Aperture et simplifié, calcule la moyenne mobile exponentielle (EMA) de la latence. Il utilise les mesures de latence de Prometheus et déclenche une alerte lorsque la valeur calculée est supérieure à un seuil. 

circuit:
    components:
      - promql:
          evaluation_interval: 1s
          out_ports:
            output:
              signal_name: LATENCY
          query_string:
            # OMITTED
      - ema:
          ema_window: 1500s
          in_ports:
            input:
              signal_name: LATENCY
          out_ports:
            output:
              signal_name: LATENCY_EMA
          warm_up_window: 10s
      - decider:
          in_ports:
            lhs:
              signal_name: LATENCY
            rhs:
              signal_name: LATENCY_SETPOINT
          operator: gt
          out_ports:
            output:
              signal_name: IS_OVERLOAD_SWITCH
      - alerter:
          alerter_config:
            alert_name: overload
            severity: crit
          in_ports:
            signal:
              signal_name: IS_OVERLOAD_SWITCH
    evaluation_interval: 0.5s

Lorsqu'une alerte est déclenchée, Aperture exécute automatiquement des actions en fonction des politiques configurées. Parmi les actions qu'il propose actuellement, on peut citer la limitation du débit distribué et la limitation de la concurrence (ou délestage). Le fait qu'Aperture dispose d'une vue et d'un contrôle centralisés de l'ensemble du système ouvre de nombreuses possibilités pour atténuer les pannes. Par exemple, il est possible de configurer une politique de délestage sur un service en amont lorsqu'un service en aval est surchargé, ce qui permet aux demandes excessives d'échouer avant d'atteindre le sous-graphe problématique, ce qui rend le système plus réactif et permet d'économiser des coûts.

Pour tester les capacités d'Aperture, nous avons déployé Aperture et l'avons intégré à l'un de nos principaux services, le tout dans un environnement de test, et nous avons constaté qu'il s'agissait d'un outil de délestage efficace. En augmentant le RPS des requêtes artificielles envoyées au service, nous avons observé que le taux d'erreur augmentait, mais que le débit restait stable. Lors d'une deuxième exécution, nous avons réduit la capacité de calcul du service et, cette fois, nous avons observé que le débit diminuait, mais que la latence n'augmentait que légèrement. Dans les coulisses des deux exécutions, le contrôleur Aperture a remarqué une augmentation de la latence et a décidé de réduire la limite de simultanéité. Par conséquent, l'intégration de l'API dans notre code d'application a rejeté certaines des demandes entrantes, ce qui se traduit par un taux d'erreur plus élevé. La réduction de la limite de concurrence garantit que chaque demande acceptée reçoit suffisamment de ressources de calcul, de sorte que la latence n'est que légèrement affectée.

Avec cette configuration simple, Aperture agit essentiellement comme un délesteur, mais il est plus configurable et plus convivial que nos solutions existantes. Nous sommes en mesure de configurer Aperture avec un algorithme sophistiqué de limitation de la concurrence qui minimise l'impact d'une charge ou d'une latence inattendue. Aperture offre également un tableau de bord Grafana tout-en-un utilisant les métriques Prometheus, qui donne un aperçu rapide de la santé de nos services.

Nous n'avons pas encore essayé les fonctionnalités plus avancées d'Aperture, notamment la possibilité de coordonner les actions d'atténuation entre les services et la possibilité d'avoir des politiques d'escalade dans lesquelles l'autoscaling est déclenché après une charge soutenue. L'évaluation de ces fonctionnalités nécessite des configurations plus élaborées. Cela dit, il est préférable de tester une solution de fiabilité dans l'environnement de production, où se produisent de véritables pannes, toujours imprévisibles.

Détails de l'intégration d'Aperture

Il vaut la peine de se pencher plus avant sur la manière dont Aperture s'intègre dans un système existant. Un déploiement d'Aperture comprend les éléments suivants :

  • Contrôleur ApertureCe module est le cerveau du système Aperture. Il surveille en permanence les mesures de fiabilité et décide du moment où il convient d'exécuter un plan d'atténuation. Lorsqu'un plan est déclenché, il envoie les actions appropriées (par exemple, le délestage) à l'agent Aperture.
  • Aperture agent : chaque cluster Kubernetes fait tourner une instance de l'agent Aperture, qui est chargé de suivre et d'assurer la santé des nœuds fonctionnant dans le même cluster. Lorsqu'une demande arrive dans un service, elle est interceptée par un point d'intégration qui transmet les métadonnées correspondantes à un agent Aperture. L'agent Aperture enregistre les métadonnées et répond en décidant d'accepter ou non la demande. Cette décision est basée sur les informations fournies par le contrôleur Aperture.
  • Point d'intégration : les services qui souhaitent bénéficier d'une gestion centralisée de la fiabilité peuvent s'intégrer à Aperture de trois manières. Si les services sont construits sur un maillage de services (actuellement seulement Envoy), Aperture peut être déployé sur le maillage de services directement sans changer le code de l'application. Il existe également des SDK Aperture que l'on peut utiliser pour intégrer le code de l'application aux points d'extrémité Aperture. Pour les applications Java, il est également possible d'utiliser Java Agent pour injecter automatiquement l'intégration d'Aperture dans Netty. Pour illustrer le rôle de cette intégration, voici un extrait de code qui montre comment utiliser le SDK Aperture en Java.
  • Prometheus & etcd : il s'agit de bases de données qui stockent les mesures de fiabilité et qui sont interrogées par le contrôleur Aperture pour obtenir une mesure de l'état de fonctionnement actuel.
private String handleSuperAPI(spark.Request req, spark.Response res) {
    Flow flow = apertureSDK.startFlow(metadata);
    if (flow.accepted()) {
        res.status(202);
        work(req, res);
        flow.end(FlowStatus.OK);
    } else {
        res.status(403);
        flow.end(FlowStatus.Error);
    }
    return "";
  }

Conclusion

Les mécanismes de fiabilité existants sont instrumentés au niveau local des services individuels, et nous avons montré que les mécanismes globalisés fonctionnent mieux pour gérer les pannes. Dans ce blog, nous avons montré pourquoi le maintien de la fiabilité d'un système de microservices est un problème difficile. Nous avons également donné un aperçu de nos contre-mesures actuelles. Ces solutions existantes préviennent efficacement de nombreuses pannes, mais les ingénieurs comprennent souvent mal leur fonctionnement interne et ne les configurent pas de manière optimale. En outre, ils ne peuvent observer et agir qu'à l'intérieur de chaque service, ce qui limite leur efficacité à atténuer les pannes dans un système distribué.

Pour tester l'idée d'utiliser des mécanismes globalisés pour atténuer les pannes, nous avons étudié le projet de gestion de la fiabilité Aperture. Ce projet élève la gestion de la fiabilité au rang de composante principale du système en centralisant les responsabilités de surveillance et de contrôle plutôt que de les confier à des services individuels. Ce faisant, Aperture permet de mettre en œuvre des méthodes automatisées, efficaces et rentables pour remédier aux pannes. Nous avons eu une expérience positive lors de notre essai initial et nous sommes enthousiasmés par son potentiel.

About the Authors

  • Cong Ma

    Cong Ma (MC) is a software engineer and consultant with experience in architecture and SRE, having worked on large-scale systems at Uber and DoorDash. Currently, Cong is pursuing a PhD at the University of Waterloo, where he is studying programming languages and compiler design.

  • Matt Ranney

    Matt Ranney is a Principal Engineer at DoorDash where he works on distributed systems and making microservices more reliable. Before that, he worked at Uber ATG on self-driving cars and system reliability.

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