Questo articolo illustra come implementare il modello di app Web moderna. Il modello Di app Web moderna definisce come modernizzare le app Web nel cloud e introdurre un'architettura orientata ai servizi. Il modello di app Web moderna fornisce un'architettura prescrittiva, codice e linee guida di configurazione che si allineano ai principi di Azure Well-Architected Framework e si basa sul modello Reliable Web App.
Perché usare il modello di app Web moderna?
Il modello di app Web moderna consente di ottimizzare le aree a domanda elevata di un'app Web. Offre indicazioni dettagliate per separare queste aree, abilitando la scalabilità indipendente per l'ottimizzazione dei costi. Questo approccio consente di allocare risorse dedicate a componenti critici, migliorando le prestazioni complessive. Il disaccoppiamento dei servizi separabili può migliorare l'affidabilità impedendo che i rallentamenti in una parte dell'app influiscano sugli altri. Il disaccoppiamento abilita anche il controllo delle versioni dei singoli componenti dell'app in modo indipendente.
Come implementare il modello di app Web moderna
Questo articolo contiene informazioni sull'architettura, il codice e la configurazione per implementare il modello di app Web moderna. Usare i collegamenti seguenti per passare alle indicazioni necessarie:
- Indicazioni sull'architettura: informazioni su come modularizzare i componenti delle app Web e selezionare le soluzioni PaaS (Platform as a Service) appropriate.
- Linee guida sul codice: implementare quattro modelli di progettazione per ottimizzare i componenti disaccoppiati: Strangler Fig, Livellamento del carico basato su coda, Consumer concorrenti e Modelli di monitoraggio degli endpoint di integrità.
- Linee guida per la configurazione: configurare l'autenticazione, l'autorizzazione, la scalabilità automatica e la containerizzazione per i componenti disaccoppiati.
Suggerimento
È disponibile un'implementazione di riferimento (app di esempio) del modello di app Web moderna. Rappresenta lo stato finale dell'implementazione dell'app Web moderna. Si tratta di un'app Web di livello di produzione che include tutti gli aggiornamenti di codice, architettura e configurazione descritti in questo articolo. Distribuire e usare l'implementazione di riferimento per guidare l'implementazione del modello di app Web moderna.
Linee guida per l'architettura
Il modello di app Web moderna si basa sul modello Reliable Web App. Richiede alcuni componenti dell'architettura aggiuntivi da implementare. È necessaria una coda di messaggi, una piattaforma contenitore, un archivio dati del servizio disaccoppiato e un registro contenitori (vedere la figura 1).
Figura 1. Elementi architettonici essenziali del modello di app Web moderna.
Per un obiettivo di livello di servizio (SLO) superiore, è possibile aggiungere una seconda area all'architettura dell'app Web. Una seconda area richiede di configurare il servizio di bilanciamento del carico per instradare il traffico alla seconda area per supportare una configurazione attiva-attiva o attiva-passiva. Usare una topologia di rete hub-spoke per centralizzare e condividere risorse, ad esempio un firewall di rete. Accedere al repository di contenitori tramite la rete virtuale hub. Se si dispone di macchine virtuali, aggiungere un bastion host alla rete virtuale hub per gestirli in modo sicuro (vedere la figura 2).
Figura 2. Architettura del modello di app Web moderna con seconda area e topologia di rete hub-spoke.
Architettura disaccoppiamento
Per implementare il modello di app Web moderna, è necessario separare l'architettura dell'app Web esistente. La separazione dell'architettura comporta la suddivisione di un'applicazione monolitica in servizi più piccoli e indipendenti, ognuno responsabile di una funzionalità o di una funzionalità specifica. Questo processo comporta la valutazione dell'app Web corrente, la modifica dell'architettura e infine l'estrazione del codice dell'app Web in una piattaforma contenitore. L'obiettivo è identificare ed estrarre sistematicamente i servizi dell'applicazione che traggono vantaggio dalla separazione. Per separare l'architettura, seguire queste indicazioni:
Identificare i limiti del servizio. Applicare i principi di progettazione basati su dominio per identificare i contesti delimitati all'interno dell'applicazione monolitica. Ogni contesto delimitato rappresenta un limite logico e può essere un candidato per un servizio separato. I servizi che rappresentano funzioni aziendali distinte e hanno meno dipendenze sono buoni candidati per la disaccoppiamento.
Valutare i vantaggi del servizio. Concentrarsi sui servizi che traggono vantaggio dalla scalabilità indipendente. Separare questi servizi e convertire le attività di elaborazione da operazioni sincrone a quelle asincrone consente una gestione delle risorse più efficiente, supporta distribuzioni indipendenti e riduce il rischio di influire su altre parti dell'applicazione durante gli aggiornamenti o le modifiche. Ad esempio, è possibile separare l'estrazione dell'ordine dall'elaborazione degli ordini.
Valutare la fattibilità tecnica. Esaminare l'architettura corrente per identificare vincoli tecnici e dipendenze che potrebbero influire sul processo di disaccoppiamento. Pianificare la modalità di gestione e condivisione dei dati tra i servizi. I servizi disaccoppiati devono gestire i propri dati e ridurre al minimo l'accesso diretto al database attraverso i limiti del servizio.
Distribuire i servizi di Azure. Selezionare e distribuire i servizi di Azure necessari per supportare il servizio app Web da estrarre. Usare la sezione Selezionare la sezione dei servizi di Azure appropriata per indicazioni.
Separare i servizi dell'app Web. Definire interfacce e API chiare per i servizi di app Web appena estratti per interagire con altre parti del sistema. Progettare una strategia di gestione dei dati che consente a ogni servizio di gestire i propri dati garantendo al tempo stesso coerenza e integrità. Per strategie di implementazione specifiche e modelli di progettazione da usare durante questo processo di estrazione, vedere la sezione Linee guida sul codice.
Usare l'archiviazione indipendente per i servizi disaccoppiati. Ogni servizio disaccoppiato deve avere un proprio archivio dati isolato per facilitare il controllo delle versioni indipendente, la distribuzione, la scalabilità e mantenere l'integrità dei dati. Ad esempio, l'implementazione di riferimento separa il servizio di rendering dei ticket dall'API Web ed elimina la necessità del servizio di accedere al database dell'API. Il servizio comunica invece l'URL in cui le immagini del ticket sono state generate all'API Web tramite un messaggio di bus di servizio di Azure e l'API mantiene il percorso del database.
Implementare pipeline di distribuzione separate per ogni servizio disaccoppiato. Le pipeline di distribuzione separate consentono l'aggiornamento di ogni servizio al proprio ritmo. Se team o organizzazioni diverse all'interno dell'azienda sono proprietari di servizi diversi, la presenza di pipeline di distribuzione separate offre a ogni team il controllo sulle proprie distribuzioni. Usare strumenti di integrazione continua e recapito continuo (CI/CD) come Jenkins, GitHub Actions o Azure Pipelines per configurare queste pipeline.
Rivedere i controlli di sicurezza. Assicurarsi che i controlli di sicurezza vengano aggiornati per tenere conto della nuova architettura, incluse le regole del firewall e i controlli di accesso.
Selezionare i servizi di Azure corretti
Per ogni servizio di Azure nell'architettura, vedere la guida pertinente al servizio di Azure in Well-Architected Framework. Per il modello di app Web moderna, è necessario un sistema di messaggistica per supportare la messaggistica asincrona, una piattaforma applicativa che supporta la containerizzazione e un repository di immagini del contenitore.
Scegliere una coda di messaggi. Una coda di messaggi è una parte importante delle architetture orientate ai servizi. Separa i mittenti e i ricevitori dei messaggi per abilitare la messaggistica asincrona. Usare le indicazioni sulla scelta di un servizio di messaggistica di Azure per scegliere un sistema di messaggistica di Azure che supporti le esigenze di progettazione. Azure include tre servizi di messaggistica: Griglia di eventi di Azure, Hub eventi di Azure e bus di servizio. Iniziare con bus di servizio come scelta predefinita e usare le altre due opzioni se bus di servizio non soddisfa le proprie esigenze.
Service Caso d'uso Bus di servizio Scegliere bus di servizio per il recapito affidabile, ordinato ed eventualmente transazionale di messaggi di alto valore nelle applicazioni aziendali. Griglia di eventi Scegliere Griglia di eventi quando è necessario gestire in modo efficiente un numero elevato di eventi discreti. Griglia di eventi è scalabile per le applicazioni guidate dagli eventi in cui è necessario instradare molti eventi di piccole dimensioni, ad esempio le modifiche allo stato delle risorse, ai sottoscrittori in un modello di pubblicazione-sottoscrizione a bassa latenza. Hub eventi di Scegliere Hub eventi per l'inserimento di dati con velocità effettiva elevata elevata, ad esempio dati di telemetria, log o analisi in tempo reale. Hub eventi è ottimizzato per scenari di streaming in cui i dati in blocco devono essere inseriti ed elaborati continuamente. Implementare un servizio contenitore. Per le parti dell'applicazione da inserire in contenitori, è necessaria una piattaforma applicativa che supporta i contenitori. Usare il materiale sussidiario Scegliere un servizio Azure Container per prendere decisioni. Azure include tre servizi contenitore principali: App azure Container, servizio Azure Kubernetes (AKS) e servizio app Azure. Iniziare con App contenitore come scelta predefinita e usare le altre due opzioni se App contenitore non soddisfa le proprie esigenze.
Service Caso d'uso App contenitore Scegliere App contenitore se è necessaria una piattaforma serverless che ridimensiona e gestisce automaticamente i contenitori nelle applicazioni guidate dagli eventi. servizio Azure Kubernetes Scegliere il servizio Azure Kubernetes se è necessario un controllo dettagliato sulle configurazioni di Kubernetes e sulle funzionalità avanzate per la scalabilità, la rete e la sicurezza. App Web per contenitore Scegliere App Web per contenitori in servizio app per l'esperienza PaaS più semplice. Implementare un repository di contenitori. Quando si usa un servizio di calcolo basato su contenitori, è necessario disporre di un repository per archiviare le immagini del contenitore. È possibile usare un registro contenitori pubblico, ad esempio Docker Hub o un registro gestito, ad esempio Registro Azure Container. Usare il materiale sussidiario Introduzione ai registri contenitori in Azure per prendere decisioni.
Linee guida per il codice
Per separare ed estrarre correttamente un servizio indipendente, è necessario aggiornare il codice dell'app Web con i modelli di progettazione seguenti: il modello Strangler Fig, il modello di livellamento del carico basato su coda, il modello Consumer concorrenti, il modello Monitoraggio endpoint integrità e il modello Di ripetizione dei tentativi.
Figura 3. Ruolo dei modelli di progettazione.
Modello Fig strangler: il modello Strangler Fig esegue la migrazione incrementale delle funzionalità da un'applicazione monolitica al servizio disaccoppiato. Implementare questo modello nell'app Web principale per eseguire gradualmente la migrazione delle funzionalità ai servizi indipendenti indirizzando il traffico in base agli endpoint.
Modello di livellamento del carico basato su coda: il modello di livellamento del carico basato su coda gestisce il flusso di messaggi tra il producer e il consumer usando una coda come buffer. Implementare questo modello nella parte producer del servizio disaccoppiato per gestire il flusso dei messaggi in modo asincrono usando una coda.
Modello Consumer concorrenti: il modello Consumer concorrenti consente a più istanze del servizio disaccoppiato di leggere in modo indipendente dalla stessa coda di messaggi e competere per elaborare i messaggi. Implementare questo modello nel servizio disaccoppiato per distribuire le attività tra più istanze.
Modello di monitoraggio degli endpoint di integrità: il modello di monitoraggio degli endpoint di integrità espone gli endpoint per il monitoraggio dello stato e dell'integrità di diverse parti dell'app Web. (4a) Implementare questo modello nell'app Web principale. (4b) Implementarlo anche nel servizio disaccoppiato per tenere traccia dell'integrità degli endpoint.
Modello di ripetizione dei tentativi: il modello di ripetizione dei tentativi gestisce gli errori temporanei ritentando le operazioni che potrebbero non riuscire in modo intermittente. (5a) Implementare questo modello in tutte le chiamate in uscita ad altri servizi di Azure nell'app Web principale, ad esempio le chiamate alla coda di messaggi e agli endpoint privati. (5b) Implementare anche questo modello nel servizio disaccoppiato per gestire gli errori temporanei nelle chiamate agli endpoint privati.
Ogni modello di progettazione offre vantaggi che si allineano a uno o più pilastri di Well-Architected Framework (vedere la tabella seguente).
Schema progettuale | Percorso di implementazione | Affidabilità (RE) | Sicurezza (SE) | Ottimizzazione costi (CO) | Eccellenza operativa (OE) | Efficienza delle prestazioni (PE) | Supporto dei principi del framework ben progettato |
---|---|---|---|---|---|---|---|
Modello Fig strangler | App Web principale | ✔ | ✔ | ✔ | RE:08 CO:07 CO:08 OE:06 OE:11 |
||
Schema di livellamento del carico basato sulle code | Produttore di un servizio disaccoppiato | ✔ | ✔ | ✔ | RE:06 RE:07 CO:12 PE:05 |
||
Modello consumer concorrenti | Servizio disaccoppiato | ✔ | ✔ | ✔ | RE:05 RE:07 CO:05 CO:07 PE:05 PE:07 |
||
Modello di monitoraggio degli endpoint di integrità | App Web principale e servizio disaccoppiato | ✔ | ✔ | ✔ | RE:07 RE:10 OE:07 PE:05 |
||
Modello di ripetizione dei tentativi | App Web principale e servizio disaccoppiato | ✔ | RE:07 |
Implementare il modello Strangler Fig
Usare il modello Strangler Fig per eseguire gradualmente la migrazione delle funzionalità dalla codebase monolitica ai nuovi servizi indipendenti. Estrarre nuovi servizi dalla codebase monolitica esistente e modernizzare lentamente le parti critiche dell'app Web. Per implementare il modello Strangler Fig, seguire queste indicazioni:
Configurare un livello di routing. Nella codebase dell'app Web monolitica implementare un livello di routing che indirizza il traffico in base agli endpoint. Usare la logica di routing personalizzata in base alle esigenze per gestire regole business specifiche per indirizzare il traffico. Ad esempio, se si dispone di un
/users
endpoint nell'app monolitica e si è spostata tale funzionalità nel servizio disaccoppiato, il livello di routing indirizza tutte le richieste al/users
nuovo servizio.Gestire l'implementazione delle funzionalità. Usare le librerie di Gestione funzionalità .NET per implementare flag di funzionalità e implementazione a fasi per implementare gradualmente i servizi disaccoppiati. Il routing monolitico dell'app esistente deve controllare il numero di richieste ricevute dai servizi disaccoppiati. Iniziare con una piccola percentuale di richieste e aumentare l'utilizzo nel tempo man mano che si ottiene fiducia nella stabilità e nelle prestazioni. Ad esempio, l'implementazione di riferimento estrae la funzionalità di rendering dei ticket in un servizio autonomo, che può essere gradualmente introdotta per gestire una parte più ampia delle richieste di rendering dei ticket. Poiché il nuovo servizio dimostra l'affidabilità e le prestazioni, alla fine può assumere l'intera funzionalità di rendering dei ticket dal monolith, completando la transizione.
Usare un servizio di facciata (se necessario). Un servizio di facciata è utile quando una singola richiesta deve interagire con più servizi o quando si desidera nascondere la complessità del sistema sottostante dal client. Tuttavia, se il servizio disaccoppiato non dispone di API pubbliche, potrebbe non essere necessario un servizio di facciata. Nella codebase dell'app Web monolitica implementare un servizio di facciata per instradare le richieste al back-end appropriato (monolith o microservizio). Nel nuovo servizio disaccoppiato assicurarsi che il nuovo servizio possa gestire le richieste in modo indipendente quando si accede tramite la facciata.
Implementare il modello di livellamento del carico basato su coda
Implementare il modello di livellamento del carico basato su coda nella parte producer del servizio disaccoppiato per gestire in modo asincrono le attività che non necessitano di risposte immediate. Questo modello migliora la velocità di risposta e la scalabilità complessive del sistema usando una coda per gestire la distribuzione del carico di lavoro. Consente al servizio disaccoppiato di elaborare le richieste a una velocità coerente. Per implementare questo modello in modo efficace, attenersi alle indicazioni seguenti:
Usare l'accodamento messaggi non bloccante. Verificare che il processo che invia messaggi alla coda non blocchi altri processi durante l'attesa che il servizio disaccoppiato gestisca i messaggi nella coda. Se il processo richiede il risultato dell'operazione di disaccoppiata del servizio, avere un modo alternativo per gestire la situazione durante l'attesa del completamento dell'operazione in coda. Ad esempio, l'implementazione di riferimento usa bus di servizio e la
await
parola chiave con permessageSender.PublishAsync()
pubblicare in modo asincrono i messaggi nella coda senza bloccare il thread che esegue questo codice:// Asynchronously publish a message without blocking the calling thread await messageSender.PublishAsync(new TicketRenderRequestMessage(Guid.NewGuid(), ticket, null, DateTime.Now), CancellationToken.None);
Questo approccio garantisce che l'applicazione principale rimanga reattiva e possa gestire contemporaneamente altre attività, mentre il servizio disaccoppiato elabora le richieste in coda a una velocità gestibile.
Implementare la ripetizione e la rimozione dei messaggi. Implementare un meccanismo per ritentare l'elaborazione dei messaggi in coda che non possono essere elaborati correttamente. Se gli errori vengono mantenuti, questi messaggi devono essere rimossi dalla coda. Ad esempio, bus di servizio include funzionalità predefinite per la coda di tentativi e messaggi non recapitabili.
Configurare l'elaborazione dei messaggi idempotenti. La logica che elabora i messaggi dalla coda deve essere idempotente per gestire i casi in cui un messaggio potrebbe essere elaborato più volte. Ad esempio, l'implementazione di riferimento usa
ServiceBusClient.CreateProcessor
conAutoCompleteMessages = true
eReceiveMode = ServiceBusReceiveMode.PeekLock
per assicurarsi che i messaggi vengano elaborati una sola volta e possano essere rielaborati in caso di errore (vedere il codice seguente).// 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 });
Gestire le modifiche apportate all'esperienza. L'elaborazione asincrona può portare a attività che non vengono completate immediatamente. Gli utenti devono essere informati quando l'attività è ancora in fase di elaborazione per impostare le aspettative corrette ed evitare confusione. Usare segnali visivi o messaggi per indicare che un'attività è in corso. Consentire agli utenti di ricevere notifiche al termine dell'attività, ad esempio un messaggio di posta elettronica o una notifica push.
Implementare il modello Consumer concorrenti
Implementare il modello Consumer concorrenti nei servizi disaccoppiati per gestire le attività in ingresso dalla coda dei messaggi. Questo modello comporta la distribuzione di attività tra più istanze di servizi disaccoppiati. Questi servizi elaborano i messaggi dalla coda, migliorando il bilanciamento del carico e aumentando la capacità del sistema di gestire le richieste simultanee. Il modello Consumer concorrenti è efficace quando:
- La sequenza di elaborazione dei messaggi non è fondamentale.
- La coda rimane inalterata dai messaggi in formato non valido.
- L'operazione di elaborazione è idempotente, ovvero può essere applicata più volte senza modificare il risultato oltre l'applicazione iniziale.
Per implementare il modello Consumer concorrenti, seguire queste raccomandazioni:
Gestire messaggi simultanei. Quando si ricevono messaggi da una coda, assicurarsi che il sistema sia progettato per gestire più messaggi contemporaneamente. Impostare il numero massimo di chiamate simultanee su 1 in modo che un consumer separato gestisca ogni messaggio.
Disabilitare il prelettura. Disabilitare il prelettura dei messaggi in modo che i consumer recuperino i messaggi solo quando sono pronti.
Usare modalità di elaborazione dei messaggi affidabili. Usare una modalità di elaborazione affidabile, ad esempio PeekLock (o il relativo equivalente), che ritenta automaticamente i messaggi che non superano l'elaborazione. Questa modalità migliora l'affidabilità rispetto ai metodi di eliminazione-first. Se un ruolo di lavoro non riesce a gestire un messaggio, un altro deve essere in grado di elaborarlo senza errori, anche se il messaggio viene elaborato più volte.
Implementare la gestione degli errori. Instradare messaggi non elaborati o non elaborabili a una coda separata di messaggi non recapitabili. Questa progettazione impedisce l'elaborazione ripetitiva. Ad esempio, è possibile intercettare le eccezioni durante l'elaborazione dei messaggi e spostare il messaggio problematico nella coda separata.
Gestire i messaggi non in ordine. Progettare i consumer per elaborare i messaggi che arrivano fuori sequenza. Più consumer paralleli significa che potrebbero elaborare messaggi non in ordine.
Scala in base alla lunghezza della coda. I servizi che utilizzano messaggi da una coda devono essere ridimensionati automaticamente in base alla lunghezza della coda. La scalabilità automatica basata su scalabilità consente un'elaborazione efficiente dei picchi di messaggi in ingresso.
Usare una coda message-reply. Se il sistema richiede notifiche per l'elaborazione post-messaggio, configurare una coda di risposta o risposta dedicata. Questa configurazione divide la messaggistica operativa dai processi di notifica.
Usare i servizi senza stato. Prendere in considerazione l'uso di servizi senza stato per elaborare le richieste da una coda. Consente un facile ridimensionamento e un utilizzo efficiente delle risorse.
Configurare la registrazione. Integrare la registrazione e la gestione delle eccezioni specifiche all'interno del flusso di lavoro di elaborazione dei messaggi. Concentrarsi sull'acquisizione degli errori di serializzazione e sull'indirizzamento di questi messaggi problematici a un meccanismo di messaggi non recapitabili. Questi log forniscono informazioni utili per la risoluzione dei problemi.
Ad esempio, l'implementazione di riferimento usa il modello Consumer concorrenti in un servizio senza stato in esecuzione in App contenitore per elaborare le richieste di rendering dei ticket da una coda di bus di servizio. Configura un processore di accodamento con:
- Completamento automaticoMessages: completa automaticamente i messaggi se elaborati senza errori.
- ReceiveMode: usa la modalità PeekLock e recapita i messaggi se non vengono risolti.
- MaxConcurrentCalls: impostare su 1 per gestire un messaggio alla volta.
- PrefetchCount: impostare su 0 per evitare il prelettura dei messaggi.
Il processore registra i dettagli di elaborazione dei messaggi, che facilitano la risoluzione dei problemi e il monitoraggio. Acquisisce gli errori di deserializzazione e indirizza i messaggi non validi a una coda di messaggi non recapitabili, impedendo l'elaborazione ripetitiva dei messaggi difettosi. Il servizio viene ridimensionato a livello di contenitore, consentendo una gestione efficiente dei picchi dei messaggi in base alla lunghezza della coda.
// 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);
}
};
Implementare il modello di monitoraggio degli endpoint di integrità
Implementare il modello di monitoraggio degli endpoint di integrità nel codice dell'app principale e nel codice del servizio disaccoppiato per tenere traccia dell'integrità degli endpoint dell'applicazione. Gli agenti di orchestrazione come il servizio Azure Kubernetes o le app contenitore possono eseguire il polling di questi endpoint per verificare l'integrità del servizio e riavviare le istanze non integre. ASP.NET le app di base possono aggiungere middleware di controllo integrità dedicato per gestire in modo efficiente i dati sull'integrità degli endpoint e le dipendenze chiave. Per implementare il modello di monitoraggio degli endpoint di integrità, attenersi alle indicazioni seguenti:
Implementare i controlli di integrità. Usare ASP.NET middleware dei controlli di integrità di base per fornire endpoint di controllo dell'integrità.
Convalidare le dipendenze. Assicurarsi che i controlli di integrità convalidino la disponibilità delle dipendenze chiave, ad esempio il database, l'archiviazione e il sistema di messaggistica. Il pacchetto non Microsoft, AspNetCore.Diagnostics.HealthChecks, può implementare controlli delle dipendenze del controllo integrità per molte dipendenze delle app comuni.
Ad esempio, l'implementazione di riferimento usa ASP.NET middleware del controllo integrità principale per esporre gli endpoint di controllo integrità, usando il
AddHealthChecks()
metodo sull'oggettobuilder.Services
. Il codice convalida la disponibilità di dipendenze chiave, Archiviazione BLOB di Azure e bus di servizio coda con iAddAzureBlobStorage()
metodi eAddAzureServiceBusQueue()
che fanno parte delAspNetCore.Diagnostics.HealthChecks
pacchetto. App contenitore consente la configurazione dei probe di integrità monitorati per valutare se le app sono integre o in caso di necessità di riciclaggio.// 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");
Configurare le risorse di Azure. Configurare le risorse di Azure per usare gli URL di controllo integrità dell'app per verificare la disponibilità e la conformità. Ad esempio, l'implementazione di riferimento usa Bicep per configurare gli URL del controllo integrità per confermare la disponibilità e l'idoneità della risorsa di Azure. Probe di attività per raggiungere l'endpoint
/health
ogni 10 secondi dopo un ritardo iniziale di 2 secondi.probes: [ { type: 'liveness' httpGet: { path: '/health' port: 8080 } initialDelaySeconds: 2 periodSeconds: 10 } ]
Implementare il modello di ripetizione dei tentativi
Il modello Di ripetizione dei tentativi consente alle applicazioni di eseguire il ripristino da errori temporanei. Il modello Di ripetizione dei tentativi è fondamentale per il modello Reliable Web App, quindi l'app Web deve usare già il modello Di ripetizione dei tentativi. Applicare il modello Di ripetizione dei tentativi alle richieste ai sistemi di messaggistica e alle richieste rilasciate dai servizi disaccoppiati estratti dall'app Web. Per implementare il modello Di ripetizione dei tentativi, seguire queste indicazioni:
Configurare le opzioni di ripetizione dei tentativi. Quando si esegue l'integrazione con una coda di messaggi, assicurarsi di configurare il client responsabile delle interazioni con la coda con le impostazioni di ripetizione appropriate. Specificare i parametri, ad esempio il numero massimo di tentativi, il ritardo tra i tentativi e il ritardo massimo.
Usare il backoff esponenziale. Implementare una strategia di backoff esponenziale per i tentativi. Ciò significa aumentare il tempo tra ogni tentativo in modo esponenziale, che consente di ridurre il carico nel sistema durante periodi di alti tassi di errore.
Usare la funzionalità di ripetizione dei tentativi dell'SDK. Per i servizi con SDK specializzati, ad esempio bus di servizio o archiviazione BLOB, usare i meccanismi di ripetizione dei tentativi predefiniti. I meccanismi di ripetizione dei tentativi predefiniti sono ottimizzati per i casi d'uso tipici del servizio e possono gestire i nuovi tentativi in modo più efficace con meno configurazione necessaria nella propria parte. Ad esempio, l'implementazione di riferimento usa la funzionalità predefinita di ripetizione dei tentativi di bus di servizio SDK (
ServiceBusClient
eServiceBusRetryOptions
). L'oggettoServiceBusRetryOptions
recupera le impostazioni daMessageBusOptions
per configurare le impostazioni di ripetizione dei tentativi, ad esempio MaxRetries, Delay, MaxDelay e 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); });
Adottare librerie di resilienza standard per i client HTTP. Per le comunicazioni HTTP, integrare una libreria di resilienza standard, ad esempio Polly o
Microsoft.Extensions.Http.Resilience
. Queste librerie offrono meccanismi completi di ripetizione dei tentativi fondamentali per la gestione delle comunicazioni con servizi Web esterni.Gestire il blocco dei messaggi. Per i sistemi basati su messaggi, implementare strategie di gestione dei messaggi che supportano i tentativi senza perdita di dati, ad esempio usando le modalità "peek-lock" dove disponibili. Assicurarsi che i messaggi non riusciti vengano ritentati in modo efficace e spostati in una coda di messaggi non recapitabili dopo errori ripetuti.
Implementare la traccia distribuita
Man mano che le applicazioni diventano più orientate ai servizi e i relativi componenti sono separati, il monitoraggio del flusso di esecuzione tra i servizi è fondamentale. Il modello di app Web moderna usa Application Insights e Monitoraggio di Azure per ottenere visibilità sull'integrità e sulle prestazioni delle applicazioni tramite le API OpenTelemetry, che supportano la traccia distribuita.
La traccia distribuita tiene traccia di una richiesta utente mentre attraversa più servizi. Quando viene ricevuta una richiesta, viene contrassegnata con un identificatore di traccia, che viene passato ad altri componenti tramite intestazioni HTTP e bus di servizio proprietà durante la chiamata alle dipendenze. Le tracce e i log includono quindi sia l'identificatore di traccia che un identificatore di attività (o identificatore di intervallo), che corrisponde al componente specifico e alla relativa attività padre. Strumenti di monitoraggio come Application Insights lo usano per visualizzare un albero di attività e log in diversi servizi, fondamentale per il monitoraggio delle applicazioni distribuite.
Installare le librerie OpenTelemetry. Usare le librerie di strumentazione per abilitare la traccia e le metriche dai componenti comuni. Aggiungere la strumentazione personalizzata con
System.Diagnostics.ActivitySource
eSystem.Diagnostics.Activity
, se necessario. Usare le librerie di esportazione per restare in ascolto della diagnostica OpenTelemetry e registrarle in archivi permanenti. Utilizzare gli esportatori esistenti o crearne di personalizzati conSystem.Diagnostics.ActivityListener
.Configurare OpenTelemetry. Usare la distribuzione di Monitoraggio di Azure di OpenTelemetry (
Azure.Monitor.OpenTelemetry.AspNetCore
). Assicurarsi che esporta la diagnostica in Application Insights e includa la strumentazione predefinita per metriche, tracce, log ed eccezioni comuni dal runtime .NET e ASP.NET Core. Includere altri pacchetti di strumentazione OpenTelemetry per i client SQL, Redis e Azure SDK.Monitorare e analizzare. Dopo la configurazione, assicurarsi che i log, le tracce, le metriche e le eccezioni vengano acquisiti e inviati ad Application Insights. Verificare che siano inclusi gli identificatori di traccia, attività e attività padre, consentendo ad Application Insights di fornire visibilità di traccia end-to-end attraverso i limiti HTTP e bus di servizio. Usare questa configurazione per monitorare e analizzare in modo efficace le attività dell'applicazione in tutti i servizi.
L'esempio di app Web moderna usa la distribuzione di Monitoraggio di Azure di OpenTelemetry (Azure.Monitor.OpenTelemetry.AspNetCore
). Vengono usati altri pacchetti di strumentazione per i client SQL, Redis e Azure SDK. OpenTelemetry è configurato nel servizio di rendering di ticket di esempio di App Web moderna, come illustrato di seguito:
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.*");
});
Il builder.Logging.AddOpenTelemetry
metodo instrada tutte le registrazioni tramite OpenTelemetry, garantendo traccia e registrazione coerenti nell'applicazione. Registrando i servizi OpenTelemetry con builder.Services.AddOpenTelemetry
, l'applicazione viene configurata per raccogliere ed esportare la diagnostica, che vengono quindi inviate ad Application Insights tramite UseAzureMonitor
. Inoltre, la strumentazione client per i componenti come bus di servizio e i client HTTP viene configurata tramite WithMetrics
e WithTracing
, abilitando le metriche automatiche e la raccolta di tracce senza richiedere modifiche all'utilizzo client esistente, solo un aggiornamento alla configurazione.
Linee guida per la configurazione
Le sezioni seguenti forniscono indicazioni sull'implementazione degli aggiornamenti della configurazione. Ogni sezione è allineata a uno o più pilastri del framework ben progettato.
Impostazione | Affidabilità (RE) | Sicurezza (SE) | Ottimizzazione costi (CO) | Eccellenza operativa (OE) | Efficienza delle prestazioni (PE) | Supporto dei principi del framework ben progettato |
---|---|---|---|---|---|---|
Configurare l'autenticazione e l'autorizzazione | ✔ | ✔ | SE:05 OE:10 |
|||
Implementare la scalabilità automatica indipendente | ✔ | ✔ | ✔ | RE:06 CO:12 PE:05 |
||
Distribuire un servizio in contenitori | ✔ | ✔ | CO:13 PE:09 PE:03 |
Configurare l'autenticazione e l'autorizzazione
Per configurare l'autenticazione e l'autorizzazione in tutti i nuovi servizi di Azure (identità del carico di lavoro) aggiunti all'app Web, seguire queste indicazioni:
Usare le identità gestite per ogni nuovo servizio. Ogni servizio indipendente deve avere una propria identità e usare le identità gestite per l'autenticazione da servizio a servizio. Le identità gestite eliminano la necessità di gestire le credenziali nel codice e ridurre il rischio di perdita di credenziali. Consentono di evitare di inserire informazioni riservate come stringa di connessione nel codice o nei file di configurazione.
Concedere privilegi minimi a ogni nuovo servizio. Assegnare solo le autorizzazioni necessarie a ogni nuova identità del servizio. Ad esempio, se un'identità deve eseguire solo il push in un registro contenitori, non concedere autorizzazioni pull. Esaminare queste autorizzazioni regolarmente e modificarle in base alle esigenze. Usare identità diverse per ruoli diversi, ad esempio la distribuzione e l'applicazione. Questo limita il potenziale danno se un'identità viene compromessa.
Adottare l'infrastruttura come codice (IaC). Usare gli strumenti Bicep o IaC simili per definire e gestire le risorse cloud. IaC garantisce un'applicazione coerente delle configurazioni di sicurezza nelle distribuzioni e consente di controllare la versione della configurazione dell'infrastruttura.
Per configurare l'autenticazione e l'autorizzazione per gli utenti (identità utente), seguire queste indicazioni:
Concedere privilegi minimi agli utenti. Proprio come con i servizi, assicurarsi che agli utenti vengano concesse solo le autorizzazioni necessarie per eseguire le attività. Esaminare e modificare regolarmente queste autorizzazioni.
Eseguire controlli di sicurezza regolari. Esaminare e controllare regolarmente la configurazione della sicurezza. Cercare eventuali configurazioni errate o autorizzazioni non necessarie e correggerle immediatamente.
L'implementazione di riferimento usa IaC per assegnare identità gestite a servizi aggiunti e ruoli specifici a ogni identità. Definisce ruoli e autorizzazioni di accesso per la distribuzione (containerRegistryPushRoleId
), il proprietario dell'applicazione (containerRegistryPushRoleId
) e l'applicazione App contenitore () (containerRegistryPullRoleId
vedere il codice seguente).
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'implementazione di riferimento assegna l'identità gestita come nuova identità di App contenitore in fase di distribuzione (vedere il codice seguente).
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
]
}
}
}
Configurare la scalabilità automatica indipendente
Il modello di app Web moderna inizia a suddividere l'architettura monolitica e introduce il disaccoppiamento dei servizi. Quando si separa un'architettura di app Web, è possibile ridimensionare i servizi disaccoppiati in modo indipendente. Il ridimensionamento dei servizi di Azure per supportare un servizio app Web indipendente, anziché un'intera app Web, ottimizza i costi di ridimensionamento in base alle esigenze. Per ridimensionare automaticamente i contenitori, seguire queste indicazioni:
Usare i servizi senza stato. Verificare che i servizi siano senza stato. Se l'applicazione .NET contiene lo stato della sessione in-process, esternalizzarla a una cache distribuita come Redis o a un database come SQL Server.
Configurare le regole di scalabilità automatica. Usare le configurazioni di scalabilità automatica che forniscono il controllo più conveniente sui servizi. Per i servizi in contenitori, il ridimensionamento basato su eventi, ad esempio Kubernetes Event-Driven Autoscaler (KEDA), offre spesso un controllo granulare, che consente di ridimensionare in base alle metriche degli eventi. Le app contenitore e il servizio Azure Kubernetes supportano KEDA. Per i servizi che non supportano KEDA, ad esempio servizio app, usare le funzionalità di scalabilità automatica fornite dalla piattaforma stessa. Queste funzionalità includono spesso il ridimensionamento in base alle regole basate sulle metriche o sul traffico HTTP.
Configurare le repliche minime. Per evitare un avvio a freddo, configurare le impostazioni di scalabilità automatica per mantenere almeno una replica. Un avvio a freddo si verifica quando si inizializza un servizio da uno stato arrestato, che spesso crea una risposta ritardata. Se ridurre al minimo i costi è una priorità ed è possibile tollerare ritardi di avvio a freddo, impostare il numero minimo di repliche su 0 durante la configurazione della scalabilità automatica.
Configurare un periodo di raffreddamento. Applicare un periodo di raffreddamento appropriato per introdurre un ritardo tra gli eventi di ridimensionamento. L'obiettivo è prevenire attività di ridimensionamento eccessive attivate da picchi di carico temporanei.
Configurare il ridimensionamento basato su coda. Se l'applicazione usa una coda di messaggi come bus di servizio, configurare le impostazioni di scalabilità automatica in base alla lunghezza della coda con i messaggi di richiesta. Il scaler mira a mantenere una replica del servizio per ogni N messaggi nella coda (arrotondato per errotondare).
Ad esempio, l'implementazione di riferimento usa il scaler KEDA bus di servizio per ridimensionare l'app contenitore in base alla lunghezza della coda. Ridimensiona service-bus-queue-length-rule
il servizio in base alla lunghezza di una coda di bus di servizio specificata. Il messageCount
parametro è impostato su 10, quindi il scaler ha una replica del servizio per ogni 10 messaggi nella coda. I scaleMaxReplicas
parametri e scaleMinReplicas
impostano il numero massimo e minimo di repliche per il servizio. Il queue-connection-string
segreto, che contiene il stringa di connessione per la coda di bus di servizio, viene recuperato da Azure Key Vault. Questo segreto viene usato per autenticare il scaler nel bus di servizio.
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
Distribuire un servizio in contenitori
La containerizzazione significa che tutte le dipendenze per il funzionamento dell'app vengono incapsulate in un'immagine leggera che può essere distribuita in modo affidabile in un'ampia gamma di host. Per inserire in contenitori la distribuzione, seguire queste indicazioni:
Identificare i limiti del dominio. Per iniziare, identificare i limiti del dominio all'interno dell'applicazione monolitica. Ciò consente di determinare quali parti dell'applicazione è possibile estrarre in servizi separati.
Creare immagini Docker. Quando si creano immagini Docker per i servizi .NET, usare immagini di base non crittografate. Queste immagini contengono solo il set minimo di pacchetti necessari per l'esecuzione di .NET, riducendo al minimo le dimensioni del pacchetto e l'area di attacco.
Usare Dockerfile a più fasi. Implementare dockerfile a più fasi per separare gli asset in fase di compilazione dall'immagine del contenitore di runtime. Consente di mantenere le immagini di produzione piccole e sicure.
Eseguire come utente nonroot. Eseguire i contenitori .NET come utente nonroot (tramite nome utente o UID, $APP_UID) per allinearsi al principio dei privilegi minimi. Limita gli effetti potenziali di un contenitore compromesso.
Ascolto sulla porta 8080. Quando si esegue come utente nonroot, configurare l'applicazione per l'ascolto sulla porta 8080. Si tratta di una convenzione comune per gli utenti nonroot.
Incapsulare le dipendenze. Assicurarsi che tutte le dipendenze per la funzione dell'app siano incapsulate nell'immagine del contenitore Docker. L'incapsulamento consente di distribuire l'app in modo affidabile in un'ampia gamma di host.
Scegliere le immagini di base appropriate. L'immagine di base scelta dipende dall'ambiente di distribuzione. Se si esegue la distribuzione in App contenitore, ad esempio, è necessario usare immagini Docker Linux.
Ad esempio, l'implementazione di riferimento usa un processo di compilazione a più fasi . Le fasi iniziali compilano e compilano l'applicazione usando un'immagine SDK completa (mcr.microsoft.com/dotnet/sdk:8.0-jammy
). L'immagine di runtime finale viene creata dall'immagine chiseled
di base, che esclude l'SDK e gli artefatti di compilazione. Il servizio viene eseguito come utente nonroot (USER $APP_UID
) ed espone la porta 8080. Le dipendenze necessarie per il funzionamento dell'applicazione sono incluse nell'immagine Docker, come evidenziato dai comandi per copiare i file di progetto e ripristinare i pacchetti. L'uso di immagini basate su Linux (mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled
) garantisce la compatibilità con App contenitore, che richiede contenitori Linux per la distribuzione.
# 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"]
Distribuire l'implementazione di riferimento
Distribuire l'implementazione di riferimento del modello di app Web moderno per .NET. Nel repository sono disponibili istruzioni sia per lo sviluppo che per la distribuzione di produzione. Dopo la distribuzione, è possibile simulare e osservare i modelli di progettazione.
Figura 3. Architettura dell'implementazione di riferimento. Scaricare un file di Visio di questa architettura.