Modifier

Partager via


Modèle d’application web moderne pour .NET

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

Cet article vous 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 permet d’optimiser les domaines à forte demande d’une application web. Il fournit des conseils détaillés pour dissocier ces derniers, 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 permet également le contrôle indépendant de la version des différents composants de l’application.

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

Cet article contient l’architecture, le code et les instructions 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 d’architecture : Découvrez comment modulariser les composants d’application web et sélectionner les solutions PaaS (Platform as a Service) appropriées.
  • Conseils en matière de code : implémentez quatre modèles de conception pour optimiser les composants découplés : Figuier étrangleur, Nivellement de la charge basé sur une file d’attente, consommateurs concurrents et surveillance des points de terminaison d’intégrité.
  • Conseils en matière 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 (voir la Figure 1).

Diagramme montrant l’architecture de référence du modèle d’application web moderne.Figure 1. Éléments architecturaux essentiels 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. Une deuxième région requiert la configuration de l’équilibreur de charge pour acheminer le trafic vers cette dernière afin de 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, comme 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 de type hub pour les gérer en toute sécurité (voir la figure 2).

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

Découpler 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 plus petits, indépendants, 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. Le découplage de ces services et la conversion des tâches de traitement d’opérations synchrones en opérations asynchrones permettent une gestion plus efficace des ressources, favorisent les déploiements indépendants et réduisent le risque d’impact sur d’autres parties de l’application lors de mises à jour ou de 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 souhaitez extraire. Consultez la section Sélectionnez les services Azure appropriés pour obtenir des conseils.

  • Dissocier les services d’application web. Définissez des interfaces et des API claires pour permettre l’interaction des services de l’application web récemment extraits 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 connaître les stratégies de mise en œuvre spécifiques et les modèles de conception à utiliser au cours de ce processus d’extraction, reportez-vous à la section Instructions en matière de code.

  • Utiliser un stockage indépendant pour les services découplés. Chaque service découplé doit posséder son propre magasin de données isolé pour faciliter le contrôle de version, le déploiement, la mise à l’échelle et le maintien de 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 communique l’URL où les images de ticket ont été générées vers 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 élément 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 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
    Service Bus Choisissez Service Bus pour une livraison fiable, ordonnée et éventuellement transactionnelle de messages à valeur élevée dans les applications d’entreprise.
    Event Grid 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 événements petits et indépendants (comme les modifications d’état des ressources) doivent être routés vers les abonnés dans un modèle d’abonnement à faible latence et à publication.
    Event Hubs Choisissez Event Hubs pour l’ingestion de données massives et à haut débit, telles que 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 parties de votre application que vous souhaitez conteneuriser, il vous faut une plateforme d’application qui prend en charge les conteneurs. Utilisez les conseils de la section Choisir un service de conteneur Azure pour 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é.
    Web Apps pour conteneurs Choisissez Web App pour conteneurs sur App Service pour l’expérience PaaS la plus simple.
  • Implémenter un référentiel de conteneurs. Lors de l’utilisation d’un service de calcul basé sur un conteneur, il est nécessaire d’avoir 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. Utilisez l’introduction aux registres de conteneurs dans les conseils Azure pour 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.

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

  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 sur la partie producteur du service découplé pour gérer le flux de messages de manière asynchrone à l’aide d’une file d’attente.

  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, notamment 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 en conformité avec un ou plusieurs piliers de Well-Architected Framework (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 Producteur de services découplés RE :06
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 & service découplé RE :07
RE :10
OE :07
PE :05
Modèle de nouvelle tentative Application web principale & service découplé RE :07

Implémenter le modèle Figuier étrangleur

Utilisez le modèle Figuier étrangleur 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 /users point de terminaison dans votre application monolithique et que vous avez déplacé cette fonctionnalité vers le service découplé, la couche de routage dirige toutes les requêtes vers /users 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és et un déploiement par étapes 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 fur et à mesure que vous acquérez de la confiance dans sa stabilité et ses performances. 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. À mesure que le nouveau service fait la preuve de sa fiabilité et de ses performances, il peut finalement prendre en charge l’ensemble de la fonctionnalité de rendu des tickets du monolithe, achevant ainsi 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é, il faut s’assurer que le nouveau service peut traiter les demandes de manière indépendante lorsqu’on y accède par 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 = true ReceiveMode = ServiceBusReceiveMode.PeekLock garantit que les messages ne sont traités qu’une seule fois et peuvent être retraités en cas d’échec (voir le code suivant).

    // 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 vous recevez des messages à partir d’une file d’attente, assurez-vous que votre 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 améliore la fiabilité par rapport aux méthodes de suppression prioritaire. 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 traitables 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. L’existence de plusieurs consommateurs parallèles signifie qu’ils sont susceptibles de traiter les messages dans le désordre.

  • Mise à l’échelle en fonction de la longueur de la file d’attente. Les services qui consomment des messages provenant d’une file d’attente doivent s’adapter automatiquement à la longueur de cette dernière. La mise à l’échelle automatique basée sur la mise à l’échelle permet un traitement efficace des pics de 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. Cela facilite la mise à l’échelle et l’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 ont été traités sans erreur.
  • ReceiveMode : utilise le mode PeekLock et renvoie les messages s’ils ne sont pas traités.
  • MaxConcurrentCalls : définissez sur 1 pour gérer un message à la fois.
  • PrefetchCount : définissez sur 0 pour éviter les messages de prérécupération.

Le processeur enregistre les détails du traitement des messages, ce qui facilite la résolution des problèmes et la surveillance. Il capture les erreurs de désérialisation et route les messages non valides vers une file d’attente de lettres mortes, ce qui empêche 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 le middleware de contrôle d’intégrité ASP.NET Core pour exposer les points de terminaison de contrôle d’intégrité, à l’aide de la méthode AddHealthChecks() sur l’objet builder.Services. Le code valide la disponibilité des dépendances clés, des Stockage Blob Azure et de la file d’attente Service Bus avec les méthodes et AddAzureServiceBusQueue() les AddAzureBlobStorage() 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 probe liveness pour atteindre le point de terminaison /health 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 demandes réalisées aux systèmes de messagerie et à celles é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 commet le nombre maximal de nouvelles tentatives, le délai entre elles et le délai maximal.

  • Utilisez un backoff exponentiel. Implémentez une stratégie d’interruption exponentielle pour les tentatives de nouvelle tentative. Cela implique d’augmenter le temps entre chaque nouvelle tentative de manière exponentielle, pour 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 avec 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 de manière plus efficace avec moins une configuration moins poussée de votre part. 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 de MessageBusOptions pour configurer les paramètres de nouvelle tentative tels que MaxRetries, Delay, MaxDelay 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 telle que 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 des messages, implémentez des stratégies de gestion des messages qui prennent en charge les nouvelles tentatives sans perte de données, notamment l’utilisation de modes « peek-lock » lorsqu’ils 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 l’utilisent pour afficher une arborescence d’activités et de journaux sur différents services, cruciales 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 les vôtres avec System.Diagnostics.ActivityListener.

  • 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 mesures courantes, les traces, les journaux et les exceptions du runtime .NET et d’ASP.NET Core. Incluez d’autres packages d’instrumentation OpenTelemetry pour les clients SQL, Redis et Azure SDK.

  • Surveillance et analyse. Après la configuration, 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é parente sont inclus, ce qui permet à Application Insights de fournir une visibilité de la trace de bout en bout à travers les limites HTTP et Service Bus. Utilisez cette configuration pour surveiller et analyser efficacement 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 méthode builder.Logging.AddOpenTelemetry achemine toute la journalisation via OpenTelemetry, ce qui garantit un suivi et une journalisation cohérents dans l’ensemble de l’application. En inscrivant les services OpenTelemetry avec builder.Services.AddOpenTelemetry, 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 le biais WithMetrics et WithTracing, en activant 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.

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.

Configuration 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 de nouveaux services Azure (identités de charge de travail) que vous ajoutez à l’application web, suivez ces recommandations :

  • 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 dont ils ont besoin 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 () (containerRegistryPullRoleIdvoir le code suivant).

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 en tant que nouvelle identité Container Apps au déploiement (consultez le code suivant).

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, comme Kubernetes Event-Driven Autoscaler (KEDA), fournit souvent un contrôle granulaire, ce 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 elle-même. Ces fonctionnalités incluent souvent la mise à l’échelle en fonction des règles basées sur des métriques ou du 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. On parle de démarrage à froid lorsqu’on initialise un service à partir d’un état arrêté, ce qui entraîne souvent une réponse différée. Si la réduction des coûts constitue une priorité et que vous pouvez tolérer les retards d’un démarrage à froid, définissez le nombre minimal de réplicas sur 0 lors de la configuration de 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

La conteneurisation implique que toutes les dépendances nécessaires au fonctionnement de 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 au sein de votre application monolithique. Cela permet de déterminer quelles parties de l’application vous pouvez extraire dans des services distincts.

  • Créez des images Docker. Lors de la création d’images Docker pour vos services .NET, utilisez des images épurées. Ces images contiennent uniquement l’ensemble minimal de packages nécessaires pour que .NET s’exécute, ce qui réduit à la fois 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. Vous pouvez ainsi gardez 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-racine (par le biais du nom d’utilisateur ou de l’UID, $APP_UID) pour s’aligner sur le principe de moindre privilège. Cela limite les effets potentiels d’un conteneur compromis.

  • Écouter sur le port 8080. Lors de l’exécution en tant qu’utilisateur non-racine, configurez votre application pour écouter sur le port 8080. Il s’agit d’une convention commune pour les utilisateurs non-racine.

  • Encapsuler les dépendances. Vérifiez que toutes les dépendances nécessaires au fonctionnement de l’application sont encapsulées dans l’image conteneur Docker. L’encapsulation permet le déploiement fiable de 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 nécessaires au fonctionnement de l’application 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 non-root 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 le déploiement, vous pouvez simuler et observer les modèles de conception.

Diagramme montrant l’architecture de l’implémentation de référence.Figure 3 : Architecture de l’implémentation de référence. Téléchargez un fichier Visio de cette architecture.