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 De aplicación web moderna ayuda a optimizar las áreas de alta demanda de la 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. Desacoplamiento de servicios separables puede mejorar la confiabilidad evitando ralentizaciones en una parte de la aplicación y habilitando el control de versiones de 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
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 aplicación web moderna. 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 contenedor, un servicio de almacenamiento y un registro de contenedor (consulte la figura 1).
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. Configure el equilibrador de carga para enrutar el tráfico a la segunda región para admitir una configuración activa-activa o activa-pasiva en función de las necesidades empresariales. Las dos regiones requieren los mismos servicios, excepto una región, que tiene una red virtual de concentrador que se conecta. Adopte 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).
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:
Identificar los límites de 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. Por ejemplo, una dependencia externa, como un proveedor de servicios de correo electrónico en una aplicación loB, podría requerir más aislamiento de errores. Tenga en cuenta los servicios que se someten a actualizaciones o cambios frecuentes. Desacoplamiento de estos servicios permite una implementación independiente y reduce el riesgo de afectar a otras partes de la aplicación.
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 el servicio de aplicación 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 sus propios almacenes de datos para facilitar el control de versiones y la implementación. Por ejemplo, la implementación de referencia separa el servicio de correo electrónico de la aplicación web y elimina la necesidad de que el servicio acceda a la base de datos. En su lugar, el servicio comunica el estado de entrega de correo electrónico a la aplicación web a través de un mensaje de Azure Service Bus y la aplicación web guarda una nota en 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. Utilice la guía Introducción a los registros de contenedores en Azure para tomar una 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.
Figura 3. Función de los patrones de diseño.
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.
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.
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.
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.
Patrón de reintento: El patrón de reintento administra los fallos transitorios reintentando las operaciones que pueden fallar de forma intermitente. (5a) Implemente este patrón en todas las llamadas salientes a otros servicios de Azure de la aplicación web principal, como las llamadas a la cola de mensajes y 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 varios pilares del marco 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
Use el patrón strangler fig para migrar gradualmente la funcionalidad del código base monolítico 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. Implemente marcas de características y lanzamiento preconfigurado para implementar gradualmente los servicios desacoplados. El enrutamiento de aplicaciones monolíticas existente debe controlar cuántas solicitudes recibe 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 entrega de correo electrónico en un servicio independiente, que se puede introducir gradualmente para controlar una parte mayor de las solicitudes para enviar correos electrónicos que contienen guías de soporte técnico de Contoso. Como el nuevo servicio demuestra su confiabilidad y rendimiento, finalmente puede asumir todo el conjunto de responsabilidades de correo electrónico del monolito, completando 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 desacoplada-service, implemente una manera alternativa de controlar la situación mientras espera a que se complete la operación en cola. Por ejemplo, en Spring Boot, puede usar la
StreamBridge
clase para publicar mensajes de forma asincrónica en la cola sin bloquear el subproceso de llamada (vea código de ejemplo):private final StreamBridge streamBridge; public SupportGuideQueueSender(StreamBridge streamBridge) { this.streamBridge = streamBridge; } // Asynchronously publish a message without blocking the calling thread @Override public void send(String to, String guideUrl, Long requestId) { EmailRequest emailRequest = EmailRequest.newBuilder() .setRequestId(requestId) .setEmailAddress(to) .setUrlToManual(guideUrl) .build(); log.info("EmailRequest: {}", emailRequest); var message = emailRequest.toByteArray(); streamBridge.send(EMAIL_REQUEST_QUEUE, message); log.info("Message sent to the queue"); }
En este ejemplo de Java se usa
StreamBridge
para enviar mensajes de forma asincrónica. 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. En Spring Boot, puede usar
@StreamListener
o@KafkaListener
con un identificador de mensaje único para evitar el procesamiento duplicado. O bien, puede organizar el proceso de negocio para que funcione en un enfoque funcional con Spring Cloud Stream, donde elconsume
método se define de una manera que genera el mismo resultado cuando se ejecuta repetidamente. Lea Spring Cloud Stream con Service Bus para obtener más información sobre la configuración que administra el comportamiento de cómo se consumen los mensajes.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 De consumidores competidores en el servicio desacoplado 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. Al recibir mensajes de una cola, asegúrese de que el sistema se escala de forma predecible mediante la configuración de la simultaneidad para que coincida con el diseño del sistema. Los resultados de la prueba de carga le ayudan a decidir el número adecuado de mensajes simultáneos que se van a controlar y puede empezar de uno a medir el rendimiento del sistema.
Desactive la precarga. Deshabilite la captura previa de mensajes para que los consumidores solo capturen mensajes 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. Para Service Bus, los mensajes se mueven a la cola de mensajes fallidos después de un número especificado de intentos de entrega o en el rechazo explícito de la aplicación.
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 mensajes de respuesta. 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 las solicitudes de entrega de correo electrónico desde una cola de Service Bus.
El procesador registra los detalles de procesamiento de mensajes, lo que ayuda a solucionar problemas y supervisarlos. Captura errores de deserialización y proporciona información necesaria al depurar el proceso. El servicio se escala en el nivel de contenedor, lo que permite un control eficaz de los picos de mensajes en función de la longitud de la cola (consulte el código siguiente).
@Configuration
public class EmailProcessor {
private static final Logger log = LoggerFactory.getLogger(EmailProcessor.class);
@Bean
Function<byte[], byte[]> consume() {
return message -> {
log.info("New message received");
try {
EmailRequest emailRequest = EmailRequest.parseFrom(message);
log.info("EmailRequest: {}", emailRequest);
EmailResponse emailResponse = EmailResponse.newBuilder()
.setEmailAddress(emailRequest.getEmailAddress())
.setUrlToManual(emailRequest.getUrlToManual())
.setRequestId(emailRequest.getRequestId())
.setMessage("Email sent to " + emailRequest.getEmailAddress() + " with URL to manual " + emailRequest.getUrlToManual())
.setStatus(Status.SUCCESS)
.build();
return emailResponse.toByteArray();
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException("Error parsing email request message", e);
}
};
}
}
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. Spring Boot proporciona compatibilidad integrada con comprobaciones de estado con el accionador de Spring Boot, que puede exponer puntos de conexión de comprobación de estado para dependencias clave, como bases de datos, agentes de mensajes y sistemas de almacenamiento. Para implementar el patrón Health Endpoint Monitoring, siga estas recomendaciones:
Implemente comprobaciones de salud. Use el accionador de Spring Boot para proporcionar puntos de conexión de comprobación de estado. El accionador de Spring Boot expone un punto de conexión
/actuator/health
que incluye indicadores de estado integrados y comprobaciones personalizadas para varias dependencias. Para habilitar el punto de conexión de mantenimiento, agregue laspring-boot-starter-actuator
dependencia en elpom.xml
archivo obuild.gradle
.<!-- Add Spring Boot Actuator dependency --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
Configure el punto de conexión de mantenimiento en
application.properties
como se muestra en la implementación de referencia:txt management.endpoints.web.exposure.include=metrics,health,info,retry,retryevents
Valide las dependencias. El accionador de Spring Boot incluye indicadores de estado para varias dependencias, como bases de datos, agentes de mensajes (RabbitMQ o Kafka) y servicios de almacenamiento. Para validar la disponibilidad de los servicios de Azure, como Azure Blob Storage o Service Bus, use complementos de comunidad como Las integraciones de Azure Spring Apps o Micrometer, que proporcionan indicadores de estado para estos servicios. Si se necesitan comprobaciones personalizadas, puede implementarlas mediante la creación de un bean personalizado
HealthIndicator
.import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; @Component public class CustomAzureServiceBusHealthIndicator implements HealthIndicator { @Override public Health health() { // Implement your health check logic here (e.g., ping Service Bus) boolean isServiceBusHealthy = checkServiceBusHealth(); return isServiceBusHealthy ? Health.up().build() : Health.down().build(); } private boolean checkServiceBusHealth() { // Implement health check logic (pinging or connecting to the service) return true; // Placeholder, implement actual logic } }
Configuración de recursos de Azure. Configure el recurso 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, puede usar Terraform para usar las direcciones URL de comprobación de estado para confirmar la ejecución y la preparación de las aplicaciones implementadas en Container Apps. Para obtener más información, consulte Sondeos de estado en Container Apps.
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 la 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.
Adopte bibliotecas de resistencia estándar para clientes HTTP. Para los clientes HTTP, puede usar Resilience4* junto con RestTemplate o WebClient de Spring para controlar los reintentos en las comunicaciones HTTP. RestTemplate de Spring se puede encapsular con la lógica de reintento de Resilience4j para controlar los errores HTTP transitorios de forma eficaz.
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.
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. Ayuda a evitar colocar información confidencial como cadena de conexión en los archivos de código o 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 bicep o herramientas de IaC similares, como Terraform, 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 roles y permisos de acceso para la implementación mediante la definición de roles para la inserción y extracción de Container Registry (consulte el código siguiente).
resource "azurerm_role_assignment" "container_app_acr_pull" {
principal_id = var.aca_identity_principal_id
role_definition_name = "AcrPull"
scope = azurerm_container_registry.acr.id
}
resource "azurerm_user_assigned_identity" "container_registry_user_assigned_identity" {
name = "ContainerRegistryUserAssignedIdentity"
resource_group_name = var.resource_group
location = var.location
}
resource "azurerm_role_assignment" "container_registry_user_assigned_identity_acr_pull" {
scope = azurerm_container_registry.acr.id
role_definition_name = "AcrPull"
principal_id = azurerm_user_assigned_identity.container_registry_user_assigned_identity.principal_id
}
# For demo purposes, allow current user access to the container registry
# Note: when running as a service principal, this is also needed
resource "azurerm_role_assignment" "acr_contributor_user_role_assignement" {
scope = azurerm_container_registry.acr.id
role_definition_name = "Contributor"
principal_id = data.azuread_client_config.current.object_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 web contiene el estado de sesión en proceso, externalícela 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. En el caso de los servicios en contenedores, el escalado basado en eventos, como Kubernetes Event-Driven Autoscaler (KEDA) suele proporcionar un control granular, lo que le permite escalar en función de las 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 escalador tiene como objetivo mantener una réplica del servicio para cada mensaje N de la cola (redondeado hacia arriba).
Por ejemplo, la implementación de referencia usa el escalador KEDA de Service Bus para escalar automáticamente la aplicación contenedora en función de la longitud de la cola de Service Bus. La regla de escalado, denominada service-bus-queue-length-rule
, ajusta el número de réplicas de servicio en función del recuento de mensajes de la cola de Service Bus especificada. El messageCount
parámetro se establece en 10, lo que significa que el escalador agrega una réplica por cada 10 mensajes de la cola. Las réplicas máximas (max_replicas
) se establecen en 10 y las réplicas mínimas son implícitamente 0 a menos que se invaliden, lo que permite que el servicio se reduzca verticalmente a cero cuando no haya ningún mensaje en la cola. El cadena de conexión de la cola de Service Bus se almacena de forma segura como un secreto en Azure, denominado azure-servicebus-connection-string
, que se usa para autenticar el escalador en Service Bus.
max_replicas = 10
min_replicas = 1
custom_scale_rule {
name = "service-bus-queue-length-rule"
custom_rule_type = "azure-servicebus"
metadata = {
messageCount = 10
namespace = var.servicebus_namespace
queueName = var.email_request_queue_name
}
authentication {
secret_name = "azure-servicebus-connection-string"
trigger_parameter = "connection"
}
}
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. Al crear imágenes de Docker para los servicios de Java, use imágenes base oficiales de OpenJDK. Estas imágenes solo contienen el conjunto mínimo de paquetes necesarios para que Java se ejecute, lo que minimiza tanto el tamaño del paquete como el área expuesta a ataques.
Utilice Dockerfiles multietapa. Use un Dockerfiles de varias fases para separar los recursos en tiempo de compilación de la imagen de contenedor en tiempo de ejecución. Esto ayuda a mantener las imágenes de producción pequeñas y seguras. También puede usar un servidor de compilación preconfigurado y copiar el archivo jar en la imagen de contenedor.
Ejecute como usuario que no sea de raíz. Ejecute los contenedores de Java como usuario que no sea raíz (mediante el nombre de usuario o UID, $APP_UID) para alinearse con el principio de privilegios mínimos. 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.
La implementación de referencia muestra un proceso de compilación de Docker para incluir en contenedores una aplicación Java. Este Dockerfile usa una compilación de una sola fase con la imagen base de OpenJDK (mcr.microsoft.com/openjdk/jdk:17-ubuntu
), que proporciona el entorno de tiempo de ejecución de Java necesario.
El Dockerfile incluye los pasos siguientes:
- Declaración de volumen: se define un volumen temporal (
/tmp
), lo que permite el almacenamiento de archivos temporal independiente del sistema de archivos principal del contenedor. - Copiar artefactos: el archivo JAR de la aplicación (
email-processor.jar
) se copia en el contenedor, junto con el agente de Application Insights (applicationinsights-agent.jar
) para la supervisión. - Establecimiento del punto de entrada: el contenedor está configurado para ejecutar la aplicación con el agente de Application Insights habilitado, mediante
java -javaagent
para supervisar la aplicación durante el tiempo de ejecución.
Este Dockerfile mantiene la imagen ajustada solo mediante la inclusión de dependencias en tiempo de ejecución, adecuadas para entornos de implementación como Container Apps, que admiten contenedores basados en Linux.
# Use OpenJDK 17 base image on Ubuntu as the foundation
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu
# Define a volume to allow temporary files to be stored separately from the container's main file system
VOLUME /tmp
# Copy the packaged JAR file into the container
COPY target/email-processor.jar app.jar
# Copy the Application Insights agent for monitoring
COPY target/agent/applicationinsights-agent.jar applicationinsights-agent.jar
# Set the entry point to run the application with the Application Insights agent
ENTRYPOINT ["java", "-javaagent:applicationinsights-agent.jar", "-jar", "/app.jar"]
Realice la implementación de referencia
Implemente la implementación de referencia del patrón de aplicación web moderna para Java. 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.
Figura 3. Arquitectura de la implementación de referencia. Arquitectura de la implementación de referencia. Descargue un archivo Visio de esta arquitectura.