Editar

Compartir a través de


Patrón Modern Web App para .NET

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

Este artículo muestra cómo implementar el patrón Modern Web App. El patrón Modern Web App define cómo modernizar las aplicaciones web en la nube e introducir una arquitectura orientada a servicios. El patrón Modern Web App proporciona instrucciones de configuración, código y arquitectura prescriptivas que se alinean con los principios del marco de trabajo de Azure Well-Architected y se basa en el patrón Reliable Web App.

¿Por qué utilizar el patrón Modern Web App?

El patrón Modern Web App ayuda a optimizar las áreas de alta demanda de una aplicación web. Ofrece una guía detallada para desacoplar estas áreas, lo que permite un escalado independiente para la optimización de costes. Este enfoque le permite asignar recursos dedicados a componentes críticos, mejorando el rendimiento general. Desacoplar servicios separables puede mejorar la fiabilidad al evitar que las ralentizaciones en una parte de la aplicación afecten a otras. El desacoplamiento también permite versionar componentes individuales de la aplicación de forma independiente.

Cómo implementar el patrón Modern Web App

Este artículo incluye orientación sobre arquitectura, código y configuración para implantar el patrón Modern Web App. Utilice los siguientes enlaces para acceder a la guía que necesite:

  • Guía de arquitectura: aprenda a modularizar los componentes de la aplicación web y a seleccionar las soluciones de plataforma como servicio (PaaS) adecuadas.
  • Guía de código: Implementar cuatro patrones de diseño para optimizar los componentes desacoplados: Strangler Fig, Queue-Based Load Leveling, Competing Consumers y patrones Health Endpoint Monitoring.
  • Guía de configuración: Configure la autenticación, autorización, autoescalado y contenedorización para los componentes desacoplados.

Sugerencia

Logotipo de GitHub Existe una implementación de referencia (aplicación de ejemplo) del patrón Modern Web App. Representa el estado final de la implementación de Modern Web App. Se trata de una aplicación web de producción que incluye todas las actualizaciones de código, arquitectura y configuración comentadas en este artículo. implemente y utilice la implementación de referencia para guiar su implementación del patrón Modern Web App.

Guía de arquitectura

El patrón Modern Web App se basa en el patrón Reliable Web App. Requiere algunos componentes arquitectónicos adicionales para implementar. Necesita una cola de mensajes, una plataforma de contenedores, un almacén de datos de servicios desacoplados y un registro de contenedor (consulte figura 1).

Diagrama que muestra la arquitectura básica del patrón Modern Web App.Figura 1. Elementos arquitectónicos esenciales del patrón Modern Web App.

Para un objetivo de nivel de servicio (SLO) superior, puede añadir una segunda región a la arquitectura de su aplicación web. Una segunda región requiere que configure su equilibrador de carga para enrutar el tráfico a la segunda región con el fin de admitir una configuración activa-activa o activa-pasiva. Utilice una topología de red radial para centralizar y compartir recursos, como un firewall de red. Acceda al repositorio de contenedores a través de la red virtual hub. Si tiene máquinas virtuales, añada un host bastión a la red virtual hub para administrarlas de forma segura (consulte la figura 2).

Diagrama que muestra la arquitectura del patrón Modern Web App con segunda región y topología radial.Figura 2. Arquitectura del patrón Modern Web App con segunda región y topología radial.

Arquitectura de desacoplamiento

Para implementar el patrón Modern Web App, es necesario desacoplar la arquitectura de la aplicación web existente. La desacoplamiento de la arquitectura implica dividir una aplicación monolítica en servicios más pequeños e independientes, cada uno responsable de una característica o funcionalidad específica. Este proceso implica evaluar la aplicación web actual, modificar la arquitectura y, por último, extraer el código de la aplicación web a una plataforma de contenedores. El objetivo es identificar y extraer sistemáticamente los servicios de aplicación que se benefician de la mayor parte de la desacoplación. Para desacoplar su arquitectura, siga estas recomendaciones:

  • Identifique los límites del servicio. Aplique principios de diseño controlados por dominio para identificar contextos enlazados dentro de la aplicación monolítica. Cada contexto delimitado representa un límite lógico y puede ser un candidato para un servicio independiente. Los servicios que representan funciones empresariales distintas y tienen menos dependencias son buenos candidatos para el desacoplamiento.

  • Evalúe los beneficios del servicio. Céntrese en los servicios que más se benefician del escalado independiente. Desacoplar estos servicios y convertir las tareas de procesamiento de operaciones síncronas a asíncronas permite una administración más eficaz de los recursos, admite implantaciones independientes y reduce el riesgo de afectar a otras partes de la aplicación durante actualizaciones o cambios. Por ejemplo, puede separar la desprotección del pedido del procesamiento de pedidos.

  • Evaluar la viabilidad técnica. Examine la arquitectura actual para identificar las limitaciones y dependencias técnicas que podrían afectar al proceso de desacoplamiento. Planificar cómo se administran y comparten los datos entre servicios. Los servicios desacoplados deben administrar sus propios datos y minimizar el acceso directo a la base de datos a través de los límites del servicio.

  • Implementar servicios de Azure. Seleccione e implemente los servicios Azure que necesita para dar soporte al servicio de aplicación web que pretende extraer. Utilice la siguiente sección Seleccionar los servicios Azure adecuados como guía.

  • Desacoplar los servicios de aplicaciones web. Defina interfaces y API claras para que los servicios de aplicación web recién extraídos interactúen con otras partes del sistema. Diseñe una estrategia de administración de datos que permita a cada servicio administrar sus propios datos a la vez que garantiza la coherencia y la integridad. Para conocer las estrategias de implementación específicas y los patrones de diseño que se deben utilizar durante este proceso de extracción, consulte la sección Orientación sobre el código.

  • Utilice almacenamiento independiente para los servicios desacoplados. Cada servicio desacoplado debe tener su propio almacén de datos aislado para facilitar el versionado independiente, la implementación, la escalabilidad y mantener la integridad de los datos. Por ejemplo, la implementación de referencia separa el servicio de representación de vales de la API web y elimina la necesidad de que el servicio acceda a la base de datos de la API. En su lugar, el servicio comunica la dirección URL en la que las imágenes de vale se generaron de nuevo a la API web a través de un mensaje de Azure Service Bus y la API conserva la ruta de acceso a su base de datos.

  • Implemente pipelines de implementación independientes para cada servicio desacoplado. Las pipelines de implementación separadas permiten que cada servicio se actualice a su propio ritmo. Si distintos equipos u organizaciones de su empresa son propietarios de distintos servicios, disponer de pipelines de implementación independientes permite a cada equipo controlar sus propias implementaciones. Use herramientas de integración continua y entrega continua (CI/CD), como Jenkins, Acciones de GitHub o Azure Pipelines para configurar estas canalizaciones.

  • Revise los controles de seguridad. Asegúrese de que sus controles de seguridad están actualizados para tener en cuenta la nueva arquitectura, incluidas las reglas de firewall y los controles de acceso.

Seleccione los servicios Azure adecuados

Para cada servicio Azure de su arquitectura, consulte la guía de servicios Azure correspondiente en el Marco de trabajo bien diseñado. Para el patrón Modern Web App, necesita un sistema de mensajería que admita la mensajería asíncrona, una plataforma de aplicaciones que admita la contenedorización y un repositorio de imágenes de contenedor.

  • Elija una cola de mensajes. Una cola de mensajes es una pieza importante de las arquitecturas orientadas a servicios. Desacopla los emisores y receptores de mensajes para permitir la mensajería asíncrona. Utilice la guía para elegir un servicio de mensajería de Azure para elegir un sistema de mensajería de Azure que satisfaga sus necesidades de diseño. Azure tiene tres servicios de mensajería: Azure Event Grid, Azure Event Hubs y Service Bus. Comience con Service Bus como opción predeterminada y use las otras dos opciones si Service Bus no satisface sus necesidades.

    Service Caso de uso
    Service Bus Elija Service Bus para la entrega confiable, ordenada y posiblemente transaccional de mensajes de alto valor en aplicaciones empresariales.
    Event Grid Elija Event Grid cuando necesite controlar un gran número de eventos discretos de forma eficaz. Event Grid es escalable para aplicaciones controladas por eventos en las que es necesario enrutar muchos eventos pequeños e independientes (como cambios de estado de recursos) a los suscriptores en un modelo de baja latencia y publicación-suscripción.
    Event Hubs Elija Event Hubs para la ingesta masiva de datos de alto rendimiento, como telemetría, registros o análisis en tiempo real. Event Hubs está optimizado para escenarios de streaming en los que los datos masivos deben ingerirse y procesarse continuamente.
  • Implemente un servicio de contenedor. Para las partes de su aplicación que desea contendorizar, necesita una plataforma de aplicaciones que admita contenedores. Utilice la guía Elija un servicio de contenedor Azure para ayudarle a tomar su decisión. Azure tiene tres servicios de contenedor principales: Azure Container Apps, Azure Kubernetes Service (AKS) y App de Azure Service. Comience con Container Apps como opción predeterminada y use las otras dos opciones si Container Apps no satisface sus necesidades.

    Service Caso de uso
    Aplicaciones de contenedor Elija Container Apps si necesita una plataforma sin servidor que escale y administre automáticamente los contenedores en aplicaciones controladas por eventos.
    AKS Elija AKS si necesita un control detallado de las configuraciones de Kubernetes y funciones avanzadas de escalado, redes y seguridad.
    Web Apps for Container Elija Aplicación web para contenedores en App Service para obtener la experiencia paaS más sencilla.
  • Implemente un repositorio de contenedores. Al usar cualquier servicio de proceso basado en contenedores, es necesario tener un repositorio para almacenar las imágenes de contenedor. Puede utilizar un registro de contenedores público como Docker Hub o un registro administrado como Azure Container Registry. Use la guía Introducción a los registros de contenedor en Azure para ayudar a tomar la decisión.

Guía de código

Para desacoplar y extraer con éxito un servicio independiente, debe actualizar el código de su aplicación web con los siguientes patrones de diseño: el patrón Strangler Fig, el patrón Queue-Based Load Leveling, el patrón Competing Consumers, el patrón Health Endpoint Monitoring y el patrón Retry.

Diagrama que muestra la función de los patrones de diseño en la arquitectura de patrones de Modern Web App.Figura 3. Función de los patrones de diseño.

  1. Patrón Strangler Fig: El patrón Strangler Fig migra de forma incremental la funcionalidad de una aplicación monolítica al servicio desacoplado. Implemente este patrón en la aplicación web principal para migrar gradualmente la funcionalidad a servicios independientes dirigiendo el tráfico en función de los puntos de conexión.

  2. Patrón de nivelación de carga basado en cola: el patrón de nivelación de carga basado en cola administra el flujo de mensajes entre el productor y el consumidor mediante una cola como búfer. Implemente este patrón en la parte productora del servicio desacoplado para administrar el flujo de mensajes de forma asíncrona utilizando una cola.

  3. Patrón de consumidores en competencia: El patrón Consumidores en competencia permite que varias instancias del servicio desacoplado lean independientemente de la misma cola de mensajes y compitan por procesar los mensajes. Implemente este patrón en el servicio desacoplado para distribuir tareas entre varias instancias.

  4. Patrón Health Endpoint Monitoring: El patrón Health Endpoint Monitoring expone puntos de conexión para supervisar el estado y la salud de diferentes partes de la aplicación web. (4a) Implementar este patrón en la aplicación web principal. (4b) Impleméntalo también en el servicio desacoplado para controlar el estado de los puntos de conexión.

  5. Patrón de reintento: El patrón de reintento administra los fallos transitorios reintentando las operaciones que pueden fallar de forma intermitente. (5a) (5a) Implemente este patrón en todas las llamadas salientes a otros servicios Azure en la aplicación web principal, como las llamadas a la cola de mensajes y a los puntos de conexión privados. (5b) Implementar también este patrón en el servicio desacoplado para administrar fallos transitorios en las llamadas a los puntos de conexión privados.

Cada patrón de diseño proporciona ventajas que se alinean con uno o más pilares del marco de trabajo bien diseñado (consulte la tabla siguiente).

Modelo de diseño Lugar de implementación Fiabilidad (RE) Seguridad (SE) Optimización de costes (OC) Excelencia operativa (OE) Eficiencia del rendimiento (PE) Apoyo a principios de marco bien diseñados
Patrón Fig Strangler Aplicación web principal RE:08
CO:07
CO:08
OE:06
OE:11
Patrón Queue-based Load Leveling Productor de servicio desacoplado RE:06
RE:07
CO:12
PE:05
Patrón de consumidores simultáneos Servicio desacoplado RE:05
RE:07
CO:05
CO:07
PE:05
PE:07
Patrón de supervisión de puntos de conexión de mantenimiento Servicio desacoplado & de la aplicación web principal RE:07
RE:10
OE:07
PE:05
Patrón Retry Servicio desacoplado & de la aplicación web principal RE:07

Implementación del patrón Strangler Fig

Utilice el patrón Strangler Fig para migrar gradualmente la funcionalidad de la base de código monolítica a nuevos servicios independientes. Extraiga nuevos servicios de la base de código monolítica existente y modernice lentamente las partes críticas de la aplicación web. Para aplicar el patrón Strangler Fig, siga estas recomendaciones:

  • Configure una capa de enrutamiento. En la base de código de aplicación web monolítica, implemente una capa de enrutamiento que dirija el tráfico en función de los puntos de conexión. Utilice lógica de enrutamiento personalizada según sea necesario para administrar reglas de negocio específicas para dirigir el tráfico. Por ejemplo, si tiene un /users punto de conexión en la aplicación monolítica y ha movido esa funcionalidad al servicio desacoplado, la capa de enrutamiento dirige todas las solicitudes al /users nuevo servicio.

  • Administrar el lanzamiento de características. Utilice las bibliotecas de administración de características .NET para implementar indicadores de características y lanzamientos preconfigurados para implementar gradualmente los servicios desacoplados. El enrutamiento de la aplicación monolítica existente debería controlar cuántas solicitudes reciben los servicios desacoplados. Comience con un pequeño porcentaje de solicitudes y aumente su uso con el tiempo a medida que vaya confiando en su estabilidad y rendimiento. Por ejemplo, la implementación de referencia extrae la funcionalidad de representación de vales en un servicio independiente, que se puede introducir gradualmente para controlar una parte mayor de las solicitudes de representación de vales. A medida que el nuevo servicio demuestre su fiabilidad y rendimiento, podrá asumir toda la funcionalidad de renderizado de tickets del monolito, completando así la transición.

  • Utilizar un servicio de fachada (si es necesario). Un servicio de fachada es útil cuando una única solicitud debe interactuar con varios servicios o cuando se desea ocultar al cliente la complejidad del sistema subyacente. Sin embargo, si el servicio desacoplado no tiene ninguna API de acceso público, es posible que no sea necesario un servicio de fachada. En la base de código de la aplicación web monolítica, implemente un servicio de fachada para enrutar las solicitudes al back-end adecuado (monolito o microservicio). En el nuevo servicio desacoplado, asegúrese de que el nuevo servicio pueda administrar solicitudes de forma independiente cuando se acceda a él a través de la fachada.

Implementar el patrón de Queue-Based Load Leveling

Implemente el patrón de nivelación de carga basado en cola en la parte del productor del servicio desacoplado para controlar de forma asincrónica las tareas que no necesitan respuestas inmediatas. Este patrón mejora la capacidad de respuesta y la escalabilidad general del sistema mediante el uso de una cola para administrar la distribución de la carga de trabajo. Permite que el servicio desacoplado procese las solicitudes a un ritmo constante. Para implementar este patrón de forma eficaz, siga estas recomendaciones:

  • Use colas de mensajes no bloqueantes. Asegúrese de que el proceso que envía mensajes a la cola no bloquea otros procesos mientras espera a que el servicio desacoplado administre los mensajes de la cola. Si el proceso requiere el resultado de la operación del servicio desacoplado, tenga una forma alternativa de manejar la situación mientras espera a que se complete la operación en cola. Por ejemplo, la implementación de referencia usa Service Bus y la await palabra clave con messageSender.PublishAsync() para publicar mensajes de forma asincrónica en la cola sin bloquear el subproceso que ejecuta este código:

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

    Este enfoque garantiza que la aplicación principal siga respondiendo y pueda administrar otras tareas simultáneamente, mientras que el servicio desacoplado procesa las solicitudes en cola a un ritmo manejable.

  • Implementar el reintento y la eliminación de mensajes. Implemente un mecanismo para reintentar el procesamiento de mensajes en cola que no puedan procesarse con éxito. Si los fallos persisten, estos mensajes deben eliminarse de la cola. Por ejemplo, Service Bus tiene características integradas de cola de reintentos y mensajes fallidos.

  • Configure el procesamiento de mensajes idempotente. La lógica que procesa los mensajes de la cola debe ser idempotente para administrar los casos en los que un mensaje puede procesarse más de una vez. Por ejemplo, la implementación de referencia usa ServiceBusClient.CreateProcessor con AutoCompleteMessages = true y ReceiveMode = ServiceBusReceiveMode.PeekLock para asegurarse de que los mensajes solo se procesan una vez y se pueden volver a procesar en caso de error (consulte el código siguiente).

    // 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
    });
    
  • Gestionar los cambios en la experiencia. El procesamiento asíncrono puede hacer que las tareas no se completen inmediatamente. Los usuarios deben saber cuándo se está procesando su tarea para establecer expectativas correctas y evitar confusiones. Utilice indicaciones visuales o mensajes para indicar que una tarea está en curso. Ofrezca a los usuarios la opción de recibir notificaciones cuando su tarea esté terminada, como un correo electrónico o una notificación push.

Implementar el patrón de Competing Consumers

Implemente el patrón Competing Consumers en los servicios desacoplados para administrar las tareas entrantes desde la cola de mensajes. Este patrón consiste en distribuir las tareas entre varias instancias de servicios desacoplados. Estos servicios procesan mensajes de la cola, lo que mejora el equilibrio de carga y aumenta la capacidad del sistema para controlar las solicitudes simultáneas. El patrón Competing Consumers es eficaz cuando:

  • La secuencia de procesamiento de los mensajes no es crucial.
  • La cola no se ve afectada por mensajes con formato incorrecto.
  • La operación de procesamiento es idempotente, lo que significa que puede aplicarse varias veces sin cambiar el resultado más allá de la aplicación inicial.

Para implementar el patrón Competing Consumers, siga estas recomendaciones:

  • Controlar mensajes simultáneos. Cuando reciba mensajes de una cola, asegúrese de que su sistema está diseñado para administrar varios mensajes simultáneamente. Establezca el número máximo de llamadas simultáneas en 1 para que cada mensaje sea administrado por un consumidor independiente.

  • Desactive la precarga. Desactive la precarga de mensajes para que los consumidores solo los reciban cuando estén listos.

  • Use modos de procesamiento de mensajes fiables. Use un modo de procesamiento fiable, como PeekLock (o su equivalente), que reintente automáticamente los mensajes que no se procesen correctamente. Este modo mejora la fiabilidad con respecto a los métodos de borrado en primer lugar. Si un trabajador no puede administrar un mensaje, otro debe ser capaz de procesarlo sin errores, incluso si el mensaje se procesa varias veces.

  • Implementar tratamiento de errores. Dirija los mensajes con formato incorrecto o que no se puedan procesar a una cola separada con formato incorrecto. Este diseño evita el procesamiento repetitivo. Por ejemplo, puede detectar excepciones durante el procesamiento del mensaje y mover el mensaje problemático a la cola separada.

  • Controle los mensajes desordenados. Diseñe consumidores que procesen mensajes que llegan fuera de secuencia. Si hay varios consumidores en paralelo, es posible que procesen los mensajes fuera de orden.

  • Escala basada en la longitud de la cola. Los servicios que consumen mensajes de una cola deben autoescalarse en función de la longitud de la cola. El autoescalado basado en la longitud de la cola permite un procesamiento eficaz de los picos de mensajes entrantes.

  • Use una cola de respuesta de mensajes. Si el sistema requiere notificaciones para el procesamiento posterior al mensaje, configure una cola de respuesta o respuesta dedicada. Esta configuración divide la mensajería operativa de los procesos de notificación.

  • Usar servicios sin estado. Considere la posibilidad de utilizar servicios sin estado para procesar las solicitudes de una cola. Permite escalar fácilmente y hacer un uso eficiente de los recursos.

  • Configure el registro. Integre el registro y el control de excepciones específicos en el flujo de trabajo de procesamiento de mensajes. Céntrese en capturar errores de serialización y dirigir estos mensajes problemáticos a un mecanismo de mensajes fallidos. Estos registros proporcionan información valiosa para la solución de problemas.

Por ejemplo, la implementación de referencia usa el patrón De consumidores competidores en un servicio sin estado que se ejecuta en Container Apps para procesar solicitudes de representación de vales desde una cola de Service Bus. Configura un procesador de cola con:

  • AutoCompleteMessages: Completa automáticamente los mensajes si se procesan sin fallos.
  • ReceiveMode: Utiliza el modo PeekLock y vuelve a entregar los mensajes si no se resuelven.
  • MaxConcurrentCalls: Establecer a 1 para controlar un mensaje a la vez.
  • PrefetchCount: Se establece en 0 para evitar la precarga de mensajes.

El procesador registra los detalles de procesamiento de mensajes, lo que ayuda a la solución de problemas y la supervisión. Captura los errores de deserialización y encamina los mensajes no válidos a una cola de mensajes fallidos, lo que evita el procesamiento repetitivo de mensajes defectuosos. El servicio se escala a nivel de contenedor, lo que permite una administración eficaz de los picos de mensajes en función de la longitud de la cola.

// 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);
    }
};

Implantación del patrón Health Endpoint Monitoring

Implemente el patrón Health Endpoint Monitoring en el código de la aplicación principal y en el código del servicio desacoplado para realizar un seguimiento del estado de los puntos de conexión de la aplicación. Los orquestadores como AKS o Container Apps pueden sondear estos puntos de conexión para comprobar el estado del servicio y reiniciar las instancias incorrectas. Las aplicaciones ASP.NET Core pueden añadir middleware de comprobación de salud dedicado para servir eficientemente los datos de salud del punto de conexión y las dependencias clave. Para implementar el patrón Health Endpoint Monitoring, siga estas recomendaciones:

  • Implemente comprobaciones de salud. Utilice el middleware de comprobaciones de salud de ASP.NET Core para proporcionar puntos de conexión de comprobación de salud.

  • Valide las dependencias. Asegúrese de que sus comprobaciones de estado validan la disponibilidad de las dependencias clave, como la base de datos, el almacenamiento y el sistema de mensajería. El paquete que no es de Microsoft, AspNetCore.Diagnostics.HealthChecks, puede implementar comprobaciones de dependencias de comprobación de estado para muchas dependencias de aplicaciones comunes.

    Por ejemplo, la implementación de referencia utiliza el middleware de comprobación de salud de ASP.NET Core para exponer puntos de conexión de comprobación de salud, utilizando el método AddHealthChecks() en el objeto builder.Services. El código valida la disponibilidad de las dependencias clave, Azure Blob Storage y la cola de Service Bus con los AddAzureBlobStorage() métodos y AddAzureServiceBusQueue() , que forman parte del AspNetCore.Diagnostics.HealthChecks paquete. Container Apps permite configurar sondeos de estado que se supervisan para medir si las aplicaciones son correctas o necesitan reciclaje.

    // 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");
    
  • Configuración de recursos de Azure. Configure los recursos de Azure para usar las direcciones URL de comprobación de estado de la aplicación para confirmar la vida y la preparación. Por ejemplo, la implementación de referencia utiliza Bicep para configurar las URL de comprobación de estado a fin de confirmar la vitalidad y disponibilidad del recurso Azure. Un sondeo de ejecución para golpear el punto de conexión /health cada 10 segundos después de un retraso inicial de 2 segundos.

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

Implementar el patrón Retry

El patrón Retry permite a las aplicaciones recuperarse de fallos transitorios. El patrón Retry es fundamental para el patrón Reliable Web App, por lo que su aplicación web ya debería usar el patrón Retry. Aplique el patrón Retry a las peticiones a los sistemas de mensajería y a las peticiones emitidas por los servicios desacoplados que extraiga de la aplicación web. Para aplicar el patrón Retry, siga estas recomendaciones:

  • Configure las opciones de reintento. Cuando se integre con una cola de mensajes, asegúrese de configurar el cliente responsable de las interacciones con la cola con las opciones de reintento adecuadas. Especifique parámetros como el número máximo de reintentos, el retardo entre reintentos y el retardo máximo.

  • Utilice el retardo exponencial. Implemente una estrategia de retroceso exponencial para los reintentos. Esto significa aumentar exponencialmente el tiempo entre cada reintento, lo que ayuda a reducir la carga del sistema durante periodos de altas tasas de fallo.

  • Use la funcionalidad de reintento del SDK. Para los servicios con SDK especializados, como Service Bus o Blob Storage, use los mecanismos de reintento integrados. Los mecanismos de reintento integrados están optimizados para los casos de uso típicos del servicio y pueden administrar los reintentos de forma más eficaz con menos configuración necesaria por su parte. Por ejemplo, la implementación de referencia usa la funcionalidad de reintento integrada del SDK de Service Bus (ServiceBusClient y ServiceBusRetryOptions). El objeto ServiceBusRetryOptions recupera ajustes de MessageBusOptions para configurar ajustes de reintento como MaxRetries, Delay, MaxDelay y 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);
    });
    
  • Adoptar bibliotecas de resistencia estándar para clientes HTTP. Para las comunicaciones HTTP, integre una biblioteca de resiliencia estándar como Polly o Microsoft.Extensions.Http.Resilience. Estas bibliotecas ofrecen mecanismos de reintento completos que son cruciales para administrar las comunicaciones con servicios web externos.

  • Gestione el bloqueo de mensajes. Para los sistemas basados en mensajes, aplique estrategias de administración de mensajes que admitan reintentos sin pérdida de datos, como el uso de modos de "peek-lock" cuando estén disponibles. Asegúrese de que los mensajes fallidos se reintentan de forma efectiva y se mueven a una cola de espera después de repetidos fallos.

Implantar el rastreo distribuido

A medida que las aplicaciones se orientan más a los servicios y sus componentes se desacoplan, es crucial supervisar el flujo de ejecución entre los servicios. El patrón Modern Web App usa Application Insights y Azure Monitor para obtener visibilidad sobre el estado y el rendimiento de las aplicaciones a través de las API de OpenTelemetry, que admiten el seguimiento distribuido.

El seguimiento distribuido rastrea una solicitud de usuario a medida que atraviesa múltiples servicios. Cuando se recibe una solicitud, se etiqueta con un identificador de seguimiento, que se pasa a otros componentes a través de encabezados HTTP y propiedades de Service Bus durante la invocación de dependencias. Los seguimientos y los registros incluyen entonces tanto el identificador de seguimiento como un identificador de actividad (o identificador span), que corresponde al componente específico y a su actividad principal. Las herramientas de supervisión, como Application Insights, la usan para mostrar un árbol de actividades y registros en distintos servicios, cruciales para supervisar aplicaciones distribuidas.

  • Instale las bibliotecas de OpenTelemetry. Use bibliotecas de instrumentación para habilitar el seguimiento y las métricas de componentes comunes. Añada instrumentación personalizada con System.Diagnostics.ActivitySource y System.Diagnostics.Activity si es necesario. Use bibliotecas exportadoras para escuchar diagnósticos de OpenTelemetry y registrarlos en almacenes persistentes. Utilice los exportadores existentes o crear el suyo propio con System.Diagnostics.ActivityListener.

  • Configure OpenTelemetry. Utilice la distribución Azure Monitor de OpenTelemetry (Azure.Monitor.OpenTelemetry.AspNetCore). Asegúrese de que exporta diagnósticos a Application Insights e incluye instrumentación integrada para métricas, seguimientos, registros y excepciones comunes del tiempo de ejecución de .NET y ASP.NET Core. Incluya otros paquetes de instrumentación de OpenTelemetry para los clientes de SQL, Redis y Azure SDK.

  • Supervise y analice. Tras la configuración, asegúrese de que los registros, las trazas, las métricas y las excepciones se capturan y se envían a Application Insights. Compruebe que se incluyen los identificadores de rastreo, actividad y actividad principal, lo que permite a Application Insights proporcionar visibilidad de rastreo de extremo a extremo a través de los límites de HTTP y Service Bus. Utilice esta configuración para supervisar y analizar eficazmente las actividades de su aplicación en todos los servicios.

El ejemplo de Modern Web App utiliza la distribución Azure Monitor de OpenTelemetry (Azure.Monitor.OpenTelemetry.AspNetCore). Se usan más paquetes de instrumentación para clientes sql, Redis y Azure SDK. OpenTelemetry está configurado en el servicio de representación de vales de ejemplo de Aplicación web moderna como este:

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.*"); 
    }); 

El método builder.Logging.AddOpenTelemetry enruta todo el registro a través de OpenTelemetry, asegurando un rastreo y registro consistente en toda la aplicación. Al registrar servicios de OpenTelemetry con builder.Services.AddOpenTelemetry, la aplicación se configura para recopilar y exportar diagnósticos, que luego se envían a Application Insights a través UseAzureMonitorde . Además, la instrumentación de cliente para componentes como Service Bus y clientes HTTP se configura a través WithMetrics de y WithTracing, habilitando métricas automáticas y recopilación de seguimiento sin necesidad de cambios en el uso del cliente existente, solo una actualización de la configuración.

Guía de configuración

Las siguientes secciones proporcionan orientación sobre la implementación de las actualizaciones de configuración. Cada sección se ajusta a uno o varios pilares del marco de trabajo bien diseñado.

Configuración Fiabilidad (RE) Seguridad (SE) Optimización de costes (OC) Excelencia operativa (OE) Eficiencia del rendimiento (PE) Apoyo a principios de marco bien diseñados
Configuración de la autenticación y la autorización SE:05
OE:10
Implementar autoescalado independiente RE:06
CO:12
PE:05
Implementación de servicios de contenedorización CO:13
PE:09
PE:03

Configuración de la autenticación y la autorización

Para configurar la autenticación y autorización en cualquier nuevo servicio de Azure (identidades de carga de trabajo) que añada a la aplicación web, siga estas recomendaciones:

  • Uso de identidades administradas para cada nuevo servicio. Cada servicio independiente debe tener su propia identidad y utilizar identidades administradas para la autenticación de servicio a servicio. Las identidades administradas eliminan la necesidad de administrar credenciales en el código y reducen el riesgo de fuga de credenciales. Le ayudan a evitar poner información sensible como cadenas de conexión en su código o archivos de configuración.

  • Conceda los mínimos privilegios a cada nuevo servicio. Asigne solo los permisos necesarios a cada nueva identidad de servicio. Por ejemplo, si una identidad solo necesita insertar en un registro de contenedor, no conceda permisos de extracción. Revise estos permisos con regularidad y ajústelos según sea necesario. Use diferentes identidades para diferentes funciones, como la implementación y la aplicación. Esto limita el daño potencial si una identidad se ve comprometida.

  • Adopte la infraestructura como código (IaC). Use herramientas de Bicep o iaC similares para definir y administrar los recursos en la nube. IaC garantiza una aplicación coherente de las configuraciones de seguridad en sus implantaciones y le permite controlar las versiones de la configuración de su infraestructura.

Para configurar la autenticación y autorización de usuarios (identidades de usuario), siga estas recomendaciones:

  • Concede los mínimos privilegios a los usuarios. Al igual que con los servicios, asegúrese de que a los usuarios solo se les conceden los permisos que necesitan para realizar sus tareas. Revise y ajuste periódicamente estos permisos.

  • Realice auditorías de seguridad periódicas. Revise y audite periódicamente su configuración de seguridad. Busque cualquier error de configuración o permisos innecesarios y rectifíquelos inmediatamente.

La implementación de referencia utiliza IaC para asignar identidades administradas a servicios añadidos y roles específicos a cada identidad. Define los roles y el acceso a permisos para la implementación (containerRegistryPushRoleId), el propietario de la aplicación (containerRegistryPushRoleId) y la aplicación Container Apps () (containerRegistryPullRoleIdconsulte el código siguiente).

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
    }
\]

La implementación de referencia asigna la identidad administrada como la nueva identidad de Container Apps en la implementación (consulte el código siguiente).

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
      ]
    }
  }
}

Configurar el autoescalado independiente

El patrón Modern Web App comienza a romper la arquitectura monolítica e introduce el desacoplamiento de servicios. Cuando desacopla una arquitectura de aplicación web, puede escalar los servicios desacoplados de forma independiente. Al escalar los servicios Azure para dar soporte a un servicio de aplicación web independiente, en lugar de a una aplicación web completa, se optimizan los costes de escalado al tiempo que se satisfacen las demandas. Para autoescalar contenedores, siga estas recomendaciones:

  • Usar servicios sin estado. Asegúrese de que sus servicios no tienen estado. Si la aplicación .NET contiene el estado de sesión en proceso, externalícelo a una caché distribuida como Redis o una base de datos como SQL Server.

  • Configure reglas de autoescalado. Utilice las configuraciones de autoescalado que proporcionen el control más rentable sobre sus servicios. Para los servicios en contenedores, el escalado basado en eventos, como Kubernetes Event-Driven Autoscaler (KEDA), a menudo proporciona un control granular, lo que le permite escalar en función de métricas de eventos. Container Apps y AKS admiten KEDA. En el caso de los servicios que no admiten KEDA, como App Service, use las características de escalado automático proporcionadas por la propia plataforma. Estas funciones suelen incluir el escalado basado en reglas basadas en métricas o en el tráfico HTTP.

  • Configure réplicas mínimas. Para evitar un arranque en frío, configure los ajustes de autoescalado para mantener un mínimo de una réplica. Un arranque en frío se produce cuando se inicializa un servicio desde un estado detenido, lo que a menudo provoca un retraso en la respuesta. Si minimizar los costes es una prioridad y puede tolerar retrasos en el arranque en frío, establezca el número mínimo de réplicas en 0 al configurar el autoescalado.

  • Configure un periodo de enfriamiento. Aplique un periodo de enfriamiento adecuado para introducir un retardo entre los eventos de escalado. El objetivo es evitar actividades de escalado excesivas provocadas por picos de carga temporales.

  • Configure el escalado basado en colas. Si la aplicación usa una cola de mensajes como Service Bus, configure las opciones de escalado automático para escalar según la longitud de la cola con mensajes de solicitud. El escalado tiene como objetivo mantener una réplica del servicio por cada N mensajes en la cola (redondeado hacia arriba).

Por ejemplo, la implementación de referencia usa el escalador KEDA de Service Bus para escalar la aplicación contenedora en función de la longitud de la cola. Escala service-bus-queue-length-rule el servicio en función de la longitud de una cola de Service Bus especificada. El parámetro messageCount se establece en 10, por lo que el escalador tiene una réplica del servicio por cada 10 mensajes en la cola. Los parámetros scaleMaxReplicas y scaleMinReplicas establecen el número máximo y mínimo de réplicas del servicio. El queue-connection-string secreto, que contiene el cadena de conexión de la cola de Service Bus, se recupera de Azure Key Vault. Este secreto se utiliza para autenticar el escalador en el Bus de Servicios.

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

Implementación de servicios de contenedorización

La contenedorización significa que todas las dependencias para que la aplicación funcione están encapsuladas en una imagen ligera que puede desplegarse de forma fiable en una amplia gama de hosts. Para desplegar en contenedores, siga estas recomendaciones:

  • Identifique los límites del dominio. Comience por identificar los límites de dominio dentro de su aplicación monolítica. Esto ayuda a determinar qué partes de la aplicación puede extraer en servicios separados.

  • Cree imágenes de Docker. Cuando cree imágenes Docker para sus servicios .NET, utilice imágenes base cinceladas. Estas imágenes contienen solo el conjunto mínimo de paquetes necesarios para que .NET se ejecute, lo que minimiza tanto el tamaño del paquete como la superficie de ataque.

  • Utilice Dockerfiles multietapa. Implemente Dockerfiles multietapa para separar los activos en tiempo de compilación de la imagen del contenedor en tiempo de ejecución. Esto ayuda a mantener las imágenes de producción pequeñas y seguras.

  • Ejecute como usuario que no sea de raíz. Ejecute sus contenedores .NET como un usuario no root (a través del nombre de usuario o UID, $APP_UID) para alinearse con el principio de mínimo privilegio. Esto limita los efectos potenciales de un contenedor comprometido.

  • Escucha en el puerto 8080. Cuando se ejecuta como un usuario no root, configure su aplicación para escuchar en el puerto 8080. Es una convención común para usuarios no root.

  • Encapsular dependencias. Asegúrese de que todas las dependencias para que la aplicación funcione estén encapsuladas en la imagen del contenedor Docker. La encapsulación permite que la aplicación se implemente de forma fiable en una amplia gama de hosts.

  • Elija las imágenes base adecuadas. La imagen base que elija dependerá de su entorno de implementación. Si va a implementar en Container Apps, por ejemplo, debe usar imágenes de Docker de Linux.

Por ejemplo, la implementación de referencia utiliza un proceso de compilación de multi-pila. Las etapas iniciales compilan y crean la aplicación utilizando una imagen completa del SDK (mcr.microsoft.com/dotnet/sdk:8.0-jammy). La imagen de ejecución final se crea a partir de la imagen base chiseled, que excluye el SDK y los artefactos de compilación. El servicio se ejecuta como usuario no root (USER $APP_UID) y expone el puerto 8080. Las dependencias necesarias para que la aplicación funcione están incluidas en la imagen Docker, como demuestran los comandos para copiar archivos de proyecto y restaurar paquetes. El uso de imágenes basadas en Linux (mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled) garantiza la compatibilidad con Container Apps, que requiere contenedores de Linux para la implementación.

# 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"]

Realice la implementación de referencia

Implemente la implementación de referencia del patrón Modern Web App for .NET. En el repositorio hay instrucciones tanto para la implantación de desarrollo como de producción. Después de la implementación, puede simular y observar los patrones de diseño.

Diagrama que muestra la arquitectura de la implementación de referencia.Figura 3. Arquitectura de la implementación de referencia. Arquitectura de la implementación de referencia. Descargue un archivo Visio de esta arquitectura.