Partager via


Modèle d’application web moderne pour .NET

Azure App Service
Azure Front Door
Cache Azure pour Redis
.NET

Cet article montre comment implémenter le modèle d’application web moderne. Le modèle d’application web moderne définit la façon dont vous devez moderniser les applications web dans le cloud et mettre en place une architecture orientée service. Le modèle d’application web moderne fournit une architecture, un code et des instructions de configuration préscriptifs qui s’alignent sur les principes du Framework Azure Well-Architected et s’appuient sur le modèle Reliable Web App.

Pourquoi utiliser le modèle d’application web moderne ?

Le modèle d’application web moderne peut vous aider à optimiser les zones à forte demande d’une application web. Il fournit des instructions détaillées pour découpler ces domaines, ce qui permet une mise à l’échelle indépendante pour l’optimisation des coûts. Cette approche vous permet d’allouer des ressources dédiées à des composants critiques, ce qui améliore les performances globales. Le découplage de services séparables peut améliorer la fiabilité en évitant que les ralentissements d’une partie de l’application n’affectent les autres. Le découplage vous permet également de versionner des composants d’application individuels indépendamment.

Comment implémenter le modèle d’application web moderne (Modern Web App)

Cet article contient des instructions d’architecture, de code et de configuration pour implémenter le modèle d’application web moderne. Utilisez les liens suivants pour accéder aux conseils dont vous avez besoin :

  • Conseils sur l’architecture. Découvrez comment modulariser les composants d’application web et sélectionner les solutions PaaS (Platform as a Service) appropriées.
  • Conseils sur le code. Implémentez quatre modèles de conception pour optimiser les composants découplés : Strangler Fig, Queue-Based Niveau de charge, Consommateurs concurrents et Surveillance des points de terminaison d’intégrité.
  • Conseils de configuration. Configurez l’authentification, l’autorisation, la mise à l’échelle automatique et la conteneurisation pour les composants découplés.

Conseil

Logo GitHub Il existe une implémentation de référence (application d’exemple) du modèle d’application web moderne. Il représente l’état final de l’implémentation de l’application web moderne. Il s’agit d’une application web de niveau production qui présente toutes les mises à jour du code, de l’architecture et de la configuration dont il est question dans cet article. Déployez et utilisez l’implémentation de référence pour guider votre implémentation du modèle d’application web moderne.

Conseils sur l’architecture

Le modèle d’application web moderne s’appuie sur le modèle d’application web fiable. Il nécessite quelques composants architecturaux supplémentaires à implémenter. Vous avez besoin d’une file d’attente de messages, d’une plateforme de conteneurs, d’un magasin de données de service découplé et d’un registre de conteneurs. Le diagramme suivant illustre l’architecture de base.

Diagramme montrant l’architecture de base du modèle d’application web moderne.

Pour un objectif de niveau de service (SLO) supérieur, vous pouvez ajouter une deuxième région à votre architecture d’application web. Si vous ajoutez une deuxième région, vous devez configurer votre équilibreur de charge pour acheminer le trafic vers cette région pour prendre en charge une configuration active-active ou active-passive. Utilisez une topologie de réseau hub-and-spoke pour centraliser et partager des ressources, telles qu’un pare-feu réseau. Accédez au référentiel de conteneurs via le réseau virtuel hub. Si vous avez des machines virtuelles, ajoutez un hôte bastion au réseau virtuel hub pour les gérer avec une sécurité renforcée. Le diagramme suivant illustre cette architecture.

Diagramme montrant l’architecture de modèle d’application web moderne avec une deuxième région et une topologie de réseau hub-and-spoke.

Dissocier l’architecture

Pour implémenter le modèle d’application web moderne, vous devez découpler l’architecture existante de l’application web. Découpler l’architecture implique de décomposer une application monolithique en services indépendants plus petits qui sont chacun responsable d’une fonctionnalité ou d’une fonctionnalité spécifique. Ce processus implique l’évaluation de l’application web actuelle, la modification de l’architecture et pour finir l’extraction du code de l’application web dans une plateforme de conteneurs. L’objectif est d’identifier et d’extraire systématiquement les services d’application qui bénéficient le plus d’être découplés. Pour découpler votre architecture, suivez ces recommandations :

  • Identifiez les limites du service. Appliquez des principes de conception pilotés par le domaine pour identifier les contextes délimités au sein de votre application monolithique. Chaque limite de contexte représente une limite logique et peut être candidate à un service distinct. Les services qui représentent des fonctions métier distinctes et qui ont moins de dépendances sont de bons candidats au découplage.

  • Évaluer les avantages du service. Concentrez-vous sur les services qui bénéficient le plus d’une mise à l’échelle indépendante. Découpler ces services et convertir des tâches de traitement de synchrones en opérations asynchrones permet une gestion des ressources plus efficace, prend en charge les déploiements indépendants et réduit le risque d’affecter d’autres parties de l’application pendant les mises à jour ou les modifications. Par exemple, vous pouvez séparer la commande du traitement des commandes.

  • Évaluer la faisabilité technique. Examinez l’architecture existante pour identifier les contraintes techniques et les dépendances susceptibles d’affecter le processus de découplage. Planifiez la façon dont les données sont gérées et partagées entre les services. Les services découplés doivent gérer leurs propres données et réduire au minimum l’accès direct à la base de données au-delà des limites des services.

  • Déployer des services Azure. Sélectionnez et déployez les services Azure dont vous avez besoin pour prendre en charge le service d’application web que vous envisagez d’extraire. Pour obtenir des conseils, consultez Sélectionner les services Azure appropriés.

  • Dissocier les services d’application web. Définissez des interfaces claires et des API pour permettre aux services d’application web nouvellement extraits d’interagir avec d’autres parties du système. Concevez une stratégie de gestion des données qui permet à chaque service de gérer ses propres données tout en garantissant la cohérence et l’intégrité. Pour obtenir des stratégies d’implémentation et des modèles de conception spécifiques à utiliser pendant ce processus d’extraction, consultez la section Conseils sur le code de cet article.

  • Utiliser un stockage indépendant pour les services découplés. Chaque service découplé doit avoir son propre magasin de données isolé pour faciliter le contrôle de version, le déploiement et l’extensibilité indépendants et maintenir l’intégrité des données. Par exemple, l’implémentation de référence sépare le service de rendu de ticket de l’API web et élimine la nécessité pour le service d’accéder à la base de données de l’API. Au lieu de cela, le service transmet l’URL où les images de ticket ont été générées à l’API web via un message Azure Service Bus, et l’API conserve le chemin d’accès à sa base de données.

  • Implémenter des pipelines de déploiement distincts pour chaque service découplé. Les pipelines de déploiement distincts permettent la mise à jour de chaque service à son propre rythme. Si différentes équipes ou organisations au sein de votre entreprise disposent de services différents, les pipelines de déploiement distincts permettent à chacune d’elles de contrôler ses propres déploiements. Utilisez des outils d’intégration continue et de livraison continue (CI/CD) tels que Jenkins, GitHub Actions ou Azure Pipelines pour configurer ces pipelines.

  • Vérifier les contrôles de sécurité. Veillez à ce que vos contrôles de sécurité soient mis à jour pour tenir compte de la nouvelle architecture, y compris les règles de pare-feu et les contrôles d’accès.

Sélectionner les services Azure appropriés

Pour chaque service Azure de votre architecture, consultez le guide de service Azure approprié dans Well-Architected Framework. Pour le modèle d’application web moderne, il vous faut un système de messagerie pour prendre en charge la messagerie asynchrone, une plateforme d’application qui prend en charge la conteneurisation et un référentiel d’images conteneur.

  • Choisir une file d’attente de messages. Une file d’attente de messages est un composant important des architectures orientées service. Elle découple les expéditeurs et les récepteurs de messages pour activer une messagerie asynchrone. Utilisez les conseils sur le choix d’un service de messagerie Azure pour en sélectionner un qui prend en charge vos besoins en matière de conception. Azure dispose de trois services de messagerie : Azure Event Grid, Azure Event Hubs et Azure Service Bus. Commencez par Service Bus comme choix par défaut et utilisez les deux autres options si Service Bus ne répond pas à vos besoins.

    Service Cas d’usage
    Bus de Service Choisissez Service Bus pour une livraison fiable, ordonnée et éventuellement transactionnelle de messages à valeur élevée dans les applications d’entreprise.
    Grid d'événements Choisissez Event Grid lorsque vous devez gérer efficacement un grand nombre d’événements discrets. Event Grid est évolutif pour les applications pilotées par les événements où de nombreux petits événements indépendants (comme les modifications d’état des ressources) doivent être acheminés vers les abonnés dans un modèle d’abonnement de publication à faible latence.
    Event Hubs Choisissez Event Hubs pour l’ingestion massive et à haut débit de données, comme la télémétrie, les journaux ou l’analytique en temps réel. Event Hubs est optimisé pour les scénarios de streaming où les données en bloc doivent être ingérées et traitées en continu.
  • Implémenter un service de conteneur. Pour les composants de votre application que vous souhaitez conteneuriser, vous avez besoin d’une plateforme d’application qui prend en charge les conteneurs. Le guide Choisir un service de conteneur Azure peut vous aider à prendre votre décision. Azure a trois principaux services de conteneur : Azure Container Apps, Azure Kubernetes Service (AKS) et Azure App Service. Commencez par Container Apps comme choix par défaut et utilisez les deux autres options si Container Apps ne répond pas à vos besoins.

    Service Cas d’usage
    Applications de conteneur Choisissez Container Apps si vous avez besoin d’une plateforme serverless qui met automatiquement à l’échelle et gère les conteneurs dans les applications pilotées par les événements.
    AKS Choisissez AKS si vous voulez disposer d’un contrôle précis sur les configurations Kubernetes et de fonctionnalités avancées pour la mise à l’échelle, la mise en réseau et la sécurité.
    Applications Web pour les conteneurs Choisissez Web App pour conteneurs dans App Service pour l’expérience PaaS la plus simple.
  • Implémenter un référentiel de conteneurs. Lorsque vous utilisez un service de calcul basé sur un conteneur, vous devez disposer d’un référentiel pour stocker les images conteneur. Vous pouvez utiliser un registre de conteneurs public comme Docker Hub ou un registre managé comme Azure Container Registry. L’introduction aux registres de conteneurs dans les conseils Azure peut vous aider à prendre votre décision.

Conseils sur le code

Pour réussir à découpler et à extraire un service indépendant, vous devez mettre à jour votre code d’application web avec les modèles de conception suivants : Figuier étrangleur, Nivellement de charge basé sur la file d’attente, Consommateurs concurrents, Surveillance du point de terminaison d’intégrité et Nouvelle tentative. Les rôles de ces modèles sont illustrés ici :

Diagramme montrant les rôles des modèles de conception dans l’architecture de modèle d’application web moderne.

  1. Modèle Figuier étrangleur : le modèle Figuier étrangleur migre de façon incrémentielle les fonctionnalités d’une application monolithique vers le service découplé. Implémentez ce modèle dans l’application web principale pour migrer progressivement les fonctionnalités vers des services indépendants en dirigeant le trafic en fonction des points de terminaison.

  2. Modèle de niveau de charge basé sur la file d’attente : le modèle de nivellement de charge basé sur la file d’attente gère le flux de messages entre le producteur et le consommateur à l’aide d’une file d’attente en tant que mémoire tampon. Implémentez ce modèle dans la base de code qui produit des messages pour la file d’attente. Le service découplé consomme ensuite ces messages de la file d’attente de façon asynchrone.

  3. Modèle Consommateurs concurrents : le modèle Consommateurs concurrents permet à plusieurs instances du service découplé de lire indépendamment la même file d’attente de messages et de se faire concurrence pour traiter ces derniers. Implémentez ce modèle dans le service découplé pour distribuer des tâches entre plusieurs instances.

  4. Modèle de surveillance des points de terminaison d’intégrité : le modèle de surveillance des points de terminaison d’intégrité expose les points de terminaison pour surveiller l’état et l’intégrité des différentes parties de l’application web. (4a) Implémentez ce modèle dans l’application web principale. (4b) Implémentez-le également dans le service découplé pour surveiller l’intégrité des points de terminaison.

  5. Modèle Nouvelle tentative : : le modèle Nouvelle tentative permet de gérer les défaillances transitoires en relançant les opérations susceptibles d’échouer de manière intermittente. (5a) Implémentez ce modèle sur tous les appels sortants vers d’autres services Azure dans l’application web principale, tels que les appels à la file d’attente de messages et aux points de terminaison privés. (5b) Implémentez également ce modèle dans le service découplé pour gérer les erreurs temporaires dans les appels aux points de terminaison privés.

Chaque modèle de conception offre des avantages qui s’alignent sur un ou plusieurs piliers du framework Well-Architected. Pour plus de détails, voir le tableau suivant.

Modèle de conception Emplacement de l’implémentation Fiabilité (RE) Sécurité (SE) Optimisation des coûts (CO) Excellence opérationnelle (OE) Efficacité des performances (PE) Prise en charge des principes de l’infrastructure bien architecte
Modèle Figuier étrangleur Application web principale RE :08
CO :07
CO :08
OE :06
OE :11
Modèle de nivellement de charge basé sur une file d’attente Application web principale (producteur de messages) RE :07
RE :07
CO :12
PE :05
Modèle Consommateurs concurrents Service découplé RE :05
RE :07
CO :05
CO :07
PE :05
PE :07
Modèle de supervision des points de terminaison d’intégrité Application web principale et service découplé RE :07
RE :10
OE :07
PE :05
Modèle Nouvelle tentative Application web principale et service découplé RE :07

Implémenter le modèle Figuier étrangleur

Utilisez le modèle Strangler Fig pour migrer progressivement les fonctionnalités de la base de code monolithique vers de nouveaux services indépendants. Extrayez de nouveaux services à partir de la base de code monolithique existante et modernisez les parties critiques de l’application web. Pour implémenter le modèle Figuier étrangleur, suivez les recommandations ci-dessous :

  • Configurez une couche de routage. Dans la base de code d’application web monolithique, implémentez une couche de routage qui dirige le trafic en fonction des points de terminaison. Utilisez la logique de routage personnalisée si nécessaire pour gérer des règles métier spécifiques pour diriger le trafic. Par exemple, si vous avez un point de terminaison /users dans votre application monolithique et que vous déplacez cette fonctionnalité vers le service découplé, la couche de routage dirige toutes les requêtes vers /users vers le nouveau service.

  • Gérer le déploiement des fonctionnalités. Utilisez les bibliothèques de gestion des fonctionnalités .NET pour implémenter des indicateurs de fonctionnalité et un déploiement intermédiaire pour déployer progressivement les services découplés. Le routage de l’application monolithique existant doit contrôler le nombre de demandes reçues par les services découplés. Commencez par un petit pourcentage de demandes et augmentez l’utilisation au fil du temps, car vous gagnez en confiance dans la stabilité et les performances du nouveau service. Par exemple, l’implémentation de référence extrait la fonctionnalité de rendu des tickets dans un service autonome, qui peut être introduit progressivement pour gérer une plus grande partie des demandes de rendu de ticket. Comme le nouveau service prouve sa fiabilité et ses performances, il peut éventuellement prendre en charge toute la fonctionnalité de rendu des tickets à partir du monolithe, en effectuant la transition.

  • Utiliser un service de façade (si nécessaire). Un service de façade est utile lorsqu’une demande unique doit interagir avec plusieurs services ou lorsque vous souhaitez cacher au client la complexité du système sous-jacent. Toutefois, si le service découplé n’a pas d’API publiques, un service de façade peut ne pas être nécessaire. Dans la base de code de l’application web monolithique, implémentez un service de façade pour acheminer les requêtes vers le serveur principal approprié (monolithe ou microservice). Dans le nouveau service découplé, assurez-vous que le nouveau service peut gérer les demandes indépendamment lorsqu’il est accessible via la façade.

Implémenter le modèle de nivellement de la charge basé sur une file d’attente

Implémentez le modèle de nivellement de charge basé sur la file d’attente sur la partie producteur du service découplé pour gérer de manière asynchrone les tâches qui n’ont pas besoin de réponses immédiates. Ce modèle améliore la réactivité et l’évolutivité globales du système en utilisant une file d’attente pour gérer la distribution de la charge de travail. Il permet au service découplé de traiter les demandes à un rythme constant. Pour implémenter efficacement ce modèle, suivez ces recommandations :

  • Utiliser une mise en file d’attente des messages non bloquante. Assurez-vous que le processus qui envoie des messages à la file d’attente ne bloque pas les autres processus en attendant que le service découplé gère les messages dans la file d’attente. Si le processus nécessite le résultat de l’opération de service découplé, il faut prévoir un autre moyen de gérer la situation en attendant que l’opération de mise en file d’attente se termine. Par exemple, l’implémentation de référence utilise Service Bus et le await mot clé avec messageSender.PublishAsync() pour publier de manière asynchrone des messages dans la file d’attente sans bloquer le thread qui exécute ce code :

    // Asynchronously publish a message without blocking the calling thread.
    await messageSender.PublishAsync(new TicketRenderRequestMessage(Guid.NewGuid(), ticket, null, DateTime.Now), CancellationToken.None);
    

    Cette approche garantit que l’application principale reste réactive et peut gérer d’autres tâches simultanément, tandis que le service découplé traite les demandes mises en file d’attente à un débit raisonnable.

  • Implémenter la nouvelle tentative et la suppression de messages. Implémentez un mécanisme pour effectuer une nouvelle tentative de traitement des messages mis en file d’attente qui ne peuvent pas être traités correctement. Si les échecs persistent, ces messages doivent être supprimés de la file d’attente. Par exemple, Service Bus dispose de fonctionnalités intégrées de nouvelle tentative et de file d’attente de lettres mortes.

  • Configurer le traitement des messages idempotents. La logique qui traite les messages de la file d’attente doit être idempotente pour gérer les cas où un message peut être traité plusieurs fois. Par exemple, l’implémentation de référence utilise ServiceBusClient.CreateProcessor et AutoCompleteMessages = trueReceiveMode = ServiceBusReceiveMode.PeekLock garantit que les messages ne sont traités qu’une seule fois et peuvent être retraités en cas d’échec. Le code suivant illustre cette logique.

    // Create a processor for idempotent message processing.
    var processor = serviceBusClient.CreateProcessor(path, new ServiceBusProcessorOptions
    {
        // Allow the messages to be auto-completed
        // if processing finishes without failure.
        AutoCompleteMessages = true,
    
        // PeekLock mode provides reliability in that unsettled messages
        // will be redelivered on failure.
        ReceiveMode = ServiceBusReceiveMode.PeekLock,
    
        // Containerized processors can scale at the container level
        // and need not scale via the processor options.
        MaxConcurrentCalls = 1,
        PrefetchCount = 0
    });
    
  • Gérer les modifications apportées à l’expérience. Le traitement asynchrone peut empêcher l’achèvement immédiat des tâches. Les utilisateurs doivent être informés du fait que leur tâche est encore en cours de traitement afin de définir des attentes adéquates et d’éviter toute confusion. Utilisez des signaux visuels ou des messages pour indiquer qu’une tâche est en cours. Offrez aux utilisateurs la possibilité de recevoir des notifications lorsque leur tâche est effectuée, par exemple un e-mail ou une notification Push.

Implémenter le modèle Consommateurs concurrents

Implémentez le modèle Consommateurs concurrents dans les services découplés pour gérer les tâches entrantes à partir de la file d’attente de messages. Ce modèle implique la distribution de tâches entre plusieurs instances de services découplés. Ces services traitent les messages de la file d’attente, améliorent l’équilibrage de charge et renforcent la capacité du système pour gérer les requêtes simultanées. Le modèle Consommateurs concurrents est efficace dans les cas suivants :

  • La séquence de traitement des messages n’est pas cruciale.
  • La file d’attente n’est pas affectée par les messages malformés.
  • L’opération de traitement est idempotente, ce qui signifie qu’elle peut être appliquée plusieurs fois sans modifier le résultat au-delà de l’application initiale.

Pour implémenter le modèle Consommateurs concurrents, suivez ces recommandations :

  • Gérez les messages simultanés. Lorsque votre système reçoit des messages d’une file d’attente, assurez-vous que le système est conçu pour gérer plusieurs messages simultanément. Définissez le nombre maximal d’appels simultanés sur 1 afin qu’un consommateur distinct gère chaque message.

  • Désactivez la prérécupération. Désactivez la prérécupération des messages afin que les consommateurs récupèrent les messages uniquement lorsqu’ils sont prêts.

  • Utilisez des modes de traitement des messages fiables. Utilisez un mode de traitement fiable, comme PeekLock (ou son équivalent), qui effectue automatiquement une nouvelle tentative de traitement des messages qui échouent. Ce mode offre plus de fiabilité que les méthodes de suppression première. Si un collaborateur ne parvient pas à gérer un message, un autre doit être en mesure de le traiter sans erreurs, même si le message est traité plusieurs fois.

  • Implémentez la gestion des erreurs Acheminer les messages mal formés ou non traités vers une file d’attente de lettres mortes distincte. Cette conception permet d’éviter les traitements répétitifs. Par exemple, vous pouvez détecter les exceptions pendant le traitement du message et déplacer le message problématique vers la file d’attente distincte.

  • Gérez les messages désordonnés. Concevez des consommateurs pour traiter les messages qui arrivent dans le désordre. Si vous avez plusieurs consommateurs parallèles, ils peuvent traiter les messages hors de commande.

  • Mise à l’échelle en fonction de la longueur de la file d’attente. Les services consommant des messages à partir d’une file d’attente doivent envisager la mise à l’échelle automatique en fonction de la longueur de la file d’attente ou en utilisant des critères de mise à l’échelle supplémentaires pour améliorer les pics de traitement des messages entrants.

  • Utilisez une file d’attente de réponse aux messages. Si le système requiert des notifications pour le traitement post-message, configurez une file d’attente de réponse ou de réponse dédiée. Cette configuration sépare la messagerie opérationnelle des processus de notification.

  • Utilisez des service sans état. Envisagez d’utiliser des services sans état pour traiter les demandes à partir d’une file d’attente. Ces services permettent une mise à l’échelle facile et une utilisation efficace des ressources.

  • Configurez la journalisation. Intégrez la journalisation et la gestion des exceptions spécifiques dans le flux de travail de traitement des messages. Concentrez-vous sur la capture d’erreurs de sérialisation et la mise en place de ces messages problématiques vers un mécanisme de lettres mortes. Ces journaux fournissent des informations précieuses pour la résolution des problèmes.

Par exemple, l’implémentation de référence utilise le modèle Consommateurs concurrents sur un service sans état exécuté dans Container Apps pour traiter les demandes de rendu des tickets à partir d’une file d’attente Service Bus. Il configure un processeur de file d’attente avec :

  • AutoCompleteMessages. Termine automatiquement les messages s’ils sont traités sans échec.
  • ReceiveMode. Utilise le mode PeekLock et les messages redéliseurs s’ils ne sont pas réglés.
  • MaxConcurrentCalls. Définissez la valeur 1 pour gérer un message à la fois.
  • PrefetchCount. Définissez la valeur 0 pour éviter les messages de prérécupération.

Le processeur enregistre les détails du traitement des messages, ce qui peut vous aider à résoudre les problèmes et à surveiller. Il capture les erreurs de désérialisation et route les messages non valides vers une file d’attente de lettres mortes pour empêcher le traitement répétitif des messages défectueux. Le service est mis à l’échelle au niveau du conteneur, ce qui permet une gestion efficace des pics de messages en fonction de la longueur de la file d’attente.

// Create a processor for the given queue that will process
// incoming messages.
var processor = serviceBusClient.CreateProcessor(path, new ServiceBusProcessorOptions
{
    // Allow the messages to be auto-completed
    // if processing finishes without failure.
    AutoCompleteMessages = true,
    // PeekLock mode provides reliability in that unsettled messages
    // are redelivered on failure.
    ReceiveMode = ServiceBusReceiveMode.PeekLock,
    // Containerized processors can scale at the container level
    // and need not scale via the processor options.
    MaxConcurrentCalls = 1,
    PrefetchCount = 0
});

// Called for each message received by the processor.
processor.ProcessMessageAsync += async args =>
{
    logger.LogInformation("Processing message {MessageId} from {ServiceBusNamespace}/{Path}", args.Message.MessageId, args.FullyQualifiedNamespace, args.EntityPath);
    // Unhandled exceptions in the handler will be caught by
    // the processor and result in abandoning and dead-lettering the message.
    try
    {
        var message = args.Message.Body.ToObjectFromJson<T>();
        await messageHandler(message, args.CancellationToken);
        logger.LogInformation("Successfully processed message {MessageId} from {ServiceBusNamespace}/{Path}",args.Message.MessageId, args.FullyQualifiedNamespace, args.EntityPath);
    }
    catch (JsonException)
    {
        logger.LogError("Invalid message body; could not be deserialized to {Type}", typeof(T));
        await args.DeadLetterMessageAsync(args.Message, $"Invalid message body; could not be deserialized to {typeof(T)}",cancellationToken: args.CancellationToken);
    }
};

Implémenter le modèle Surveillance de point de terminaison d’intégrité

Implémentez le modèle Surveillance des point de terminaison d’intégrité dans le code principal de l’application et le code de service découplé pour surveiller l’intégrité des points de terminaison d’application. Les orchestrateurs tels qu’AKS ou Container Apps peuvent interroger ces points de terminaison pour vérifier l’intégrité du service et redémarrer des instances non saines. Les applications ASP.NET Core peuvent ajouter un middleware de contrôle de l’intégrité dédié pour traiter efficacement les données d’intégrité des points de terminaison et les dépendances clés. Pour implémenter le modèle de Surveillance de point de terminaison d’intégrité, suivez ces recommandations :

  • Implémentez des contrôles d’intégrité. Utilisez le middleware de contrôles d’intégrité ASP.NET Core pour fournir des points de terminaison de contrôle d’intégrité.

  • Validez les dépendances. Vérifiez que votre contrôle d’intégrité valide la disponibilité des dépendances clés, notamment la base de données, le stockage et le système de messagerie. Le package non Microsoft AspNetCore.Diagnostics.HealthChecks peut implémenter des vérifications de dépendance de contrôle d’intégrité pour de nombreuses dépendances d’application courantes.

    Par exemple, l’implémentation de référence utilise ASP.NET middleware core health check pour exposer les points de terminaison de contrôle d’intégrité. Il utilise la AddHealthChecks() méthode sur l’objet builder.Services . Le code valide la disponibilité des dépendances clés, du Stockage Blob Azure et de la file d’attente Service Bus à l’aide des méthodes et AddAzureBlobStorage() des AddAzureServiceBusQueue() méthodes qui font partie du AspNetCore.Diagnostics.HealthChecks package. Container Apps permet la configuration des sondes d’intégrité surveillées pour déterminer si les applications sont saines ou en besoin de recyclage.

    // Add health checks, including health checks for Azure services
    // that are used by this service.
    // The Blob Storage and Service Bus health checks are provided by
    // AspNetCore.Diagnostics.HealthChecks
    // (a popular open source project) rather than by Microsoft. 
    // https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks
    builder.Services.AddHealthChecks()
    .AddAzureBlobStorage(options =>
    {
        // AddAzureBlobStorage will use the BlobServiceClient registered in DI.
        // We just need to specify the container name.
        options.ContainerName = builder.Configuration.GetRequiredConfigurationValue("App:StorageAccount:Container");
    })
    .AddAzureServiceBusQueue(
        builder.Configuration.GetRequiredConfigurationValue("App:ServiceBus:Host"),
        builder.Configuration.GetRequiredConfigurationValue("App:ServiceBus:RenderRequestQueueName"),
        azureCredentials);
    
    // Further app configuration omitted for brevity.
    app.MapHealthChecks("/health");
    
  • Configurer des ressources Azure. Configurez les ressources Azure pour utiliser les URL de contrôle d’intégrité de l’application pour confirmer la durée de vie et la préparation. Par exemple, l’implémentation de référence utilise Bicep pour configurer les URL de contrôle d’intégrité pour confirmer l’activité et la préparation de la ressource Azure. Une sonde liveness atteint le /health point de terminaison toutes les 10 secondes après un délai initial de 2 secondes.

    probes: [
      {
        type: 'liveness'
        httpGet: {
          path: '/health'
          port: 8080
        }
        initialDelaySeconds: 2
        periodSeconds: 10
      }
    ]
    

Implémenter le modèle Nouvelle tentative

Le modèle Nouvelle tentative permet aux applications de récupérer à partir d’erreurs temporaires. Le modèle Nouvelle tentative est au cœur du modèle d’application web fiable, votre application web devrait donc déjà l’utiliser. Appliquez le modèle Nouvelle tentative aux requêtes aux systèmes de messagerie et aux demandes émises par les services découplés que vous extrayez de l’application web. Pour implémenter le modèle Nouvelle tentative, suivez ces recommandations :

  • Configurer les options de nouvelles tentatives. Lors de l’intégration à une file d’attente de messages, veillez à configurer le client responsable des interactions avec la file d’attente avec les paramètres de nouvelle tentative appropriés. Spécifiez des paramètres comme le nombre maximal de nouvelles tentatives, le délai entre les nouvelles tentatives et le délai maximal.

  • Utilisez un backoff exponentiel. Implémentez une stratégie d’interruption exponentielle pour les tentatives de nouvelle tentative. Cette stratégie implique d’augmenter le temps entre chaque nouvelle tentative de manière exponentielle, ce qui permet de réduire la charge sur le système pendant les périodes de taux d’échec élevés.

  • Utilisez la fonctionnalité de nouvelle tentative du Kit de développement logiciel (SDK). Pour les services qui ont des kits sdk spécialisés, tels que Service Bus ou Stockage Blob, utilisez les mécanismes de nouvelle tentative intégrés. Les mécanismes de nouvelle tentative intégrés sont optimisés pour les cas d’utilisation classiques du service et peuvent gérer les nouvelles tentatives plus efficacement avec moins de configuration requise. Par exemple, l’implémentation de référence utilise la fonctionnalité de nouvelle tentative intégrée du Kit de développement logiciel (SDK) Service Bus (ServiceBusClient et ServiceBusRetryOptions). L’objet ServiceBusRetryOptions extrait les paramètres à partir de MessageBusOptions pour configurer les paramètres de nouvelle tentative tels que MaxRetries, , DelayMaxDelay, et TryTimeout.

    // ServiceBusClient is thread-safe and can be reused for the lifetime
    // of the application.
    services.AddSingleton(sp =>
    {
        var options = sp.GetRequiredService<IOptions<MessageBusOptions>>().Value;
        var clientOptions = new ServiceBusClientOptions
        {
            RetryOptions = new ServiceBusRetryOptions
            {
                Mode = ServiceBusRetryMode.Exponential,
                MaxRetries = options.MaxRetries,
                Delay = TimeSpan.FromSeconds(options.BaseDelaySecondsBetweenRetries),
                MaxDelay = TimeSpan.FromSeconds(options.MaxDelaySeconds),
                TryTimeout = TimeSpan.FromSeconds(options.TryTimeoutSeconds)
            }
        };
        return new ServiceBusClient(options.Host, azureCredential ?? new DefaultAzureCredential(), clientOptions);
    });
    
  • Adoptez les bibliothèques de résilience standard pour les clients HTTP. Pour les communications HTTP, intégrez une bibliothèque de résilience standard comme Polly ou Microsoft.Extensions.Http.Resilience. Ces bibliothèques offrent des mécanismes de nouvelle tentative complets qui sont essentiels pour la gestion des communications avec des services web externes.

  • Gérez le verrouillage des messages. Pour les systèmes basés sur les messages, implémentez des stratégies de gestion des messages qui prennent en charge les nouvelles tentatives sans perte de données, comme l’utilisation de modes « peek-lock » lorsqu’elles sont disponibles. Assurez-vous que les messages qui échouent sont correctement relancés et placés dans une file d’attente de lettres mortes en cas d’échecs répétés.

Implémentez le traçage distribué

À mesure que les applications deviennent plus orientées service et que leurs composants sont découplés, la surveillance du flux d’exécution entre les services est cruciale. Le modèle d’application web moderne utilise Application Insights et Azure Monitor pour obtenir une visibilité sur l’intégrité et les performances des applications via les API OpenTelemetry, qui prennent en charge le suivi distribué.

Le traçage distribué permet de suivre une demande d’utilisateur lorsqu’elle traverse plusieurs services. Lorsqu’une demande est reçue, elle est marquée avec un identificateur de trace, qui est transmis à d’autres composants via des en-têtes HTTP et des propriétés Service Bus pendant l’appel des dépendances. Les traces et les journaux incluent ensuite tant l’identificateur de trace qu’un identificateur d’activité (ou identificateur d’étendue), qui correspond au composant spécifique et à son activité parente. Les outils de supervision comme Application Insights utilisent ces informations pour afficher une arborescence d’activités et de journaux sur différents services, ce qui est essentiel pour la supervision des applications distribuées.

  • Installez les bibliothèques OpenTelemetry. Utilisez des bibliothèques d’instrumentation pour activer le suivi et les métriques à partir de composants courants. Ajoutez une instrumentation personnalisée avec System.Diagnostics.ActivitySource et System.Diagnostics.Activity, si nécessaire. Utilisez des bibliothèques d’exportation pour écouter les diagnostics OpenTelemetry et les enregistrer dans des magasins persistants. Utilisez des exportateurs existants ou créez-en votre propre à l’aide System.Diagnostics.ActivityListenerde .

  • Configurez OpenTelemetry. Utilisez la distribution Azure Monitor d’OpenTelemetry (Azure.Monitor.OpenTelemetry.AspNetCore). Assurez-vous qu’elle exporte les diagnostics vers Application Insights et inclut l’instrumentation intégrée pour les métriques, les traces, les journaux et les exceptions courants du runtime .NET et ASP.NET Core. Incluez d’autres packages d’instrumentation OpenTelemetry pour les clients SQL, Redis et Azure SDK.

  • Surveillance et analyse. Après avoir configuré le suivi, vérifiez que les journaux, les traces, les métriques et les exceptions sont capturés et envoyés à Application Insights. Vérifiez que les identificateurs de trace, d’activité et d’activité parent sont inclus. Ces identificateurs permettent à Application Insights de fournir une visibilité de trace de bout en bout sur les limites HTTP et Service Bus. Utilisez cette configuration pour surveiller et analyser les activités de votre application entre les services.

L’exemple d’application web moderne utilise la distribution Azure Monitor d’OpenTelemetry (Azure.Monitor.OpenTelemetry.AspNetCore). D’autres packages d’instrumentation sont utilisés pour les clients SQL, Redis et Azure SDK. OpenTelemetry est configuré dans l’exemple de service de rendu de ticket d’application web moderne comme suit :

builder.Logging.AddOpenTelemetry(o => 
{ 
    o.IncludeFormattedMessage = true; 
    o.IncludeScopes = true; 
}); 

builder.Services.AddOpenTelemetry() 
    .UseAzureMonitor(o => o.ConnectionString = appInsightsConnectionString) 
    .WithMetrics(metrics => 
    { 
        metrics.AddAspNetCoreInstrumentation() 
                .AddHttpClientInstrumentation() 
                .AddRuntimeInstrumentation(); 
    }) 
    .WithTracing(tracing => 
    { 
        tracing.AddAspNetCoreInstrumentation() 
                .AddHttpClientInstrumentation() 
                .AddSource("Azure.*"); 
    }); 

La builder.Logging.AddOpenTelemetry méthode achemine toute la journalisation via OpenTelemetry pour garantir un suivi et une journalisation cohérents dans l’application. Étant donné que les services OpenTelemetry sont inscrits builder.Services.AddOpenTelemetryavec , l’application est configurée pour collecter et exporter des diagnostics, qui sont ensuite envoyés à Application Insights via UseAzureMonitor. En outre, l’instrumentation du client pour les composants tels que Service Bus et les clients HTTP est configurée par WithMetrics et WithTracing, qui active la collecte automatique des métriques et des traces sans nécessiter de modifications apportées à l’utilisation existante du client. Seule une mise à jour de la configuration est requise.

Conseils sur la configuration

Les sections suivantes contiennent des conseils sur l’implémentation des mises à jour de la configuration. Chaque section s’aligne sur un ou plusieurs piliers de Well-Architected Framework.

Paramétrage Fiabilité (RE) Sécurité (SE) Optimisation des coûts (CO) Excellence opérationnelle (OE) Efficacité des performances (PE) Prise en charge des principes de l’infrastructure bien architecte
Configurer l’authentification et l’autorisation SE :05
OE :10
Implémenter une mise à l’échelle automatique indépendante RE :06
CO :12
PE :05
Conteneuriser le déploiement d’un service CO :13
PE :09
PE :03

Configurer l’authentification et l’autorisation

Pour configurer l’authentification et l’autorisation sur les nouveaux services Azure (identités de charge de travail) que vous ajoutez à l’application web, suivez les recommandations suivantes :

  • Utilisez des identités managées pour chaque nouveau service. Chaque service indépendant doit avoir sa propre identité et utiliser des identités managées pour l’authentification de service à service. Les identités managées éliminent le besoin de gérer les informations d’identification dans votre code et réduisent le risque de fuite d’informations d’identification. Elles vous aident à éviter de placer des informations sensibles comme des chaîne de connexion dans vos fichiers de code ou de configuration.

  • Octroyez des privilèges minimum à chaque nouveau service. Attribuez uniquement les autorisations nécessaires à chaque nouvelle identité du service. Par exemple, si une identité doit uniquement envoyer (push) à un registre de conteneurs, ne lui accordez pas d’autorisations d’extraction. Passez en revue ces autorisations régulièrement et ajustez-les si nécessaire. Utilisez différentes identités pour différents rôles, tels que le déploiement et l’application. Cela limite les dommages potentiels si une identité est compromise.

  • Adoptez l’infrastructure en tant que code (IaC). Utilisez Bicep ou des outils IaC similaires pour définir et gérer vos ressources cloud. IaC garantit une application cohérente des configurations de sécurité dans vos déploiements et vous permet de contrôler la version de votre configuration d’infrastructure.

Pour configurer l’authentification et l’autorisation sur les utilisateurs (identités utilisateur), suivez ces recommandations :

  • Accordez aux utilisateurs des privilèges minimum. Comme pour les services, assurez-vous que les utilisateurs disposent uniquement des autorisations nécessaires pour effectuer leurs tâches. Examinez et ajustez régulièrement ces autorisations.

  • Effectuez des audits de sécurité réguliers. Examinez et auditez régulièrement votre configuration de sécurité. Recherchez les mauvaises configurations ou les autorisations inutiles et remédiez-y immédiatement.

L’implémentation de référence utilise IaC pour affecter des identités managées à des services ajoutés et des rôles spécifiques à chaque identité. Il définit les rôles et l’accès aux autorisations pour le déploiement (containerRegistryPushRoleId), le propriétaire de l’application (containerRegistryPushRoleId) et l’application Container Apps (containerRegistryPullRoleId). L’exemple suivant illustre le code.

roleAssignments: \[
    {
    principalId: deploymentSettings.principalId
    principalType: deploymentSettings.principalType
    roleDefinitionIdOrName: containerRegistryPushRoleId
    }
    {
    principalId: ownerManagedIdentity.outputs.principal_id
    principalType: 'ServicePrincipal'
    roleDefinitionIdOrName: containerRegistryPushRoleId
    }
    {
    principalId: appManagedIdentity.outputs.principal_id
    principalType: 'ServicePrincipal'
    roleDefinitionIdOrName: containerRegistryPullRoleId
    }
\]

L’implémentation de référence attribue l’identité managée comme nouvelle identité Container Apps lors du déploiement :

module renderingServiceContainerApp 'br/public:avm/res/app/container-app:0.1.0' = {
  name: 'application-rendering-service-container-app'
  scope: resourceGroup()
  params: {
    // Other parameters omitted for brevity.
    managedIdentities: {
      userAssignedResourceIds: [
        managedIdentity.id
      ]
    }
  }
}

Configurez la mise à l’échelle automatique indépendante

Le modèle d’application web moderne commence par fractionner l’architecture monolithique et introduit le découplage des services. Le découplage d’une architecture d’application web vous permet de mettre à l’échelle des services découplés indépendamment. La mise à l’échelle des services Azure pour prendre en charge un service d’application web indépendant, plutôt qu’une application web entière, optimise les coûts de mise à l’échelle tout en répondant aux demandes. Pour la mise à l’échelle automatique de conteneurs, suivez ces recommandations :

  • Utilisez des service sans état. Vérifiez que vos services sont sans état. Si votre application .NET contient un état de session in-process, externalisez-la vers un cache distribué tel que Redis ou une base de données comme SQL Server.

  • Configurer des règles de mise à l’échelle automatique. Utilisez les configurations de mise à l’échelle automatique qui offrent le contrôle le plus économique sur vos services. Pour les services conteneurisés, la mise à l’échelle basée sur les événements, telle que Kubernetes Event-Driven Autoscaler (KEDA), fournit souvent un contrôle granulaire qui vous permet de mettre à l’échelle en fonction des métriques d’événement. Container Apps et AKS prennent en charge KEDA. Pour les services qui ne prennent pas en charge KEDA, tels qu’App Service, utilisez les fonctionnalités de mise à l’échelle automatique fournies par la plateforme. Ces fonctionnalités incluent souvent la mise à l’échelle basée sur des règles basées sur des métriques ou le trafic HTTP.

  • Configurez le nombre minimal de réplicas. Pour éviter un démarrage à froid, configurez les paramètres de mise à l’échelle automatique pour conserver un minimum d’un réplica. Un démarrage à froid se produit lorsque vous initialisez un service à partir d’un état arrêté, ce qui crée souvent une réponse différée. Si la réduction des coûts est une priorité et que vous pouvez tolérer des retards de démarrage à froid, définissez le nombre minimal de réplicas sur 0 lorsque vous configurez la mise à l’échelle automatique.

  • Configurez une période de recharge. Appliquez une période de recharge appropriée pour introduire un délai entre les événements de mise à l’échelle. L’objectif est d’éviter des activités de mise à l’échelle excessives déclenchées par des pics de charge temporaires.

  • Configurez la mise à l’échelle basée sur la file d’attente. Si votre application utilise une file d’attente de messages comme Service Bus, configurez vos paramètres de mise à l’échelle automatique pour qu’ils s’adaptent en fonction de la longueur de la file d’attente avec des messages de demande. Le scaler vise à maintenir un réplica du service pour chaque message N dans la file d’attente (arrondi).

Par exemple, l’implémentation de référence utilise le scaler KEDA Service Bus pour mettre à l’échelle l’application conteneur en fonction de la longueur de la file d’attente. Met service-bus-queue-length-rule à l’échelle le service en fonction de la longueur d’une file d’attente Service Bus spécifiée. Le paramètre messageCount est défini sur 10, ainsi le scaler a un réplica de service pour tous les 10 messages de la file d’attente. Les paramètres scaleMaxReplicas et scaleMinReplicas définissent le nombre maximal et minimal de réplicas pour le service. Le queue-connection-string secret, qui contient le chaîne de connexion de la file d’attente Service Bus, est récupéré à partir d’Azure Key Vault. Ce secret est utilisé pour authentifier le scaler auprès de Service Bus.

scaleRules: [
  {
    name: 'service-bus-queue-length-rule'
    custom: {
      type: 'azure-servicebus'
      metadata: {
        messageCount: '10'
        namespace: renderRequestServiceBusNamespace
        queueName: renderRequestServiceBusQueueName
      }
      auth: [
        {
          secretRef: 'render-request-queue-connection-string'
          triggerParameter: 'connection'
        }
      ]
    }
  }
]

scaleMaxReplicas: 5
scaleMinReplicas: 0

Conteneuriser le déploiement d’un service

Dans un déploiement conteneurisé, toutes les dépendances requises par l’application sont encapsulées dans une image légère qui peut être déployée de manière fiable sur un large éventail d’hôtes. Pour conteneuriser le déploiement, suivez ces recommandations :

  • Identifiez les limites de domaine. Commencez par identifier les limites de domaine dans votre application monolithique. Cela vous aide à déterminer quelles parties de l’application peuvent être extraites dans des services distincts.

  • Créez des images Docker. Lorsque vous créez des images Docker pour vos services .NET, utilisez des images de base cselées. Ces images contiennent uniquement l’ensemble minimal de packages nécessaires pour que .NET s’exécute, ce qui réduit la taille du package et la surface d’attaque.

  • Utilisez Dockerfiles multiphases. Implémentez des fichiers Dockerfile multiphases pour séparer les ressources au moment de la génération de l’image conteneur runtime. L’utilisation de ce type de fichier permet de maintenir vos images de production petites et sécurisées.

  • Exécutez en tant qu’utilisateur non-root. Exécutez vos conteneurs .NET en tant qu’utilisateur non-root (via le nom d’utilisateur ou UID $APP_UID) pour vous aligner sur le principe du privilège minimum. Cela limite les effets potentiels d’un conteneur compromis.

  • Écouter sur le port 8080. Lorsque vous exécutez des conteneurs en tant qu’utilisateur non-root, configurez votre application pour écouter le port 8080. Il s’agit d’une convention commune pour les utilisateurs non-roots.

  • Encapsuler les dépendances. Vérifiez que toutes les dépendances de l’application sont encapsulées dans l’image conteneur Docker. L’encapsulation vous permet de déployer de manière fiable l’application sur un large éventail d’hôtes.

  • Choisir les images de base appropriées. L’image de base que vous choisissez dépend de votre environnement de déploiement. Si vous effectuez un déploiement sur Container Apps, par exemple, vous devez utiliser des images Docker Linux.

L’implémentation de référence utilise un processus de génération multiphases. Les phases initiales compilent et créent l’application à l’aide d’une image complète du kit SDK (mcr.microsoft.com/dotnet/sdk:8.0-jammy). L’image du runtime finale est créée à partir de l’image de base chiseled, ce qui exclut le kit SDK et les artefacts de génération. Le service s’exécute en tant qu’utilisateur non-racine (USER $APP_UID) et expose le port 8080. Les dépendances requises pour que l’application fonctionne sont incluses dans l’image Docker, comme le montrent les commandes permettant de copier des fichiers projet et de restaurer des packages. L’utilisation d’images linux (mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled) garantit la compatibilité avec Container Apps, ce qui nécessite des conteneurs Linux pour le déploiement.

# Build in a separate stage to avoid copying the SDK into the final image.
FROM mcr.microsoft.com/dotnet/sdk:8.0-jammy AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src

# Restore packages.
COPY ["Relecloud.TicketRenderer/Relecloud.TicketRenderer.csproj", "Relecloud.TicketRenderer/"]
COPY ["Relecloud.Messaging/Relecloud.Messaging.csproj", "Relecloud.Messaging/"]
COPY ["Relecloud.Models/Relecloud.Models.csproj", "Relecloud.Models/"]
RUN dotnet restore "./Relecloud.TicketRenderer/Relecloud.TicketRenderer.csproj"

# Build and publish.
COPY . .
WORKDIR "/src/Relecloud.TicketRenderer"
RUN dotnet publish "./Relecloud.TicketRenderer.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

# Chiseled images contain only the minimal set of packages needed for .NET 8.0.
FROM mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled AS final
WORKDIR /app
EXPOSE 8080

# Copy the published app from the build stage.
COPY --from=build /app/publish .

# Run as nonroot user.
USER $APP_UID
ENTRYPOINT ["dotnet", "./Relecloud.TicketRenderer.dll"]

Déployer l’implémentation de référence

Déployez l’implémentation de référence du modèle d’application web moderne pour .NET. Le référentiel contient des instructions pour le développement et le déploiement en production. Après avoir déployé l’implémentation, vous pouvez simuler et observer des modèles de conception.

Le diagramme suivant illustre l’architecture de l’implémentation de référence :

Diagramme montrant l’architecture de l’implémentation de référence.

Téléchargez un fichier Visio de cette architecture.