Créer des applications évolutives et résilientes

La résilience et l'évolutivité sont des éléments essentiels à l'architecture de n'importe quelle application. Une application bien conçue doit pouvoir évoluer de façon fluide en fonction de l'augmentation et de la diminution de la demande, et être suffisamment résiliente pour supporter la perte d'une ou plusieurs ressources de calcul.

Ce document est destiné aux professionnels d'exploitation de systèmes qui utilisent Compute Engine. Dans ce document, vous apprendrez à utiliser Google Cloud Platform (GCP) pour créer des architectures d'applications évolutives et résilientes à l'aide de modèles et de bonnes pratiques largement applicables à toute application. Vous apprendrez également comment ces principes s'appliquent à des scénarios réels grâce à un exemple de déploiement du populaire outil de gestion de projets Open Source Redmine, une application basée sur Ruby on Rails. Ensuite, dans la section Déployer l'exemple de solution, vous pourrez déployer l'application vous-même et télécharger l'intégralité du code source.

GCP vous permet de créer et d'exécuter des applications Web à la fois évolutives et résilientes. Vous pouvez recourir à des services tels que Compute Engine et l'autoscaler pour ajuster les ressources de votre application, en fonction de la demande. De plus, avec le modèle de tarification de Compute Engine, vous payez à la seconde, et vous bénéficiez automatiquement du meilleur prix grâce aux remises automatiques proportionnelles à une utilisation soutenue, sans planification compliquée des capacités ou des réservations.

Pour découvrir une présentation générale de vos options d'hébergement Web sur GCP, consultez la page Diffuser un site Web.

Définir l'évolutivité et la résilience

Avant de décrire un exemple d'architecture d'application, il est utile de définir les termes évolutivité et résilience.

Évolutivité : adaptation de la capacité en fonction de la demande

Une application évolutive fonctionne bien, que ce soit avec 1 utilisateur ou 1 000 000 d'utilisateurs. Elle gère de manière optimale et automatique les pics et baisses de trafic. En ajoutant et en supprimant des machines virtuelles uniquement en cas de besoin, les applications évolutives ne consomment que les ressources nécessaires pour répondre à la demande.

Le graphique suivant représente la réaction d'une application évolutive face aux augmentations et aux diminutions de la demande.

Graphique montrant l'évolution des ressources en fonction de la demande.

Notez que la capacité s'ajuste de façon dynamique pour tenir compte de l'évolution de la demande. Cette configuration est parfois appelée élasticité dans la conception. Elle permet de garantir que vous ne payez que pour les ressources de calcul dont votre application a besoin à un moment donné.

Résilience : conçue pour faire face à l'inattendu

Une application hautement disponible ou résiliente est une application qui continue à fonctionner malgré les défaillances prévues ou imprévues des composants du système. Si une instance unique échoue ou si une zone entière rencontre un problème, une application résiliente reste tolérante aux pannes. Elle continue de fonctionner et se répare automatiquement si nécessaire. Les informations avec état ne sont stockées sur aucune instance. Ainsi, la perte d'une instance, voire d'une zone entière, ne devrait pas affecter les performances de l'application.

Une application véritablement résiliente nécessite une planification à la fois au niveau du développement logiciel et au niveau de l'architecture de l'application. Ce document se concentre principalement sur le niveau de l'architecture de l'application.

La conception d'une architecture d'application résiliente implique généralement les éléments suivants :

  • Des équilibreurs de charge pour surveiller les serveurs et répartir le trafic sur les serveurs capables de gérer au mieux les requêtes
  • L'hébergement de machines virtuelles dans plusieurs régions
  • La configuration d'une solution de stockage robuste

GCP : flexible et économique

Les architectures traditionnelles compatibles avec l'évolutivité et la résilience nécessitent souvent d'importants investissements en ressources. Avec les solutions sur site, l'évolutivité implique souvent de devoir choisir entre une utilisation excessive de la capacité du serveur pour gérer les pics d'utilisation, et l'achat basé uniquement sur les besoins moyens, au risque d'obtenir de mauvaises performances des applications ou une mauvaise expérience utilisateur lorsque le trafic augmente. La résilience ne se limite toutefois pas à la capacité du serveur. L'emplacement est également important. Pour minimiser l'impact d'événements physiques tels que des tempêtes violentes ou des tremblements de terre, vous devez envisager d'utiliser des serveurs à différents emplacements physiques, ce qui entraîne des coûts importants.

GCP propose une alternative : un ensemble de services cloud qui vous offre un moyen flexible d'apporter l'évolutivité et la résilience à votre architecture. De plus, GCP fournit ces services en utilisant une grille tarifaire que vous pouvez contrôler.

Construire des architectures résilientes et évolutives avec GCP

Le tableau suivant indique la correspondance des différents services GCP avec les exigences clés nécessaires pour rendre les applications évolutives et résilientes.

Exigence d'architecture Service GCP
Équilibrage de charge Équilibrage de charge HTTP
Hébergement de serveur Compute Engine, Régions et zones
Gestion de serveur Modèles d'instances, Groupes d'instances gérés, Autoscaler
Stockage de données Cloud SQL, Cloud Storage

Le schéma suivant représente la manière dont ces composants GCP interagissent pour créer une application évolutive et résiliente. Le rôle de chaque composant est décrit plus en détail dans la section suivante.

Schéma illustrant une application évolutive et résiliente.

Présentation des composants

Chaque composant de l'exemple d'architecture d'application joue un rôle précis pour garantir à la fois l'évolutivité et la résilience de l'application. Cette section décrit brièvement chacun de ces services. Les sections suivantes expliquent le fonctionnement de ces derniers ensemble.

Équilibreur de charge HTTP

L'équilibreur de charge HTTP expose une adresse IP publique unique que les clients utilisent pour accéder à l'application. Cette adresse IP peut être associée à un enregistrement DNS A (par exemple, example.com) ou CNAME (par exemple, www.example.com). Les requêtes entrantes sont réparties sur les groupes d'instances dans chaque zone en fonction de la capacité de chaque groupe. Dans une zone, les requêtes sont réparties uniformément sur les instances du groupe. L'équilibreur de charge HTTP peut équilibrer le trafic sur plusieurs régions. Toutefois, cet exemple de solution l'utilise dans une seule région comportant plusieurs zones.

Zone

Une zone est un emplacement isolé dans une région. Les zones d'une même région bénéficient entre elles de connexions réseau à haut débit et à faible latence. Google recommande de déployer des applications sur plusieurs zones au sein d'une région.

Instance

Une instance est une machine virtuelle hébergée dans l'infrastructure de Google. Vous pouvez installer et configurer ces instances comme des serveurs physiques. Dans l'exemple de déploiement, vous utilisez des scripts de démarrage et l'outil Chef pour configurer des instances avec le serveur et le code de l'application.

Modèle d'instance

Un modèle d'instance définit un type de machine, une image, une zone, des libellés et d'autres propriétés d'instance. Vous pouvez utiliser un modèle d'instance pour créer un groupe d'instances géré.

Groupe d'instances géré

Un groupe d'instances géré est un ensemble d'instances homogènes basées sur un modèle d'instance. Un groupe d'instances géré peut être ciblé par un équilibreur de charge HTTP pour répartir le travail sur plusieurs instances du groupe. Un groupe d'instances géré possède une ressource de gestionnaire de groupe d'instances correspondante, chargée de l'ajout et de la suppression d'instances dans le groupe.

Autoscaler

L'autoscaler Compute Engine ajoute ou supprime des instances Compute Engine dans un groupe d'instances géré en communiquant avec le gestionnaire du groupe en fonction du trafic, de l'utilisation du processeur ou d'autres signaux. Dans l'exemple de solution, l'autoscaler répond en fonction de la métrique RPS (Request Per Second) correspondant au nombre de requêtes par seconde de l'équilibreur de charge HTTP. Un autoscaler est requis pour chaque groupe d'instances géré que vous souhaitez faire évoluer automatiquement.

Cloud SQL

Cloud SQL est un service de base de données entièrement géré, compatible avec MySQL et PostgreSQL. La réplication, le chiffrement, les correctifs et les sauvegardes sont gérés par Google. Une instance Cloud SQL est déployée dans une seule zone, et les données sont automatiquement répliquées dans d'autres zones. L'application Redmine utilisée dans cet exemple est compatible avec MySQL et fonctionne de façon fluide avec Cloud SQL.

Cloud Storage

Cloud Storage permet le stockage et la récupération des objets (généralement des fichiers) à l'aide d'une interface simple et évolutive. Dans cette solution, un bucket Cloud Storage est utilisé pour répartir des clés SSL privées entre les instances évolutives Compute Engine, ainsi que pour stocker tous les fichiers téléchargés vers l'application Redmine. Cela signifie qu'aucune information avec état n'est stockée sur les disques des instances.

Résilience

Pour que cet exemple d'architecture soit résilient, il doit remplacer automatiquement les instances en échec ou indisponibles. Lorsqu'une nouvelle instance se connecte, elle doit :

  • comprendre son rôle dans le système ;
  • se configurer automatiquement ;
  • découvrir toutes ses dépendances ;
  • commencer à traiter automatiquement les requêtes.

Pour remplacer automatiquement une instance défaillante, vous pouvez utiliser plusieurs composants de Compute Engine, y compris les suivants :

Un script de démarrage s'exécute lorsque votre instance démarre ou redémarre. Vous pouvez utiliser ces scripts pour installer des logiciels et des mises à jour, pour vous assurer que des services s'exécutent au sein de la machine virtuelle, ou même pour installer un outil de gestion des configurations, tel que Chef, Puppet, Ansible ou Salt.

Ce scénario utilise un script de démarrage pour installer Chef Solo, qui à son tour affine la configuration des instances pour que celles-ci fonctionnent avec l'application. Pour savoir comment utiliser les scripts de démarrage et Chef Solo pour configurer automatiquement des instances, consultez la section Annexe : Ajouter une nouvelle instance à la fin de cette rubrique.

En plus d'un script de démarrage, vous devez définir quelques éléments supplémentaires avant de lancer une instance Compute Engine. Par exemple, vous devez spécifier le type de machine associé, l'image du système d'exploitation à utiliser et les disques que vous souhaitez connecter. Vous devez définir ces options à l'aide d'un modèle d'instance.

Ensemble, un modèle d'instance et un script de démarrage définissent le lancement d'une instance Compute Engine et la configuration du logiciel sur cette instance pour qu'il remplisse un rôle spécifique dans l'architecture de votre application.

Schéma illustrant la manière dont les scripts de démarrage, les modèles d'instance et les instances fonctionnent ensemble.

Bien sûr, un modèle d'instance n'est rien d'autre qu'un modèle. Pour que celui-ci fonctionne, vous avez besoin d'un moyen pour l'appliquer aux nouvelles instances Compute Engine à mesure qu'elles se connectent. Pour ce faire, vous devez créer un groupe d'instances géré. Vous déterminez ainsi le nombre d'instances que vous souhaitez exécuter à un moment donné et le modèle d'instance que vous souhaitez appliquer à ces instances. Un gestionnaire de groupe d'instances est ensuite chargé de lancer et de configurer ces instances en fonction des besoins.

Le schéma suivant indique la manière dont les composants suivants fonctionnent ensemble :

  • Script de démarrage
  • Modèle d'instance
  • Gestionnaire de groupe d'instances
  • Groupe d'instances géré
Schéma illustrant la manière dont les scripts de démarrage, les modèles d'instance, les gestionnaires de groupe d'instances et les groupes d'instances gérés fonctionnent ensemble

Un groupe d'instances géré et son gestionnaire de groupe d'instances correspondant peuvent être des ressources régionales ou spécifiques à une zone. Un modèle d'instance est une ressource au niveau du projet qui peut être réutilisée sur plusieurs groupes d'instances gérés, dans n'importe quelle zone de n'importe quelle région. Toutefois, vous pouvez spécifier certaines ressources zonales dans un modèle d'instance. Cela permet de limiter l'utilisation de ce modèle à la zone dans laquelle résident ces ressources zonales.

Avec les scripts de démarrage, les modèles d'instances et les groupes d'instances gérés, vous disposez désormais d'un système pouvant remplacer les instances non opérationnelles par de nouvelles. Dans la section suivante, vous découvrirez un moyen de définir une instance non opérationnelle et de la détecter.

Vérifications d'état

À ce stade, l'exemple d'application dispose de presque tous les outils nécessaires pour rendre l'application résiliente. Toutefois, il nous manque encore un élément : l'application a besoin d'un moyen d'identifier les instances non opérationnelles pour savoir si elle doit les remplacer.

Cette application est conçue pour permettre aux utilisateurs de se connecter à une instance opérationnelle appropriée à l'aide d'un équilibreur de charge HTTP. Cette architecture vous permet d'utiliser deux services pour identifier les instances capables de diffuser les requêtes :

  • Vérifications d'état : une vérification d'état HTTP spécifie le port et le chemin d'accès permettant de vérifier l'état de chaque instance. Cette vérification attend une réponse 200 OK de la part d'une instance opérationnelle.
  • Services backend : un service backend définit un ou plusieurs groupes d'instances recevant le trafic envoyé par un équilibreur de charge. Le service backend spécifie le port et le protocole exposés par les instances (par exemple, le port HTTP 80), ainsi que la vérification d'état HTTP à utiliser sur les instances des groupes d'instances.

Le schéma suivant affiche l'architecture de l'application, ainsi que le lien entre un service backend et une vérification d'état HTTP, en lien avec l'équilibreur de charge et les groupes d'instances.

Schéma illustrant les vérifications d'état et les services backend.

Résilience des données avec Cloud SQL

Les trois principaux aspects de toute architecture d'application sont la mise en réseau, le calcul et le stockage. L'architecture de l'application décrite ici couvre les composants de mise en réseau et de calcul. Toutefois, pour être complète, elle doit également aborder le composant de stockage.

Cet exemple de solution utilise des instances Cloud SQL de première génération pour fournir une base de données MySQL entièrement gérée. Avec Cloud SQL, Google gère automatiquement la réplication, le chiffrement, la gestion des correctifs et les sauvegardes.

Une base de données Cloud SQL couvre l'ensemble de la région. Cela signifie que les données sont répliquées dans les zones au sein d'une région. Cela équivaut à réaliser une sauvegarde de toutes les mises à jour de vos données au fur et à mesure qu'elles sont effectuées. Dans l'éventualité peu probable d'une défaillance complète d'une zone, les données seront conservées.

Cloud SQL vous permet de choisir entre deux types de réplication :

  • Réplication synchrone : avec ce type de réplication, les mises à jour sont copiées dans plusieurs zones avant de revenir au client. C'est la solution idéale pour la fiabilité et la disponibilité en cas d'incident majeur, mais elle ralentit les écritures.
  • Réplication asynchrone : elle augmente le débit en écriture par un accusé de réception des écritures une fois celles-ci mises en cache localement, mais avant de copier les données vers d'autres emplacements. La réplication asynchrone se traduit par des écritures plus rapides dans la base de données, car il n'est pas nécessaire d'attendre la fin de la réplication. Toutefois, vous risquez de perdre vos dernières mises à jour dans l'éventualité peu probable d'une défaillance du système du centre de données quelques secondes après la mise à jour de la base de données.

L'application Redmine utilisée dans cette solution utilise la réplication synchrone, car la charge de travail n'est pas très intensive en écriture. Vous devez choisir entre une réplication synchrone et asynchrone selon les exigences en matière de performances d'écriture et de durabilité des données de votre application.

Évolutivité

Les sections précédentes ont montré comment l'exemple d'application utilise GCP pour créer une application résiliente. Toutefois, la résilience seule n'est pas suffisante : l'évolutivité est également importante. L'application doit pouvoir fonctionner correctement pour 1 utilisateur ou 1 000 000 d'utilisateurs, et ses ressources doivent augmenter ou diminuer en fonction du nombre d'utilisateurs pour être rentables.

L'idée que les ressources de l'application peuvent augmenter ou diminuer nécessite la mise en place des éléments suivants :

  • Il vous faut un moyen d'ajouter ou de supprimer des instances du service. Vous avez également besoin d'un moyen de décider quand une instance doit être ajoutée ou supprimée. L'autoscaler GCP permet de résoudre ce problème.
  • Il vous faut également un moyen de stocker des données avec état. Les instances peuvent être ajoutées et supprimées. Il est donc déconseillé de stocker des données avec état sur ces instances. L'architecture de l'application permet de résoudre ce problème pour les données relationnelles en les stockant dans une instance Cloud SQL distincte. Toutefois, l'architecture doit également tenir compte des fichiers téléchargés par l'utilisateur. Cloud Storage permet de répondre à ce besoin.

Les sections suivantes indiquent comment utiliser l'autoscaler pour faire évoluer l'infrastructure exécutant l'application Redmine et comment exploiter Cloud Storage pour les fichiers téléchargés.

Faire évoluer l'infrastructure avec l'autoscaler

L'utilisation d'une application fluctue naturellement. Elle doit donc pouvoir ajuster de manière dynamique les ressources dont elle a besoin. Vous pouvez relever ce défi avec un autoscaler Compute Engine.

Lorsque le trafic ou la charge augmente, l'autoscaler ajoute des ressources pour gérer l'activité supplémentaire. Lorsque le trafic ou la charge diminue, il les diminue pour vous aider à réduire les coûts. L'autoscaler réalise ces actions de façon automatique selon les règles d'autoscaling que vous définissez et sans intervention ultérieure de votre part.

L'impact de l'autoscaler est double :

  1. Vos utilisateurs profitent pleinement de votre application, car il y a toujours suffisamment de ressources pour répondre à la demande.
  2. Vous contrôlez mieux vos coûts, car l'autoscaler supprime les instances lorsque la demande tombe en dessous d'un seuil spécifié.

L'autoscaler peut adapter le nombre de machines virtuelles en fonction de l'utilisation du processeur, de la capacité de diffusion ou d'une métrique de Stackdriver Monitoring. Cette solution utilise la métrique de capacité de diffusion pour ajouter ou supprimer des instances Compute Engine en fonction du nombre de requêtes par seconde (RPS) qu'elles reçoivent de l'équilibreur de charge. Pour en savoir plus, consultez la page Traitement par lots avec l'autoscaler Compute Engine.

Requêtes par seconde (RPS)

Les sections précédentes ont présenté un service backend unique qui identifie les groupes d'instances devant recevoir le trafic de l'équilibreur de charge. Pour chacun des groupes d'instances associés au service backend, cet exemple de solution définit également le mode d'équilibrage balancingMode=RATE. Cette propriété indique à l'équilibreur de charge de procéder à un équilibrage en fonction du nombre de requêtes par seconde (RPS) défini dans la propriété maxRatePerInstance, dont la valeur est de 100 pour cet exemple. Cette configuration signifie que l'équilibreur de charge tente de maintenir chaque instance à 100 RPS ou moins. Pour en savoir plus sur les propriétés de configuration d'un service backend,
consultez la documentation relative aux services backend.

Pour faire évoluer le nombre de RPS, vous devez créer un autoscaler pour chaque groupe d'instances que vous souhaitez faire évoluer automatiquement. Dans cet exemple, le groupe d'instances est une ressource zonale. Vous devez donc créer un autoscaler dans chaque zone.

Schéma illustrant l'intégration d'un autoscaler dans l'architecture d'une application.

Chaque autoscaler inclut une propriété utilizationTarget qui définit la fraction de la capacité maximale de diffusion de l'équilibrage de charge que l'autoscaler maintient. Dans cet exemple, la propriété utilizationTarget de chaque autoscaler est définie sur 80 % du taux maximal du service backend de 100 RPS pour chaque instance. Cela signifie que l'autoscaler évolue une fois que les RPS dépassent les 80 % du taux maximal par instance, ce qui correspond à 80 RPS. L'autoscaler se réduit lorsque les RPS descendent en dessous de ce seuil.

Organigramme montrant comment l'autoscaler détermine si une instance doit être ajoutée ou supprimée.

Chaque autoscaler définit également un nombre minimal et maximal d'instances qu'il devra respecter.

Notez que seuls les groupes d'instances gérés proposent des fonctionnalités d'autoscaling. Pour en savoir plus, consultez les pages Groupes d'instances et Procéder à l'autoscaling de groupes d'instances.

Traiter les importations de fichiers

Une partie de la fonctionnalité de l'application Redmine inclut la possibilité pour les utilisateurs d'importer et de sauvegarder des fichiers lorsqu'ils sont connectés. Le comportement par défaut de Redmine et de nombreuses autres applications similaires consiste à stocker ces fichiers directement sur le disque local. Cette approche est adaptée si vous ne disposez que d'un seul serveur doté d'un mécanisme de sauvegarde bien défini. Toutefois, elle n'est pas optimale lorsque vous disposez de plusieurs instances Compute Engine qui évoluent automatiquement derrière un équilibreur de charge. Si un utilisateur importe un fichier, rien ne garantit que la requête suivante arrivera sur la machine où ont été stockés les fichiers. Il n'y a également aucune garantie que l'autoscaler ne termine pas une instance inutile contenant des fichiers.

Il est donc préférable d'utiliser Cloud Storage. Ce service offre un emplacement centralisé, idéal pour stocker des fichiers importés et pouvoir y accéder sur un parc de serveurs Web qui ont évolué automatiquement. Cloud Storage expose également une API interopérable avec les clients Amazon S3, la rendant compatible avec les plug-ins d'application existants pour S3, y compris le plug-in Redmine S3, sans aucune modification. De nombreuses applications tierces et Open Source disposent de plug-ins compatibles avec des systèmes de stockage d'objets tels que Cloud Storage. Si vous créez votre propre application, vous pouvez directement utiliser l'API Cloud Storage, pour assurer la compatibilité avec le stockage des fichiers.

Le diagramme suivant représente le processus d'importation (flèches bleues) et de récupération (flèches vertes) de fichiers, réalisé à l'aide de Redmine et de Cloud Storage :

Schéma illustrant le flux de requêtes via l'application Redmine.

Le processus illustré dans le schéma est le suivant :

  1. L'utilisateur utilise une requête POST pour importer le fichier depuis un navigateur Web.
  2. L'équilibreur de charge choisit une instance pour traiter la requête.
  3. L'instance stocke le fichier dans Cloud Storage.
  4. L'instance stocke les métadonnées du fichier (telles que le nom, son propriétaire et son emplacement dans Cloud Storage) dans la base de données Cloud SQL.
  5. Lorsqu'un utilisateur demande un fichier, ce dernier est diffusé à partir de Cloud Storage vers une instance.
  6. L'instance envoie le flux via l'équilibreur de charge.
  7. Le fichier est envoyé à l'utilisateur.

Capacité de stockage

Cloud Storage supprime donc les fichiers importés avec état des instances Compute Engine, et permet à ces dernières d'évoluer de manière dynamique. Ce service offre également un stockage durable et redondant pour un nombre pratiquement infini de fichiers importés. Cette solution de stockage est résiliente, évolutive et économique : vous ne payez que pour le stockage utilisé, sans vous soucier de la planification des capacités. De plus, les données sont automatiquement stockées de façon redondante dans plusieurs zones.

Coût

Jusqu'à présent, l'architecture d'application décrite dans ce document illustre la création d'une application résiliente et évolutive à l'aide de GCP. Toutefois, il ne suffit pas de pouvoir créer une application, vous devez pouvoir le faire de la manière la plus économique possible.

Cette section explique que l'architecture d'application décrite dans ce document est non seulement résiliente et évolutive, mais également très économique. Dans cette section, nous commençons par formuler des hypothèses générales sur l'intensité et la fréquence d'utilisation de l'application, puis nous convertissons ces hypothèses en une estimation de base des coûts. Gardez à l'esprit que ces hypothèses ne sont que des hypothèses. N'hésitez pas à ajuster ces chiffres si nécessaire pour créer une estimation des coûts qui correspond davantage à l'utilisation anticipée de vos propres applications.

Calcul

L'une des priorités de toute architecture d'application est de savoir combien coûte le fonctionnement des serveurs. Cette analyse des coûts utilise les hypothèses suivantes :

Métrique Valeur
Nombre moyen de pages vues par mois 20 000 000
Nombre moyen de requêtes HTTP par mois 120 000 000
Heures pleines (90 % ou plus) d'utilisation De 7 h à 18 h, du lundi au vendredi
Transfert de données par page vue 100 Ko
Nombre d'heures pleines par mois 220
Taux de requêtes pendant les heures pleines 127 requêtes/seconde (RPS)
Taux de requêtes pendant les heures creuses 6 requêtes/seconde (RPS)

Sur la base de ces hypothèses, vous pouvez déterminer le nombre de pages vues par l'application pendant les heures pleines, de 7 h à 18 h du lundi au vendredi, chaque mois :

20 000 000 (vues/mois) * 6 (requêtes/vue) * 90 % (survenant pendant les heures pleines) = 108 000 000

En moyenne, il y a 22 jours de travail chaque mois. Si chaque journée de travail comporte 11 heures pleines, vous devez fournir suffisamment de ressources de calcul pour gérer 242 heures pleines par mois.

Vous devez ensuite déterminer quel type d'instance Compute Engine peut gérer ce type de trafic. Cette architecture d'application a été testée à l'aide de gatling.io, à des fins de tests de charge de base. Les résultats de ces tests ont déterminé que quatre instances Compute Engine de type n1-standard-1 seraient suffisantes pour gérer cette utilisation hypothétique.

En heures creuses, cette solution doit exécuter au moins deux instances n1-standard-1.

Pour savoir combien coûte l'exécution de ces instances, consultez les dernières estimations de prix à l'aide du simulateur de coût GCP. Notez que, dans les deux cas, ces instances sont alors automatiquement éligibles aux remises automatiques proportionnelles à une utilisation soutenue.

Équilibrage de charge et transfert de données

Cette application a provisionné un équilibreur de charge avec une seule règle de transfert, qui correspond à l'adresse IP publique à laquelle les utilisateurs se connectent. Cette règle de transfert est facturée à l'heure.

Pour les estimations de transfert de données, prenons d'abord l'exemple du pire des scénarios. Les frais relatifs à l'équilibreur de charge pour les données d'entrée traitées par celui-ci, ainsi que les tarifs de sortie standards, sont facturés pour le trafic sortant de l'équilibreur de charge. Supposons que 99,5 % des 120 000 000 de requêtes HTTP proviennent d'utilisateurs chargeant une page de projet Redmine. Le chargement d'une page compte pour une requête HTTP GET, ce qui crée cinq requêtes HTTP GET supplémentaires pour charger d'autres actifs (CSS, images et jQuery). Le chargement d'une page entière implique donc six requêtes HTTP. Cela entraîne donc :

  • environ 20 000 000 de pages complètes téléchargées par mois ;
  • environ 10 Ko de données d'entrée traitées et 450 Ko de transfert de données par page ;
  • un total d'environ 214 Go de données traitées par l'équilibreur de charge chaque mois et 9 091 Go de trafic sortant.

L'autre moitié des 20 000 000 de requêtes HTTP sont des requêtes HTTP POST permettant d'importer un fichier de taille moyenne (environ 0,5 Mo), pour 5 000 Go supplémentaires de données traitées par mois.

Cette estimation du simulateur de coût GCP indique le coût anticipé des 714 Go de transfert de données que l'équilibreur de charge traiterait, ainsi qu'un trafic de sortie de 9 091 Go pour ce scénario.

Cette estimation de transfert de données représente le pire des scénarios, car elle couvre l'ensemble du contenu, y compris les actifs statiques, de chaque requête provenant d'une instance Compute Engine et passant par un équilibreur de charge, sans l'avantage de la mise en cache ou d'un réseau de diffusion de contenu (CDN). Rappelez-vous, cette solution se base sur plus de 20 millions de chargements de pages chaque mois. Sur les 450 Ko environ de charge utile pour chaque chargement de page, 333 Ko sont nécessaires pour charger jQuery. En mettant simplement à jour une seule ligne de l'application pour charger jQuery depuis des bibliothèques hébergées par Google, vous réduisez les coûts de transfert de données de 73 %.

Cette nouvelle estimation de prix illustre les économies de transfert de données réalisées en passant aux bibliothèques hébergées par Google.

Stockage

Cette solution utilise Cloud Storage pour tous les fichiers importés via l'application Redmine. Comme décrit dans la section précédente, environ 0,5 % de cette utilisation concerne l'importation des fichiers, avec une taille moyenne d'environ 0,5 Mo pour chaque fichier. Cela signifie que vous pouvez vous attendre à 1 000 000 de nouveaux fichiers importés chaque mois, avec pour conséquence un nouveau stockage de 500 Go par mois. Cette solution suppose également la réalisation de 1 000 000 d'opérations HTTP PUT chaque mois pour stocker de nouveaux fichiers, facturées comme une opération de classe A.

Cette estimation de prix du simulateur de coût GCP indique le coût anticipé de l'utilisation de Cloud Storage.

Cette architecture utilise Cloud SQL pour stocker toutes les données relationnelles de l'application. D'après les exemples de métriques décrits précédemment, une base de données de type D2 avec 1024 Mo de RAM devrait fournir une capacité suffisante pour la charge de travail de l'application. Elle fonctionnera 24h/24, 7j/7. Cette base de données sera probablement utilisée de manière intensive. Choisissez donc l'option Heavy (Intensif) pour les opérations d'E/S dans le simulateur. Un test de cet exemple d'architecture a été réalisé en insérant 100 000 documents. Les résultats ont permis de déterminer qu'un disque de 50 Go peut accepter plus de 100 000 000 de documents, permettant à la base de données d'accepter plus de huit ans d'utilisation au taux indiqué.

Cette estimation du simulateur de coût GCP indique le coût prévu pour cette partie des frais d'architecture.

Déployer l'exemple de solution

Pour déployer l'exemple d'application décrit dans cette solution, accédez au dépôt GitHub et consultez la section Applications évolutives et résilientes sur GCP.

Annexe : Ajouter une instance

Dans le cadre de vos efforts pour créer une architecture d'application résiliente et évolutive, vous devez décider de la manière dont vous souhaitez ajouter de nouvelles instances. Plus précisément, vous devez déterminer comment configurer automatiquement les nouvelles instances à mesure qu'elles se connectent.

Dans cette section, vous allez examiner quelques-unes des options disponibles.

Amorcer l'installation du logiciel

Pour diffuser la requête Web d'un utilisateur, chaque instance a besoin d'un logiciel supplémentaire installé sur le système d'exploitation de base, ainsi que de données de configuration. Celles-ci incluent les informations de connexion à la base de données et le nom du bucket Cloud Storage dans lequel les fichiers sont stockés. Si vous imaginez ces composants sous forme de couches, vous pouvez visualiser l'intégralité de la pile à exécuter sur chaque instance de la manière suivante :

Schéma montrant comment le logiciel s'empile sur une instance.

Cette solution utilise un modèle d'instance, qui spécifie l'image Compute Engine que les instances utilisent lors de leur lancement. Plus précisément, cette solution utilise l'image Ubuntu 14.10 développée par Canonical et compatible avec celui-ci. Il s'agit de l'image du système d'exploitation de base. Elle n'inclut donc aucun logiciel ni configuration requise par l'application.

Pour que le reste de la pile soit automatiquement installé lorsqu'une instance est créée, vous pouvez utiliser une combinaison de scripts de démarrage Compute Engine et Chef Solo pour amorcer l'instance au moment du lancement. Vous pouvez spécifier un script de démarrage en ajoutant un élément d'attribut de métadonnées startup-script à un modèle d'instance. Un script de démarrage s'exécute à l'amorçage de l'instance.

Ce script de démarrage :

  1. installe le client Chef ;
  2. télécharge un fichier Chef spécial appelé node.json, qui indique à Chef la configuration à exécuter pour cette instance ;
  3. exécute Chef et s'occupe de la configuration détaillée.

Voici le script de démarrage dans son intégralité :

#! /bin/bash

# Install Chef
curl -L https://www.opscode.com/chef/install.sh | bash

# Download node.json (runlist)
curl -L https://github.com/googlecloudplatform/... > /tmp/node.json

# Run Chef
chef-solo -j /tmp/node.json -r https://github.com/googlecloudplatform/...

Fournir la configuration de l'application

Une fois qu'une nouvelle instance a été amorcée et s'est configurée à l'aide du script de démarrage et de Chef, elle doit disposer de certaines informations avant de pouvoir commencer à traiter les requêtes. Dans cet exemple, chaque instance doit connaître les informations de connexion à la base de données (telles que le nom d'hôte, le nom d'utilisateur et le mot de passe), ainsi que le nom du bucket Cloud Storage à utiliser et les identifiants nécessaires à la connexion.

Chaque instance Compute Engine est associée à des attributs de métadonnées que vous pouvez définir. Précédemment, vous avez appris comment ajouter l'attribut spécial de métadonnées startup-script, mais vous pouvez également ajouter des paires clé/valeur arbitraires. Ici, vous pouvez spécifier des attributs dans le modèle d'instance pour inclure les données de configuration nécessaires à la connexion des instances à la base de données et au bucket Cloud Storage.

Voici à quoi ressemblent les métadonnées d'un modèle d'instance dans la console GCP :

Capture d'écran de la console Google Cloud Platform, affichant les informations de métadonnées personnalisées pour un modèle d'instance.

Chef utilise un outil appelé Ohai pour analyser ces informations de configuration à partir des métadonnées de l'instance, remplissant des modèles pour créer les fichiers de configuration nécessaires à l'application. Voici le modèle qui crée le fichier database.yaml contenant les informations de connexion à la base de données, accédant automatiquement aux éléments de métadonnées appropriés :

production:
    adapter: mysql2
    database: <%= node['gce']['instance']['attributes']['dbname'] %>
    host:     <%= node['gce']['instance']['attributes']['dbhost'] %>
    username: <%= node['gce']['instance']['attributes']['dbuser'] %>
    password: <%= node['gce']['instance']['attributes']['dbpassword'] %>

Vous pouvez également accéder manuellement aux valeurs de métadonnées depuis une instance à l'aide du service de métadonnées local. Ici, vous pouvez utiliser curl pour récupérer le mot de passe de la base de données :

curl "http:/metadata.google.internal/computeMetadata/v1/instance/attributes/dbpassword" -H "Metadata-Flavor: Google"

Considérations relatives aux performances et à la dépendance

L'approche d'amorçage adoptée dans cette solution implique de commencer avec une image de système d'exploitation par défaut, d'installer tous les logiciels avec Chef au moment du lancement et d'utiliser des métadonnées d'instance pour fournir les données de configuration d'application.

Schéma montrant comment le logiciel s'empile sur une instance.Cette pile indique comment certains logiciels sont fournis avec l'image, certains sont installés au lancement et d'autres sont fournis après le lancement.

Cette approche présente l'avantage d'avoir la configuration du système spécifiée dans un livre de recettes Chef. Le livre de recettes peut voir ses versions contrôlées, et être partagé et utilisé pour provisionner des machines virtuelles en local à des fins de test, à l'aide de Vagrant ou de Docker, ou pour configurer des serveurs dans votre centre de données ou avec différents fournisseurs de cloud. La gestion des images est également simplifiée : dans le cas de cet exemple d'application, il vous suffit de suivre la seule image de base du système d'exploitation utilisée par l'application.

Certains inconvénients à prendre en compte incluent les temps de lancement potentiellement lents, car tous les logiciels sont téléchargés et installés en même temps, ce qui nécessite dans certains cas une compilation. Il est également important de prendre en compte les dépendances introduites par cette méthode : dans cet exemple, Chef a installé un certain nombre de packages à partir d'apt, de Rubygems et de GitHub. Si l'un de ces dépôts n'est pas disponible lors du démarrage d'une nouvelle instance, sa configuration échouera.

Images personnalisées et amorçage

Tout comme vous pouvez créer vos propres images personnalisées avec Compute Engine, tout installer au moment du lancement n'est pas la seule approche possible pour l'amorçage. Vous pouvez, par exemple, réaliser les opérations suivantes :

  1. Lancer une image de base Ubuntu 14.10
  2. Tout installer sauf l'application Redmine (Ruby, nginx, etc.)
  3. Créer une image à partir du résultat
  4. Utiliser cette image dans le modèle d'instance

Désormais, lorsqu'une nouvelle instance est lancée, il ne reste plus qu'à installer Redmine. Le temps d'amorçage est réduit, ainsi que le nombre de dépendances de packages externes.

Schéma illustrant la pile d'une instance avec tous les éléments installés sur l'image, à l'exception de Redmine.

Vous pouvez aller encore plus loin dans l'utilisation d'images personnalisées et tout intégrer dans une image, y compris l'ensemble des dépendances, la source d'application et la configuration. Cela permet d'accélérer le temps d'amorçage et de n'avoir aucune dépendance externe. Toutefois si n'importe quel élément est modifié dans votre application, vous devrez créer une nouvelle image et mettre à jour le modèle d'instance.

Schéma illustrant la pile d'une instance avec tous les logiciels fournis avec l'image.

Voyez les approches permettant d'amorcer une instance si elles s'appuyaient sur un continuum. Une configuration plus importante au moment du lancement signifie des temps d'amorçage plus lents, mais moins d'images à gérer. Une configuration plus intégrée dans une image personnalisée signifie des temps d'amorçage plus courts et moins de dépendances, mais potentiellement beaucoup plus d'images à gérer. Pour la plupart des clients, la bonne approche consiste à trouver un compromis entre les deux. Choisissez ce qui convient le mieux à votre application et à vous-même.

Schéma illustrant le continuum des façons dont le logiciel est installé sur une instance.La gamme va d'une installation de tous les éléments après le lancement à l'intégralité des éléments fournis avec l'image.

Étape suivante

  • Pour découvrir une présentation générale de vos options d'hébergement Web sur GCP, consultez la page Diffuser un site Web.
  • Testez d'autres fonctionnalités de Google Cloud Platform. Découvrez nos tutoriels.
Cette page vous a-t-elle été utile ? Évaluez-la :

Envoyer des commentaires concernant…