En este artículo se describe cómo implementar el patrón de aplicación web moderna. 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 proporciona instrucciones de configuración, código y arquitectura prescriptivas que se alinean con los principios de la Azure Well-Architected Framework. Este patrón 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 le ayuda a optimizar las áreas de alta demanda de la aplicación web. Proporciona instrucciones detalladas para desacoplar estas áreas para habilitar el escalado independiente para la optimización de costos. Este enfoque permite asignar recursos dedicados a componentes críticos, lo que mejora el rendimiento general. Desacoplamiento de servicios separables puede mejorar la confiabilidad evitando ralentizaciones en una parte de la aplicación de afectar a otros. También permite el control de versiones independiente de componentes de aplicación individuales.
Cómo implementar el patrón Modern Web App
Este artículo contiene instrucciones para implementar el patrón de aplicación web moderna. Use los vínculos siguientes para ir a las instrucciones específicas que necesita:
- guía de arquitectura de . Obtenga información sobre cómo modularizar los componentes de la aplicación web y seleccionar las soluciones de plataforma como servicio (PaaS) adecuadas.
- guía de código. Implemente cuatro patrones de diseño para optimizar los componentes desacoplados: Strangler Fig, Queue-Based Load Leveling, Competing Consumers y Health Endpoint Monitoring.
- guía de configuración de . Configure la autenticación, la autorización, el escalado automático y la contenedorización para los componentes desacoplados.
Sugerencia
Hay una implementación de referencia de (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 nivel de producción que incluye todo el código, la arquitectura y las actualizaciones de configuración que se describen 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. Necesita una cola de mensajes, una plataforma de contenedor, un servicio de almacenamiento y un registro de contenedor, como se muestra en el diagrama siguiente:
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 sus necesidades empresariales. Las dos regiones requieren los mismos servicios, salvo que una región tiene una red virtual de concentrador. Use una topología de red en estrella tipo hub-and-spoke 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, agregue un host bastión a la red virtual del centro para administrarlas con seguridad mejorada. En el diagrama siguiente se muestra esta arquitectura:
Desacoplar la arquitectura
Para implementar el patrón Modern Web App, es necesario desacoplar la arquitectura de la aplicación web existente. 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 función específica. Este proceso incluye evaluar la aplicación web actual, modificar la arquitectura y, por último, extraer el código de la aplicación web en una plataforma de contenedor. 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 enlazado representa un límite lógico y es candidato para desacoplamiento. Los servicios que representan funciones empresariales distintas y tienen menos dependencias son buenos candidatos.
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. La desacoplación de estos servicios permite la 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. Planee cómo administrar y compartir 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 de Azure que necesita para admitir el servicio de aplicaciones web que va a extraer. Para obtener instrucciones, consulte la sección Seleccionar los servicios de Azure adecuados de este artículo.
Desacoplar el servicio de aplicaciones web. Defina interfaces y API claras que los servicios de aplicación web recién extraídos pueden usar para interactuar con otras partes del sistema. Diseñe una estrategia de administración de datos que permita a cada servicio administrar sus propios datos, pero garantiza la coherencia y la integridad. Para obtener estrategias de implementación específicas y patrones de diseño que se usarán durante este proceso de extracción, consulte la sección guía de código de .
Utilice almacenamiento independiente para los servicios desacoplados. Para simplificar el control de versiones y la implementación, asegúrese de que cada servicio desacoplado tenga sus propios almacenes de datos. 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. Si implementa canalizaciones de implementación independientes, cada servicio se puede actualizar según su propia programación. Si diferentes equipos u organizaciones dentro de su empresa poseen diferentes servicios, el uso de canalizaciones de implementación independientes proporciona a cada equipo control sobre 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 un componente 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 y use una de 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 las aplicaciones controladas por eventos en las que muchos eventos pequeños e independientes (como los cambios de estado de recursos) deben enrutarse a los suscriptores en un modelo de publicación y suscripción de baja latencia. 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 los elementos de la aplicación que desea incluir en contenedores, necesita una plataforma de aplicación que admita contenedores. El Elegir un servicio de contenedor de Azure guía puede ayudarle a seleccionar uno. Azure tiene tres servicios de contenedor principales: Azure Container Apps, Azure Kubernetes Service (AKS) y App de Azure Service. Comience con Container Apps y use una de 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. Aplicación web para contenedores Elija Aplicación web para contenedores en App Service para obtener la experiencia paaS más sencilla. Implemente un repositorio de contenedores. Cuando se usa un servicio de proceso basado en contenedores, debe 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. La guía Introducción a los registros de contenedor en Azure puede ayudarle a elegir uno.
Guía de código
Para desacoplar y extraer correctamente un servicio independiente, debe actualizar el código de la aplicación web con los siguientes patrones de diseño: Strangler Fig, Queue-Based Load Leveling, Concurrent Consumers, Health Endpoint Monitoring y Retry. En el diagrama siguiente se muestran los roles de estos patrones:
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 del productor del servicio desacoplado para administrar el flujo de mensajes de forma asincrónica mediante una cola.
patrón de consumidores competidores: el patrón De consumidores competidores permite que varias instancias de un servicio desacoplado lean de forma independiente de la misma cola de mensajes y compitan para procesar mensajes. Implemente este patrón en el servicio desacoplado para distribuir tareas entre varias instancias.
patrón de supervisión de puntos de conexión de mantenimiento: el patrón de supervisión de puntos de conexión de mantenimiento expone los puntos de conexión para supervisar el estado y el estado de los distintos componentes 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) Implementar este patrón en la aplicación web principal, en todas las llamadas salientes a otros servicios de Azure, 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 de los pilares del marco de Well-Architected. En la tabla siguiente se proporcionan detalles.
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 | Aplicación web principal y servicio desacoplado | ✔ | ✔ | ✔ |
RE:07 RE:10 OE:07 PE:05 |
||
Patrón Retry | Aplicación web principal y servicio desacoplado | ✔ | RE:07 |
Implementación del patrón Strangler Fig
Use 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 punto de conexión de
/users
en la aplicación monolítica y mueve esa funcionalidad al servicio desacoplado, la capa de enrutamiento dirige todas las solicitudes a/users
al 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 reciben los servicios desacoplados. Comience con un pequeño porcentaje de solicitudes y aumente el uso a lo largo del tiempo a medida que obtenga confianza en la estabilidad y el rendimiento del servicio.
Por ejemplo, la implementación de referencia extrae la funcionalidad de entrega de correo electrónico en un servicio independiente. El servicio se puede introducir gradualmente para controlar un porcentaje 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 sola solicitud necesita interactuar con varios servicios o cuando desea ocultar la complejidad del sistema subyacente del cliente. 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). Asegúrese de que el nuevo servicio desacoplado pueda controlar las solicitudes de forma independiente cuando se accede 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 solicitudes a una velocidad coherente. 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 controle 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 clase
StreamBridge
para publicar mensajes de forma asincrónica en la cola sin bloquear el subproceso que llama: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 sigue respondiendo y puede controlar otras tareas simultáneamente mientras el servicio desacoplado procesa las solicitudes en cola a una velocidad 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 controlar los casos en los que un mensaje se puede procesar 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. Para obtener una lista de la configuración que administra el comportamiento del consumo de mensajes, consulte Spring Cloud Stream con Service Bus.Administrar los cambios en la experiencia del usuario. Al usar el procesamiento asincrónico, es posible que las tareas no se completen inmediatamente. Para establecer expectativas y evitar confusiones, asegúrese de que los usuarios sepan cuándo se siguen procesando sus tareas. 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. El patrón mejora el equilibrio de carga y aumenta la capacidad del sistema para controlar 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 se puede aplicar varias veces sin cambiar el resultado después de la aplicación inicial.
Para implementar el patrón Competing Consumers, siga estas recomendaciones:
Controlar mensajes simultáneos. Cuando los servicios reciben 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 pueden ayudarle a determinar el número adecuado de mensajes simultáneos que se van a controlar. 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 confiable, como Peek-Lock, que reintenta automáticamente los mensajes que producen un error en el procesamiento. Este modo proporciona más confiabilidad que los métodos de eliminación inicial. 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. Enrutar mensajes con formato incorrecto o no procesados a una cola de mensajes fallidos independiente. Este diseño evita el procesamiento repetitivo. Por ejemplo, puede detectar excepciones durante el procesamiento de mensajes y mover mensajes problemáticos a la cola independiente. Con Service Bus, los mensajes se mueven a la cola de mensajes fallidos después de un número especificado de intentos de entrega o tras el rechazo explícito de la aplicación.
Controle los mensajes desordenados. Diseñe consumidores que procesen mensajes que llegan fuera de secuencia. Cuando tiene varios consumidores paralelos, pueden procesar mensajes desordenados.
Escala basada en la longitud de la cola. Los servicios que consumen mensajes de una cola deben escalarse automáticamente en función de la longitud de la cola. El escalado automático basado en escala permite el procesamiento eficaz de 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 separa 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. Al hacerlo, se habilita el escalado sencillo y el uso eficaz 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.
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 entrega de correo electrónico desde una cola de Service Bus.
El procesador registra los detalles de procesamiento de mensajes para ayudar con la solución de problemas y la supervisión. Captura errores de deserialización y proporciona información que puede resultar útil durante la depuración. El servicio se escala en el nivel de contenedor para permitir un control eficaz de los picos de mensajes en función de la longitud de la cola. Este es el código:
@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. El accionador de Spring Boot proporciona compatibilidad integrada con las comprobaciones de estado. 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 expone un punto de conexión de
/actuator/health
que incluye indicadores de estado integrados y comprobaciones personalizadas para diversas dependencias. Para habilitar el punto de conexión de mantenimiento, agregue la dependenciaspring-boot-starter-actuator
en el archivopom.xml
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: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 tecnologías como Azure Spring Apps o Micrometer, que proporcionan indicadores de estado para estos servicios. Si necesita comprobaciones personalizadas, puede implementarlas mediante la creación de un bean de
HealthIndicator
personalizado: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 (for example, 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 the 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 confirmar la vida y la preparación de las aplicaciones que se implementan en Container Apps. Para obtener más información, consulte Sondeos de estado en Container Apps.
Implementar el patrón Retry
El patrón reintento permite a las aplicaciones recuperarse de errores transitorios. Este patrón es fundamental para el patrón Reliable Web App, por lo que la aplicación web ya debe usar el patrón Retry. Aplique el patrón Retry a las solicitudes a los sistemas de mensajería y las solicitudes 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. Asegúrese de configurar el cliente responsable de las interacciones con la cola de mensajes con la configuración de reintento adecuada. Especifique parámetros como el número máximo de reintentos, el retraso entre los reintentos y el retraso máximo.
Utilice el retardo exponencial. Implemente la estrategia de retroceso exponencial para los reintentos. Esta estrategia implica aumentar el tiempo entre cada reintento exponencialmente, lo que ayuda a reducir la carga en el sistema durante períodos de altas tasas de error.
Use la funcionalidad de reintento del SDK. Para los servicios que tienen SDK especializados, como Service Bus o Blob Storage, use los mecanismos de reintento integrados. Estos mecanismos integrados están optimizados para los casos de uso típicos del servicio, pueden controlar los reintentos de forma más eficaz y requieren menos configuración.
Use bibliotecas de resistencia estándar para clientes HTTP. En el caso de los clientes HTTP, puede usar Resilience4j junto con RestTemplate o WebClient de Spring para controlar los reintentos en las comunicaciones HTTP. Puede encapsular RestTemplate con la lógica de reintento de Resilience4j para controlar los errores HTTP transitorios de forma eficaz.
Gestione el bloqueo de mensajes. En el caso de los sistemas basados en mensajes, implemente estrategias de control de mensajes que admitan reintentos sin pérdida de datos. Por ejemplo, use los modos de inspección y bloqueo 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
En las secciones siguientes se proporcionan instrucciones para implementar las actualizaciones de configuración. Cada sección se alinea con uno o varios de los pilares del marco de Well-Architected.
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 los nuevos servicios de Azure (identidades de carga de trabajo) que agregue 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. Ayudan a evitar incluir información confidencial como cadenas de conexión en el código o los 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 ajustelos según sea necesario. Use diferentes identidades para diferentes funciones, como la implementación y la aplicación. Al hacerlo, se limita el posible daño si una identidad está en peligro.
Use la infraestructura como código (IaC). Use Bicep o una herramienta iaC similar, como Terraform, para definir y administrar los recursos en la nube. IaC garantiza una aplicación coherente de las configuraciones de seguridad en las implementaciones y le permite controlar las versiones de la configuración de la 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 los usuarios solo tienen 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 configuraciones incorrectas y permisos innecesarios y rectánquelos o quítelos 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 las inserciones y las extracción de Container Registry. Este es el código:
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 the current user to access 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 dividir la arquitectura monolítica e introduce la desacoplación del servicio. 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 los 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), a menudo proporciona un control granular que 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 los inicios en frío, configure las opciones de escalado automático para mantener un mínimo de una réplica. Un inicio en frío es la inicialización de un servicio desde un estado detenido. Un arranque en frío a menudo retrasa la respuesta. Si minimizar los costos 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 escalado automático.
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 de mensajes de solicitud. El escalador intenta mantener una réplica del servicio para cada N mensajes 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 parámetro messageCount
se establece en 10, que configura el escalador para agregar una réplica por cada 10 mensajes de la cola. El número máximo de réplicas (max_replicas
) se establece en 10. El recuento mínimo de réplicas es implícitamente 0 a menos que se invalide. Esta configuración permite que el servicio se reduzca verticalmente a cero cuando no haya ningún mensaje en la cola. La cadena de conexión de la cola de Service Bus se almacena como un secreto en Azure, denominado azure-servicebus-connection-string
, que se usa para autenticar el escalador en Service Bus. Este es el código de Terraform:
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 es la encapsulación de todas las dependencias necesarias para la aplicación en una imagen ligera que se puede implementar de forma confiable en una amplia gama de hosts. Para desplegar en contenedores, siga estas recomendaciones:
Identifique los límites del dominio. Empiece por identificar los límites del dominio en la aplicación monolítica. Esto le ayuda a determinar qué partes de la aplicación puede extraer en servicios independientes.
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 que Java necesita ejecutar. El uso de estas imágenes minimiza el tamaño del paquete y el área expuesta a ataques.
Utilice Dockerfiles multietapa. Use un Dockerfile de varias fases para separar los recursos en tiempo de compilación de la imagen de contenedor en tiempo de ejecución. El uso de este tipo de archivo ayuda a mantener pequeñas y seguras las imágenes de producción. 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 un usuario que no sea raíz (a través de nombre de usuario o UID $APP_UID) para alinearse con el principio de privilegios mínimos. Al hacerlo, se limitan los posibles efectos de un contenedor en peligro.
Escucha en el puerto 8080. Al ejecutar contenedores como usuario que no es de raíz, configure la aplicación para que escuche en el puerto 8080. Se trata de una convención común para los usuarios que no son raíz.
Encapsular dependencias. Asegúrese de que todas las dependencias que necesita la aplicación estén encapsuladas en la imagen de contenedor de 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 implementa 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. El 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:
- Declarando el volumen. Se define un volumen temporal (
/tmp
). Este volumen proporciona 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
) que se usa para la supervisión. - Establecer el punto de entrada. El contenedor está configurado para ejecutar la aplicación con el agente de Application Insights habilitado. El código usa
java -javaagent
para supervisar la aplicación durante el tiempo de ejecución.
El Dockerfile mantiene la imagen pequeña solo mediante la inclusión de dependencias en tiempo de ejecución. Es adecuado para entornos de implementación como Container Apps que admiten contenedores basados en Linux.
# Use the 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 entrypoint 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 implementar la implementación, puede simular y observar patrones de diseño.
En el diagrama siguiente se muestra la arquitectura de la implementación de referencia: