Concevoir des solutions évolutives et à haute disponibilité

Last reviewed 2023-08-05 UTC

Ce document du framework d'architecture Google Cloud présente les principes et bonnes pratiques de conception et d'organisation des services, afin qu'ils puissent tolérer les défaillances et s'adapter à la demande des clients. Un service fiable répond aux demandes des clients en cas de forte demande ou en cas d'événement de maintenance. Les principes et bonnes pratiques de conception de fiabilité suivants doivent faire partie de votre architecture système et de votre plan de déploiement.

Créer une redondance pour une meilleure disponibilité

Les systèmes nécessitant des niveaux de fiabilité élevés ne doivent présenter aucun point de défaillance unique et leurs ressources doivent être répliquées sur plusieurs domaines de défaillance. Un domaine de défaillance est un pool de ressources pouvant échouer indépendamment, telles qu'une instance de VM, une zone ou une région. Lorsque vous répliquez des données sur plusieurs domaines de défaillance, vous obtenez un niveau de disponibilité global plus élevé que celui des instances individuelles. Pour en savoir plus, consultez la page Régions et zones.

Illustration spécifique de l'intégration du critère de redondance à votre architecture système : pour cantonner à des zones spécifiques les défaillances de l'enregistrement DNS, utilisez des noms DNS zonaux pour que les instances du même réseau aient accès l'une à l'autre.

Concevoir une architecture multizone avec basculement pour la haute disponibilité

Rendez votre application résiliente aux défaillances de zone en la développant dans des pools de ressources répartis sur plusieurs zones, avec la réplication des données, l'équilibrage de charge et le basculement automatique entre les zones. Exécutez des instances dupliquées zonales de chaque couche de la pile d'applications et éliminez toutes les dépendances interzones de l'architecture.

Répliquer les données dans plusieurs régions pour la reprise après sinistre

Répliquez ou archivez les données dans une région distante pour permettre la reprise après sinistre en cas de panne régionale ou de perte de données. Lorsque la réplication est utilisée, la récupération est plus rapide, car les systèmes de stockage de la région distante disposent déjà de données quasiment à jour, à l'exception de la perte possible d'une petite quantité de données due au délai de réplication. Lorsque vous utilisez l'archivage périodique au lieu de la réplication continue, la reprise après sinistre implique la restauration des données à partir de sauvegardes ou d'archives dans une nouvelle région. Cette procédure entraîne généralement un temps d'arrêt de service plus long que l'activation d'une instance dupliquée de base de données mise à jour en continu et peut entraîner une perte de données plus importante en raison de l'intervalle de temps entre les opérations de sauvegarde consécutives. Quelle que soit l'approche utilisée, l'intégralité de la pile d'applications doit être redéployée et démarrée dans la nouvelle région, et le service sera indisponible pendant cette opération.

Pour en savoir plus sur les concepts et les techniques de reprise après sinistre, consultez la page Concevoir une solution de reprise après sinistre pour les pannes d'infrastructures cloud.

Concevoir une architecture multirégionale pour la résilience aux pannes régionales

Si votre service doit fonctionner sans interruption même dans les rares cas de défaillance d'une région entière, concevez-le de manière à utiliser des pools de ressources de calcul répartis dans différentes régions. Exécutez des instances dupliquées régionales de chaque couche de la pile d'applications.

Utilisez la réplication de données entre régions et le basculement automatique en cas de défaillance d'une région. Certains services Google Cloud proposent des variantes multirégionales, telles que Spanner. Pour plus de résilience contre les défaillances régionales, utilisez ces services multirégionaux dans la mesure du possible. Pour en savoir plus sur les régions et la disponibilité des services, consultez la page Emplacements Google Cloud.

Assurez-vous qu'il n'existe aucune dépendance interrégionale afin que l'étendue d'un impact au niveau d'une région soit limitée à cette région.

Éliminez les points de défaillance uniques, tels qu'une base de données principale à région unique qui, en cas d'inaccessibilité, pourrait entraîner une panne globale. Les architectures multirégionales coûtent souvent plus cher. Vous devez donc tenir compte des besoins de l'entreprise par rapport au coût avant d'adopter cette approche.

Pour plus d'informations sur la mise en œuvre de la redondance entre les domaines de défaillance, consultez l'article sur les archétypes de déploiement pour les applications cloud (PDF).

Éliminer les goulots d'étranglement liés à l'évolutivité

Identifiez les composants système qui ne peuvent pas dépasser les limites de ressources d'une seule VM ou d'une seule zone. Certaines applications évoluent verticalement, ce qui vous permet d'ajouter des cœurs de processeur, de la mémoire ou de la bande passante réseau sur une seule instance de VM pour gérer l'augmentation de la charge. Ces applications sont soumises à des limites strictes en termes d'évolutivité, et vous devez souvent les configurer manuellement pour gérer la croissance.

Si possible, repensez ces composants pour les faire évoluer horizontalement, par exemple avec des partitions ou un partitionnement entre des VM ou des zones. Pour gérer la croissance du trafic ou de l'utilisation, vous devez ajouter des partitions. Utilisez des types de VM standards qui peuvent être ajoutés automatiquement afin de gérer les augmentations de charge par partition. Consultez la section Modèles d'applications évolutives et résilientes pour plus d'informations.

Si vous ne pouvez pas redéfinir l'application, vous pouvez remplacer les composants gérés par vous par des services cloud entièrement gérés, conçus pour évoluer horizontalement sans aucune action de l'utilisateur.

Dégrader les niveaux de service de manière concertée en cas de surcharge

Concevez vos services de manière à tolérer une surcharge. Les services doivent détecter une surcharge et renvoyer des réponses de qualité inférieure à l'utilisateur ou supprimer partiellement le trafic, sans échouer complètement en cas de surcharge.

Par exemple, un service peut répondre aux requêtes des utilisateurs avec des pages Web statiques et désactiver temporairement un comportement dynamique plus coûteux à traiter. Ce comportement est détaillé dans le modèle de basculement tiède de Compute Engine vers Cloud Storage. Le service peut également autoriser des opérations en lecture seule et désactiver temporairement les mises à jour de données.

Les opérateurs doivent être notifiés qu'ils doivent corriger la condition d'erreur en cas de dégradation d'un service.

Anticiper et atténuer les pics de trafic

Ne synchronisez pas les requêtes entre les clients. Lorsqu'un nombre trop élevé de clients envoient du trafic au même moment, cela peut entraîner des pics de trafic, qui eux-mêmes peuvent générer des défaillances en cascade.

Mettez en œuvre des stratégies de limitation des pics côté serveur, telles que la limitation, la mise en file d'attente, le délestage ou la rupture de circuit, la dégradation élégante et la priorité des requêtes critiques.

Les stratégies d'atténuation sur le client incluent une limitation côté client et un intervalle exponentiel entre les tentatives avec gigue.

Nettoyer et valider les entrées

Pour éviter les entrées erronées, aléatoires ou malveillantes entraînant des pannes de service ou des failles de sécurité, nettoyez et validez les paramètres d'entrée des API et des outils opérationnels. Par exemple, Apigee et Google Cloud Armor peuvent vous aider à vous protéger contre les attaques par injection.

Utilisez régulièrement des tests flous lorsqu'un faisceau de test appelle intentionnellement des API avec des entrées aléatoires, vides ou trop volumineuses. Effectuez ces tests dans un environnement de test isolé.

Les outils opérationnels doivent valider automatiquement les modifications de configuration avant leur déploiement et rejeter les modifications en cas d'échec de la validation.

Prévention de défaillance d'une manière qui préserve la fonction

En cas de défaillance due à un problème, les composants du système doivent échouer de manière à permettre au système global de continuer à fonctionner. Ces problèmes peuvent être un bug logiciel, une entrée ou une configuration incorrecte, une panne d'instance non planifiée ou une erreur humaine. Le processus de vos services permet de déterminer si vous devez être trop permissif ou trop simpliste, plutôt que trop restrictif.

Voici des exemples de scénarios et des procédures à suivre pour gérer les défaillances :

  • Il est généralement préférable qu'un composant de pare-feu ayant une configuration incorrecte ou vide s'ouvre et permette au trafic réseau non autorisé de circuler pendant une courte période pendant que l'opérateur corrige l'erreur. Ce comportement permet de maintenir le service disponible, plutôt que de tomber en panne et de bloquer 100 % du trafic. Le service doit s'appuyer sur des vérifications d'authentification et d'autorisation plus en profondeur dans la pile d'applications pour protéger les zones sensibles pendant que tout le trafic passe.
  • Toutefois, il est préférable qu'un composant de serveur d'autorisations qui contrôle l'accès aux données utilisateur échoue et bloque tous les accès. Ce comportement entraîne une interruption du service lorsque la configuration est corrompue, mais évite le risque de fuite de données utilisateur confidentielles en cas d'échec.

Dans les deux cas, l'échec doit déclencher une alerte de priorité élevée afin qu'un opérateur puisse corriger la condition d'erreur. Les composants du service doivent échouer par excès de défaillance, sauf si cela présente des risques extrêmes pour l'entreprise.

Concevoir des appels d'API et des commandes opérationnelles réexécutables

Les API et les outils opérationnels doivent, dans la mesure du possible, rendre les réexécutions plus sûres. Une approche naturelle des nombreuses conditions d'erreur consiste à retenter l'action précédente, mais vous pourriez ne pas savoir si la première tentative a réussi.

Votre architecture système doit effectuer des actions idempotentes : si vous effectuez l'action identique sur un objet deux ou plusieurs fois de suite, elle doit produire les mêmes résultats qu'un seul appel. Les actions non idempotentes nécessitent du code plus complexe pour éviter la corruption de l'état du système.

Identifier et gérer les dépendances de service

Les concepteurs et propriétaires de services doivent gérer une liste complète de dépendances sur d'autres composants du système. La conception du service doit également inclure la récupération en cas d'échec des dépendances, ou une dégradation élégante si une récupération complète n'est pas possible. Tenez compte des dépendances sur les services cloud utilisés par votre système et des dépendances externes, telles que les API de services tierces, en tenant compte du fait que chaque dépendance système présente un taux d'échec non nul.

Lorsque vous définissez des cibles de fiabilité, vous devez savoir que le SLO d'un service est limité mathématiquement par les SLO de toutes ses dépendances critiques. Vous ne pouvez pas être plus fiable que le SLO le plus bas de l'une des dépendances. Pour en savoir plus, consultez la page Calcul de la disponibilité d'un service.

Dépendances de démarrage

Les services se comportent différemment à leur démarrage et à l'état stable. Les dépendances de démarrage peuvent différer considérablement des dépendances d'exécution à l'état stable.

Par exemple, au démarrage, un service peut avoir besoin de charger des informations sur l'utilisateur ou le compte à partir d'un service de métadonnées utilisateur qu'il rappelle rarement. Lorsque de nombreuses instances dupliquées de service redémarrent après un plantage ou une maintenance de routine, elles peuvent augmenter considérablement la charge sur les dépendances de démarrage, en particulier lorsque les caches sont vides et doivent être rechargés.

Testez le démarrage du service en cas de charge et provisionnez les dépendances de démarrage en conséquence. Envisagez une conception qui se dégrade de manière élégante en enregistrant une copie des données récupérées dans les dépendances de démarrage critiques. Ce comportement permet à votre service de redémarrer avec des données potentiellement obsolètes au lieu de ne pas pouvoir démarrer en cas de panne d'une dépendance critique. Votre service peut ensuite charger des données récentes, lorsque cela est possible, pour rétablir un fonctionnement normal.

Les dépendances de démarrage sont également importantes lorsque vous amorcez un service dans un nouvel environnement. Concevez votre pile d'applications avec une architecture en couches, sans dépendances cycliques entre les couches. Les dépendances cycliques peuvent sembler tolérables, car elles ne bloquent pas les modifications incrémentielles apportées à une seule application. Cependant, elles peuvent rendre difficile, voire impossible, le redémarrage après qu'une catastrophe a supprimé l'intégralité de la pile de services.

Minimiser les dépendances critiques

Minimisez le nombre de dépendances critiques pour votre service, c'est-à-dire d'autres composants dont la défaillance provoquerait inévitablement des interruptions de service. Pour rendre votre service plus résilient aux pannes ou aux lenteurs des autres composants dont il dépend, examinez les exemples de techniques et de principes de conception suivants pour convertir les dépendances critiques en dépendances non critiques :

  • Augmentez le niveau de redondance dans les dépendances critiques. L'ajout d'instances dupliquées réduit la probabilité qu'un composant entier soit indisponible.
  • Utilisez des requêtes asynchrones vers d'autres services au lieu de rester bloqué sur une réponse, ou utilisez une messagerie Pub/Sub pour dissocier les requêtes des réponses.
  • Mettez en cache les réponses d'autres services pour récupérer des indisponibilités à court terme des dépendances.

Pour rendre les défaillances de votre service moins préjudiciables aux autres composants qui en dépendent, consultez les exemples de techniques et de principes de conception suivants :

  • Utilisez des files d'attente de requêtes prioritaires et accordez une priorité plus élevée aux requêtes pour lesquelles un utilisateur attend une réponse.
  • Diffusez les réponses à partir d'un cache pour réduire la latence et la charge.
  • Prévention de défaillance d'une manière qui préserve la fonction
  • Se dégrader de manière élégante en cas de surcharge du trafic.

S'assurer que chaque modification peut faire l'objet d'un rollback

S'il n'existe pas de moyen bien défini d'annuler certains types de modifications apportées à un service, modifiez la conception du service de manière à ce qu'il accepte le rollback. Testez régulièrement les processus de rollback. Les API de chaque composant ou microservice doivent être versionnées, avec une rétrocompatibilité permettant aux générations précédentes de clients de fonctionner correctement à mesure que l'API évolue. Ce principe de conception est essentiel pour permettre le déploiement progressif des modifications d'API, avec un rollback rapide si nécessaire.

Le rollback peut être coûteux à mettre en œuvre dans les applications mobiles. Firebase Remote Config est un service Google Cloud qui simplifie le rollback des fonctionnalités.

Étant donné qu'il n'est pas possible d'annuler facilement les modifications du schéma d'une base de données, vous devez les exécuter en plusieurs phases. Concevez chaque phase pour permettre la réception des requêtes sécurisées de lecture et de mise à jour du schéma par la dernière version de votre application et la version précédente. Cette approche de conception vous permet d'effectuer un rollback en toute sécurité en cas de problème avec la dernière version.

Recommandations

Pour appliquer les conseils du framework d'architecture à votre propre environnement, procédez comme suit :

  • Mettez en œuvre un intervalle exponentiel entre les tentatives avec une logique de nouvelle tentative aléatoire pour les applications clientes.
  • Mettez en œuvre une architecture multirégionale avec basculement automatique pour une haute disponibilité.
  • Distribuez les requêtes des utilisateurs entre les segmentations et les régions à l'aide de l'équilibrage de charge.
  • Concevez l'application de manière à ce qu'elle se dégrade de manière élégante en cas de surcharge. Diffusez des réponses partielles ou fournissez des fonctionnalités limitées plutôt que d'échouer complètement.
  • Établissez un processus basé sur les données pour la planification de la capacité, et utiliser les tests de charge et les prévisions de trafic pour déterminer quand provisionner les ressources.
  • Définissez des procédures de reprise après sinistre et testez-les régulièrement.

Étape suivante

Découvrez d'autres catégories du framework d'architecture, telles que la conception du système, l'excellence opérationnelle, la sécurité, la confidentialité et la conformité.