Este artigo mostra como implementar o padrão Modern Web App. O padrão Modern Web App define como você deve modernizar aplicativos Web na nuvem e introduzir uma arquitetura orientada a serviços. O padrão Modern Web App fornece arquitetura, código e orientação de configuração prescritivos que se alinham com os princípios da Estrutura Well-Architected do Azure e se baseiam no padrão Aplicativo Web Confiável.
Por que usar o padrão Modern Web App?
O padrão Modern Web App ajuda a otimizar áreas de alta demanda do seu aplicativo Web. Ele oferece orientação detalhada para dissociar essas áreas, permitindo dimensionamento independente para otimização de custos. Essa abordagem permite alocar recursos dedicados a componentes críticos, melhorando o desempenho geral. A dissociação de serviços separáveis pode melhorar a confiabilidade, evitando que lentidão em uma parte do aplicativo afete outras e permitindo o controle de versão de componentes individuais do aplicativo de forma independente.
Como implementar o padrão Modern Web App
Este artigo contém diretrizes de arquitetura, código e configuração para implementar o padrão Modern Web App. Use os links a seguir para navegar até a orientação necessária:
- Orientação de arquitetura: saiba como modularizar componentes de aplicativos Web e selecionar soluções de plataforma como serviço (PaaS) apropriadas.
- Orientação de código: implemente quatro padrões de projeto para otimizar os componentes dissociados: Strangler Fig, Nivelamento de carga baseado em fila, Consumidores concorrentes e padrões de monitoramento de ponto final de integridade.
- Diretrizes de configuração: configure autenticação, autorização, dimensionamento automático e conteinerização para os componentes dissociados.
Gorjeta
Há uma implementação de referência (aplicativo de exemplo) do padrão Modern Web App. Ele representa o estado final da implementação do Modern Web App. É um aplicativo Web de nível de produção que apresenta todas as atualizações de código, arquitetura e configuração discutidas neste artigo. Implante e use a implementação de referência para orientar sua implementação do padrão Modern Web App.
Orientações para a arquitetura
O padrão Modern Web App baseia-se no padrão Reliable Web App. Ele requer alguns componentes de arquitetura extra para implementar. Você precisa de uma fila de mensagens, plataforma de contêiner, serviço de armazenamento e um registro de contêiner (consulte a figura 1).
Figura 1. Elementos arquitetónicos essenciais do padrão Modern Web App.
Para um SLO (objetivo de nível de serviço) mais alto, você pode adicionar uma segunda região à arquitetura do seu aplicativo Web. Configure seu balanceador de carga para rotear o tráfego para a segunda região para oferecer suporte a uma configuração ativa-ativa ou ativa-passiva, dependendo da sua necessidade comercial. As duas regiões exigem os mesmos serviços, exceto que uma região tenha uma rede virtual de hub que se conecta. Adote uma topologia de rede hub-and-spoke para centralizar e compartilhar recursos, como um firewall de rede. Acesse o repositório de contêineres por meio da rede virtual do hub. Se você tiver máquinas virtuais, adicione um host bastion à rede virtual do hub para gerenciá-las com segurança (consulte a figura 2).
Figura 2. A arquitetura de padrão do Aplicativo Web Moderno com topologia de rede hub-and-spoke de segunda região e hub-and-spoke.
Arquitetura de desacoplamento
Para implementar o padrão Modern Web App, você precisa desacoplar a arquitetura existente do aplicativo Web. A dissociação da arquitetura envolve a divisão de um aplicativo monolítico em serviços menores e independentes, cada um responsável por um recurso ou funcionalidade específica. Esse processo envolve avaliar o aplicativo Web atual, modificar a arquitetura e, finalmente, extrair o código do aplicativo Web para uma plataforma de contêiner. O objetivo é identificar e extrair sistematicamente os serviços de aplicativos que mais se beneficiam da dissociação. Para dissociar sua arquitetura, siga estas recomendações:
Identificar limites de serviço Aplique princípios de design orientados por domínio para identificar contextos limitados em seu aplicativo monolítico. Cada contexto delimitado representa um limite lógico e pode ser um candidato para um serviço separado. Serviços que representam funções empresariais distintas e têm menos dependências são bons candidatos para a dissociação.
Avalie os benefícios do serviço. Concentre-se nos serviços que mais se beneficiam do dimensionamento independente. Por exemplo, uma dependência externa como um provedor de serviços de email em um aplicativo LOB pode exigir mais isolamento de falhas. Considere serviços que sofrem atualizações ou alterações frequentes. A dissociação desses serviços permite uma implantação independente e reduz o risco de afetar outras partes do aplicativo.
Avaliar a viabilidade técnica. Examine a arquitetura atual para identificar restrições técnicas e dependências que possam afetar o processo de dissociação. Planeje como os dados são gerenciados e compartilhados entre serviços. Os serviços dissociados devem gerenciar seus próprios dados e minimizar o acesso direto ao banco de dados entre os limites do serviço.
Implante os serviços do Azure. Selecione e implante os serviços do Azure necessários para dar suporte ao serviço de aplicativo Web que você pretendia extrair. Use o seguinte : Selecione a seção correta de serviços do Azure para obter orientação.
Desacoplar serviço de aplicativo Web. Defina interfaces e APIs claras para que os serviços de aplicativo Web recém-extraídos interajam com outras partes do sistema. Projete uma estratégia de gerenciamento de dados que permita que cada serviço gerencie seus próprios dados, garantindo consistência e integridade. Para estratégias de implementação específicas e padrões de design a serem usados durante esse processo de extração, consulte a seção Orientação de código.
Use armazenamento independente para serviços dissociados. Cada serviço dissociado deve ter seus próprios armazenamentos de dados para facilitar o controle de versão e a implantação. Por exemplo, a implementação de referência separa o serviço de email do aplicativo Web e elimina a necessidade de o serviço acessar o banco de dados. Em vez disso, o serviço comunica o status de entrega de email de volta ao aplicativo Web por meio de uma mensagem do Barramento de Serviço do Azure e o aplicativo Web salva uma anotação em seu banco de dados.
Implemente pipelines de implantação separados para cada serviço dissociado. Pipelines de implantação separados permitem que cada serviço seja atualizado em seu próprio ritmo. Se diferentes equipes ou organizações dentro da sua empresa possuem serviços diferentes, ter pipelines de implantação separados dá a cada equipe controle sobre suas próprias implantações. Use ferramentas de integração contínua e entrega contínua (CI/CD) como Jenkins, GitHub Actions ou Azure Pipelines para configurar esses pipelines.
Revise os controles de segurança. Certifique-se de que seus controles de segurança estejam atualizados para levar em conta a nova arquitetura, incluindo regras de firewall e controles de acesso.
Selecione os serviços do Azure certos
Para cada serviço do Azure em sua arquitetura, consulte o guia de serviço do Azure relevante no Well-Architected Framework. Para o padrão Modern Web App, você precisa de um sistema de mensagens que ofereça suporte a mensagens assíncronas, uma plataforma de aplicativo que ofereça suporte à conteinerização e um repositório de imagens de contêiner.
Escolha uma fila de mensagens. Uma fila de mensagens é uma parte importante das arquiteturas orientadas a serviços. Ele separa remetentes e recetores de mensagens para permitir mensagens assíncronas. Use a orientação sobre como escolher um serviço de mensagens do Azure para escolher um sistema de mensagens do Azure que ofereça suporte às suas necessidades de design. O Azure tem três serviços de mensagens: Grade de Eventos do Azure, Hubs de Eventos do Azure e Service Bus. Comece com o Service Bus como a opção padrão e use as outras duas opções se o Service Bus não atender às suas necessidades.
Serviço Caso de utilização Service Bus Escolha o Service Bus para uma entrega confiável, ordenada e possivelmente transacional de mensagens de alto valor em aplicativos corporativos. Event Grid Escolha Grade de Eventos quando precisar lidar com um grande número de eventos discretos de forma eficiente. A Grade de Eventos é escalável para aplicativos controlados por eventos em que muitos eventos pequenos e independentes (como alterações no estado do recurso) precisam ser roteados para assinantes em um modelo de baixa latência, publicação-assinatura. Hubs de Eventos Escolha Hubs de Eventos para ingestão massiva de dados de alta taxa de transferência, como telemetria, logs ou análises em tempo real. Os Hubs de Eventos são otimizados para cenários de streaming em que os dados em massa precisam ser ingeridos e processados continuamente. Implemente um serviço de contêiner. Para as partes do seu aplicativo que você deseja colocar em contêineres, você precisa de uma plataforma de aplicativo que ofereça suporte a contêineres. Use a orientação Escolha um serviço de contêiner do Azure para ajudar a tomar sua decisão. O Azure tem três serviços de contêiner principais: Aplicativos de Contêiner do Azure, Serviço Kubernetes do Azure (AKS) e Serviço de Aplicativo do Azure. Comece com Aplicativos de Contêiner como a opção padrão e use as outras duas opções se Aplicativos de Contêiner não atender às suas necessidades.
Serviço Caso de utilização Aplicativos de contêiner Escolha Aplicativos de contêiner se precisar de uma plataforma sem servidor que dimensione e gerencie automaticamente contêineres em aplicativos controlados por eventos. AKS Escolha o AKS se precisar de controle detalhado sobre as configurações do Kubernetes e recursos avançados para escalabilidade, rede e segurança. Aplicativos Web para contêiner Escolha Aplicativo Web para Contêineres no Serviço de Aplicativo para obter a experiência PaaS mais simples. Implemente um repositório de contêiner. Ao usar qualquer serviço de computação baseado em contêiner, é necessário ter um repositório para armazenar as imagens de contêiner. Você pode usar um registro de contêiner público como o Docker Hub ou um registro gerenciado como o Azure Container Registry. Use a orientação Introdução aos registros de contêiner no Azure para ajudar a tomar sua decisão.
Orientação de código
Para desacoplar e extrair com êxito um serviço independente, você precisa atualizar o código do aplicativo Web com os seguintes padrões de design: o padrão Strangler Fig, o padrão Queue-Based Load Leveling, o padrão Competing Consumers, o padrão Health Endpoint Monitoring e o padrão Retry.
Figura 3. Papel dos padrões de design.
Padrão Strangler Fig: O padrão Strangler Fig migra incrementalmente a funcionalidade de um aplicativo monolítico para o serviço dissociado. Implemente esse padrão no aplicativo Web principal para migrar gradualmente a funcionalidade para serviços independentes, direcionando o tráfego com base em pontos de extremidade.
Padrão de Nivelamento de Carga Baseado em Fila: O padrão de Nivelamento de Carga Baseado em Fila gerencia o fluxo de mensagens entre o produtor e o consumidor usando uma fila como buffer. Implemente esse padrão na parte do produtor do serviço desacoplado para gerenciar o fluxo de mensagens de forma assíncrona usando uma fila.
Padrão de consumidores concorrentes: o padrão de consumidores concorrentes permite que várias instâncias do serviço dissociado leiam independentemente a mesma fila de mensagens e compitam para processar mensagens. Implemente esse padrão no serviço dissociado para distribuir tarefas em várias instâncias.
Padrão de monitoramento de ponto de extremidade de integridade: o padrão de monitoramento de ponto de extremidade de integridade expõe pontos de extremidade para monitorar o status e a integridade de diferentes partes do aplicativo Web. (4a) Implemente este padrão na aplicação Web principal. (4b) Implementá-lo também no serviço dissociado para rastrear a integridade dos endpoints.
Padrão de repetição: o padrão de repetição lida com falhas transitórias repetindo operações que podem falhar intermitentemente. (5a) Implemente esse padrão em todas as chamadas de saída para outros serviços do Azure no aplicativo Web principal, como chamadas para fila de mensagens e pontos de extremidade privados. (5b) Implementar também este padrão no serviço dissociado para lidar com falhas transitórias em chamadas para os pontos finais privados.
Cada padrão de design fornece benefícios que se alinham com um ou mais pilares do Well-Architected Framework (consulte a tabela a seguir).
Padrão de estruturação | Local de implementação | Fiabilidade (RE) | Segurança (SE) | Otimização de Custos (CO) | Excelência Operacional (OE) | Eficiência de desempenho (PE) | Apoiando os princípios da estrutura bem arquitetada |
---|---|---|---|---|---|---|---|
Padrão de figo Strangler | Aplicação Web principal | ✔ | ✔ | ✔ | RE:08 CO:07 CO:08 OE:06 OE:11 |
||
Padrão de Redistribuição de Carga Baseada na Fila | Produtor de serviço dissociado | ✔ | ✔ | ✔ | RE:06 RE:07 CO:12 PE:05 |
||
Padrão de consumidores concorrentes | Serviço dissociado | ✔ | ✔ | ✔ | RE:05 RE:07 CO:05 CO:07 PE:05 PE:07 |
||
Padrão de monitoramento de ponto final de integridade | Aplicativo Web principal & serviço dissociado | ✔ | ✔ | ✔ | RE:07 RE:10 OE:07 PE:05 |
||
Padrão de repetição | Aplicativo Web principal & serviço dissociado | ✔ | RE:07 |
Implementar o padrão Strangler Fig
Use o padrão fig Strangler para migrar gradualmente a funcionalidade da base de código monolítica para novos serviços independentes. Extraia novos serviços da base de código monolítica existente e modernize lentamente partes críticas do aplicativo Web. Para implementar o padrão Strangler Fig, siga estas recomendações:
Configure uma camada de roteamento. Na base de código monolítica do aplicativo Web, implemente uma camada de roteamento que direcione o tráfego com base em pontos de extremidade. Use a lógica de roteamento personalizada conforme necessário para lidar com regras de negócios específicas para direcionar o tráfego. Por exemplo, se você tiver um ponto de
/users
extremidade em seu aplicativo monolítico e tiver movido essa funcionalidade para o serviço dissociado, a camada de roteamento direcionará todas as solicitações para/users
o novo serviço.Gerencie a distribuição de recursos. Implemente sinalizadores de recursos e distribuição em estágios para implantar gradualmente os serviços dissociados. O roteamento de aplicativo monolítico existente deve controlar quantas solicitações os serviços dissociados recebem. Comece com uma pequena percentagem de pedidos e aumente a utilização ao longo do tempo à medida que ganha confiança na sua estabilidade e desempenho.
Por exemplo, a implementação de referência extrai a funcionalidade de entrega de email em um serviço autônomo, que pode ser introduzido gradualmente para lidar com uma parte maior das solicitações de envio de emails contendo guias de suporte da Contoso. À medida que o novo serviço prova sua confiabilidade e desempenho, ele pode eventualmente assumir todo o conjunto de responsabilidades de e-mail do monólito, completando a transição.
Utilize um serviço de fachada (se necessário). Um serviço de fachada é útil quando uma única solicitação precisa interagir com vários serviços ou quando você deseja ocultar a complexidade do sistema subjacente do cliente. No entanto, se o serviço dissociado não tiver APIs voltadas para o público, um serviço de fachada pode não ser necessário.
Na base de código do aplicativo Web monolítico, implemente um serviço de fachada para rotear solicitações para o back-end apropriado (monólito ou microsserviço). No novo serviço dissociado, certifique-se de que o novo serviço pode lidar com solicitações de forma independente quando acessado através da fachada.
Implementar o padrão de nivelamento de carga baseado em fila
Implemente o padrão de Nivelamento de Carga Baseado em Fila na parte do produtor do serviço desacoplado para lidar de forma assíncrona com tarefas que não precisam de respostas imediatas. Esse padrão melhora a capacidade de resposta geral do sistema e a escalabilidade usando uma fila para gerenciar a distribuição da carga de trabalho. Permite que o serviço dissociado processe pedidos a um ritmo consistente. Para implementar esse padrão de forma eficaz, siga estas recomendações:
Use o serviço de enfileiramento de mensagens sem bloqueio. Certifique-se de que o processo que envia mensagens para a fila não bloqueie outros processos enquanto aguarda que o serviço desacoplado trate mensagens na fila. Se o processo exigir o resultado da operação de serviço dissociado, implemente uma maneira alternativa de lidar com a situação enquanto aguarda a conclusão da operação em fila. Por exemplo, no Spring Boot, você pode usar a
StreamBridge
classe para publicar mensagens de forma assíncrona na fila sem bloquear o thread de chamada (consulte o código de exemplo):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"); }
Este exemplo Java usa
StreamBridge
para enviar mensagens de forma assíncrona. Essa abordagem garante que o aplicativo principal permaneça responsivo e possa lidar com outras tarefas simultaneamente, enquanto o serviço desacoplado processa as solicitações enfileiradas a uma taxa gerenciável.Implemente a repetição e remoção de mensagens. Implemente um mecanismo para tentar processar novamente mensagens em fila que não podem ser processadas com êxito. Se as falhas persistirem, essas mensagens devem ser removidas da fila. Por exemplo, o Service Bus tem recursos internos de repetição e fila de mensagens mortas.
Configure o processamento idempotente de mensagens. A lógica que processa mensagens da fila deve ser idempotente para lidar com casos em que uma mensagem pode ser processada mais de uma vez. No Spring Boot, você pode usar
@StreamListener
ou@KafkaListener
com um identificador de mensagem exclusivo para evitar o processamento duplicado. Ou você pode organizar o processo de negócios para operar em uma abordagem funcional com o Spring Cloud Stream, onde oconsume
método é definido de uma forma que produz o mesmo resultado quando é executado repetidamente. Leia Spring Cloud Stream com Service Bus para obter mais uma lista de configurações que gerenciam o comportamento de como as mensagens são consumidas.Gerencie as alterações na experiência. O processamento assíncrono pode levar a que as tarefas não sejam imediatamente concluídas. Os utilizadores devem ser informados quando a sua tarefa ainda está a ser processada para definir expectativas corretas e evitar confusões. Use pistas visuais ou mensagens para indicar que uma tarefa está em andamento. Dê aos usuários a opção de receber notificações quando a tarefa for concluída, como um e-mail ou notificação por push.
Implementar o padrão de consumidores concorrentes
Implemente o padrão Consumidores concorrentes no serviço desacoplado para gerenciar tarefas de entrada da fila de mensagens. Esse padrão envolve a distribuição de tarefas entre várias instâncias de serviços dissociados. Esses serviços processam mensagens da fila, melhorando o balanceamento de carga e aumentando a capacidade do sistema para lidar com solicitações simultâneas. O padrão de Consumidores Concorrentes é eficaz quando:
- A sequência de processamento de mensagens não é crucial.
- A fila não é afetada por mensagens malformadas.
- A operação de processamento é idempotente, o que significa que pode ser aplicada várias vezes sem alterar o resultado além da aplicação inicial.
Para implementar o padrão de Consumidores Concorrentes, siga estas recomendações:
Manipule mensagens simultâneas. Ao receber mensagens de uma fila, certifique-se de que o sistema seja dimensionado de forma previsível configurando a simultaneidade para corresponder ao design do sistema. Os resultados do teste de carga ajudam a decidir o número apropriado de mensagens simultâneas a serem manipuladas e você pode começar a partir de uma para medir o desempenho do sistema.
Desative a pré-busca. Desative a pré-busca de mensagens para que os consumidores só busquem mensagens quando estiverem prontas.
Use modos de processamento de mensagens confiáveis. Use um modo de processamento confiável, como PeekLock (ou seu equivalente), que tenta automaticamente mensagens que falham no processamento. Esse modo aumenta a confiabilidade em relação aos métodos de exclusão inicial. Se um trabalhador não conseguir lidar com uma mensagem, outro deverá ser capaz de processá-la sem erros, mesmo que a mensagem seja processada várias vezes.
Implemente o tratamento de erros. Encaminhe mensagens malformadas ou não processáveis para uma fila separada de mensagens mortas. Este design evita o processamento repetitivo. Por exemplo, você pode capturar exceções durante o processamento da mensagem e mover a mensagem problemática para a fila separada. Para o Service Bus, as mensagens são movidas para a fila dead-leter após um número especificado de tentativas de entrega ou em rejeição explícita pelo aplicativo.
Lidar com mensagens fora de ordem. Projete os consumidores para processar mensagens que chegam fora de sequência. Vários consumidores paralelos significam que eles podem processar mensagens fora de ordem.
Dimensione com base no comprimento da fila. Os serviços que consomem mensagens de uma fila devem ser dimensionados automaticamente com base no comprimento da fila. O dimensionamento automático baseado em escala permite o processamento eficiente de picos de mensagens recebidas.
Use a fila de resposta de mensagem. Se o sistema exigir notificações para processamento pós-mensagem, configure uma fila de resposta ou resposta dedicada. Essa configuração divide as mensagens operacionais dos processos de notificação.
Use serviços sem monitoração de estado. Considere o uso de serviços sem monitoração de estado para processar solicitações de uma fila. Permite um dimensionamento fácil e uma utilização eficiente dos recursos.
Configure o registro em log. Integre o registro em log e o tratamento de exceções específicas no fluxo de trabalho de processamento de mensagens. Concentre-se em capturar erros de serialização e direcionar essas mensagens problemáticas para um mecanismo de letra morta. Esses logs fornecem informações valiosas para a solução de problemas.
Por exemplo, a implementação de referência usa o padrão Consumidores concorrentes em um serviço sem estado em execução em Aplicativos de Contêiner para processar as solicitações de entrega de email de uma fila do Barramento de Serviço.
O processador registra detalhes de processamento de mensagens, ajudando na solução de problemas e monitoramento. Ele captura erros de desserialização e fornece informações necessárias ao depurar o processo. O serviço é dimensionado no nível do contêiner, permitindo o tratamento eficiente de picos de mensagens com base no comprimento da fila (consulte o código a seguir).
@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);
}
};
}
}
Implementar o padrão Health Endpoint Monitoring
Implemente o padrão Health Endpoint Monitoring no código principal do aplicativo e no código de serviço desacoplado para controlar a integridade dos pontos de extremidade do aplicativo. Orquestradores como AKS ou Container Apps podem sondar esses pontos de extremidade para verificar a integridade do serviço e reiniciar instâncias não íntegras. O Spring Boot fornece suporte integrado para verificações de integridade com o Spring Boot Actuator, que pode expor pontos de extremidade de verificação de integridade para dependências importantes, como bancos de dados, agentes de mensagens e sistemas de armazenamento. Para implementar o padrão Health Endpoint Monitoring, siga estas recomendações:
Implementar verificações de integridade. Use o Spring Boot Actuator para fornecer pontos de extremidade de verificação de integridade. O Spring Boot Actuator expõe um ponto de extremidade
/actuator/health
que inclui indicadores de integridade integrados e verificações personalizadas para várias dependências. Para habilitar o ponto de extremidade de integridade, adicione aspring-boot-starter-actuator
dependência em seupom.xml
arquivo oubuild.gradle
.<!-- Add Spring Boot Actuator dependency --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
Configure o ponto de extremidade de integridade conforme
application.properties
mostrado na implementação de referência:txt management.endpoints.web.exposure.include=metrics,health,info,retry,retryevents
Valide dependências. O Spring Boot Actuator inclui indicadores de integridade para várias dependências, como bancos de dados, agentes de mensagens (RabbitMQ ou Kafka) e serviços de armazenamento. Para validar a disponibilidade de serviços do Azure, como o Armazenamento de Blobs do Azure ou o Service Bus, use plug-ins da comunidade, como o Azure Spring Apps ou integrações de micrômetros, que fornecem indicadores de integridade para esses serviços. Se forem necessárias verificações personalizadas, você poderá implementá-las criando um 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 } }
Configure os recursos do Azure. Configure o recurso do Azure para usar as URLs de verificação de integridade do aplicativo para confirmar a vivacidade e a prontidão. Por exemplo, você pode usar o Terraform para usar as URLs de verificação de integridade para confirmar a vivacidade e a prontidão dos aplicativos implantados em Aplicativos de Contêiner. Para obter mais informações, consulte Sondas de integridade em aplicativos de contêiner.
Implementar o padrão Retry
O padrão Retry permite que os aplicativos se recuperem de falhas transitórias. O padrão Repetir é central para o padrão Aplicativo Web Confiável, portanto, seu aplicativo Web já deve estar usando o padrão Repetir. Aplique o padrão Repetir a solicitações para os sistemas de mensagens e solicitações emitidas pelos serviços dissociados extraídos do aplicativo Web. Para implementar o padrão Repetir, siga estas recomendações:
Configure as opções de repetição. Ao integrar com uma fila de mensagens, certifique-se de configurar o cliente responsável pelas interações com a fila com as configurações de repetição apropriadas. Especifique parâmetros como o número máximo de tentativas, o atraso entre as tentativas e o atraso máximo.
Use backoff exponencial. Implemente a estratégia de backoff exponencial para tentativas de repetição. Isso significa aumentar exponencialmente o tempo entre cada nova tentativa, o que ajuda a reduzir a carga no sistema durante períodos de altas taxas de falha.
Use a funcionalidade de repetição do SDK. Para serviços com SDKs especializados, como Service Bus ou Armazenamento de Blob, use os mecanismos de repetição internos. Os mecanismos de repetição integrados são otimizados para os casos de uso típicos do serviço e podem lidar com novas tentativas de forma mais eficaz com menos configuração necessária da sua parte.
Adote bibliotecas de resiliência padrão para clientes HTTP. Para clientes HTTP, você pode usar o Resilience4* junto com o RestTemplate ou WebClient do Spring para lidar com novas tentativas em comunicações HTTP. O RestTemplate do Spring pode ser encapsulado com a lógica de repetição do Resilience4j para lidar com erros HTTP transitórios de forma eficaz.
Manipule o bloqueio de mensagens. Para sistemas baseados em mensagens, implemente estratégias de tratamento de mensagens que suportem novas tentativas sem perda de dados, como o uso de modos "peek-lock", quando disponíveis. Certifique-se de que as mensagens com falha sejam repetidas de forma eficaz e movidas para uma fila de mensagens mortas após repetidas falhas.
Diretrizes de configuração
As seções a seguir fornecem orientação sobre como implementar as atualizações de configuração. Cada seção se alinha com um ou mais pilares da estrutura bem arquitetada.
Configuração | Fiabilidade (RE) | Segurança (SE) | Otimização de Custos (CO) | Excelência Operacional (OE) | Eficiência de desempenho (PE) | Apoiando os princípios da estrutura bem arquitetada |
---|---|---|---|---|---|---|
Configurar autenticação e autorização | ✔ | ✔ | SE:05 OE:10 |
|||
Implementar dimensionamento automático independente | ✔ | ✔ | ✔ | RE:06 CO:12 PE:05 |
||
Implantação do serviço Containerize | ✔ | ✔ | CO:13 PE:09 PE:03 |
Configurar autenticação e autorização
Para configurar a autenticação e a autorização em quaisquer novos serviços do Azure (identidades de carga de trabalho) adicionados ao aplicativo Web, siga estas recomendações:
Use identidades gerenciadas para cada novo serviço. Cada serviço independente deve ter sua própria identidade e usar identidades gerenciadas para autenticação de serviço a serviço. As identidades gerenciadas eliminam a necessidade de gerenciar credenciais em seu código e reduzem o risco de vazamento de credenciais. Ele ajuda a evitar colocar informações confidenciais, como cadeias de conexão, em seu código ou arquivos de configuração.
Conceda o menor privilégio a cada novo serviço. Atribua apenas as permissões necessárias a cada nova identidade de serviço. Por exemplo, se uma identidade só precisa enviar por push para um registro de contêiner, não dê a ela permissões pull. Revise essas permissões regularmente e ajuste conforme necessário. Use identidades diferentes para diferentes funções, como a implantação e o aplicativo. Isso limita os danos potenciais se uma identidade for comprometida.
Adotar a infraestrutura como código (IaC). Use o Bicep ou ferramentas IaC semelhantes, como o Terraform, para definir e gerenciar seus recursos de nuvem. O IaC garante a aplicação consistente de configurações de segurança em suas implantações e permite que você controle a versão de sua configuração de infraestrutura.
Para configurar a autenticação e a autorização em usuários (identidades de usuário), siga estas recomendações:
Conceda o menor privilégio aos usuários. Assim como nos serviços, certifique-se de que os usuários recebam apenas as permissões necessárias para executar suas tarefas. Analise e ajuste regularmente essas permissões.
Realizar auditorias de segurança regulares. Reveja e audite regularmente a sua configuração de segurança. Procure por configurações incorretas ou permissões desnecessárias e retifique-as imediatamente.
A implementação de referência usa o IaC para atribuir identidades gerenciadas a serviços adicionados e funções específicas a cada identidade. Ele define funções e permissões de acesso para implantação definindo funções para push e pull do Registro de Contêiner (consulte o código a seguir).
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 o dimensionamento automático independente
O padrão Modern Web App começa a quebrar a arquitetura monolítica e introduz o desacoplamento de serviços. Ao desacoplar uma arquitetura de aplicativo Web, você pode dimensionar serviços dissociados de forma independente. Dimensionar os serviços do Azure para dar suporte a um serviço de aplicativo Web independente, em vez de um aplicativo Web inteiro, otimiza os custos de dimensionamento e, ao mesmo tempo, atende às demandas. Para dimensionar contêineres automaticamente, siga estas recomendações:
Use serviços sem monitoração de estado. Certifique-se de que os seus serviços são apátridas. Se seu aplicativo Web contiver o estado da sessão em processo, externalize-o para um cache distribuído, como o Redis, ou um banco de dados, como o SQL Server.
Configure regras de dimensionamento automático. Use as configurações de dimensionamento automático que fornecem o controle mais econômico sobre seus serviços. Para serviços em contêineres, o dimensionamento baseado em eventos, como o Kubernetes Event-Driven Autoscaler (KEDA), geralmente fornece controle granular, permitindo que você dimensione com base em métricas de eventos. Container Apps e AKS suportam KEDA. Para serviços que não suportam o KEDA, como o Serviço de Aplicativo, use os recursos de dimensionamento automático fornecidos pela própria plataforma. Esses recursos geralmente incluem dimensionamento com base em regras baseadas em métricas ou tráfego HTTP.
Configure o mínimo de réplicas. Para evitar um arranque a frio, defina as definições de dimensionamento automático para manter um mínimo de uma réplica. Um início a frio é quando você inicializa um serviço a partir de um estado interrompido, o que geralmente cria uma resposta atrasada. Se minimizar os custos for uma prioridade e você puder tolerar atrasos de inicialização a frio, defina a contagem mínima de réplicas como 0 ao configurar o dimensionamento automático.
Configure um período de reflexão. Aplique um período de reflexão apropriado para introduzir um atraso entre os eventos de dimensionamento. O objetivo é evitar atividades de dimensionamento excessivo desencadeadas por picos de carga temporários.
Configure o dimensionamento baseado em fila. Se seu aplicativo usa uma fila de mensagens como o Service Bus, configure suas configurações de dimensionamento automático para dimensionar com base no comprimento da fila com mensagens de solicitação. O scaler visa manter uma réplica do serviço para cada mensagem N na fila (arredondada para cima).
Por exemplo, a implementação de referência usa o escalonador KEDA do Service Bus para dimensionar automaticamente o Aplicativo de Contêiner com base no comprimento da fila do Barramento de Serviço. A regra de dimensionamento, chamada service-bus-queue-length-rule
, ajusta o número de réplicas de serviço dependendo da contagem de mensagens na fila especificada do Service Bus. O messageCount
parâmetro é definido como 10, o que significa que o escalador adiciona uma réplica para cada 10 mensagens na fila. O máximo de réplicas (max_replicas
) é definido como 10 e o mínimo de réplicas é implicitamente 0, a menos que seja substituído, o que permite que o serviço diminua para zero quando não houver mensagens na fila. A cadeia de conexão para a fila do Service Bus é armazenada com segurança como um segredo no Azure, chamado azure-servicebus-connection-string
, que é usado para autenticar o dimensionador para o 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"
}
}
Implantação do serviço Containerize
A conteinerização significa que todas as dependências para que o aplicativo funcione são encapsuladas em uma imagem leve que pode ser implantada de forma confiável em uma ampla variedade de hosts. Para contentorizar a implementação, siga estas recomendações:
Identificar limites de domínio. Comece identificando os limites de domínio em seu aplicativo monolítico. Isso ajuda a determinar quais partes do aplicativo você pode extrair em serviços separados.
Crie imagens do Docker. Ao criar imagens do Docker para seus serviços Java, use imagens base oficiais do OpenJDK. Essas imagens contêm apenas o conjunto mínimo de pacotes necessários para a execução do Java, o que minimiza o tamanho do pacote e a área da superfície de ataque.
Use Dockerfiles de vários estágios. Use um Dockerfiles de vários estágios para separar os ativos de tempo de compilação da imagem do contêiner de tempo de execução. Ajuda a manter as suas imagens de produção pequenas e seguras. Você também pode usar um servidor de compilação pré-configurado e copiar o arquivo jar para a imagem do contêiner.
Execute como um usuário não-root. Execute seus contêineres Java como um usuário não raiz (via nome de usuário ou UID, $APP_UID) para alinhar com o princípio de menor privilégio. Limita os efeitos potenciais de um recipiente comprometido.
Ouça na porta 8080. Ao executar como um usuário não-root, configure seu aplicativo para escutar na porta 8080. É uma convenção comum para usuários não-root.
Encapsular dependências. Certifique-se de que todas as dependências para o aplicativo funcionar estejam encapsuladas na imagem do contêiner do Docker. O encapsulamento permite que o aplicativo seja implantado de forma confiável em uma ampla gama de hosts.
Escolha as imagens de base certas. A imagem base escolhida depende do seu ambiente de implantação. Se você estiver implantando em Aplicativos de Contêiner, por exemplo, precisará usar imagens do Linux Docker.
A implementação de referência demonstra um processo de construção do Docker para containerizar um aplicativo Java. Este Dockerfile usa uma compilação de estágio único com a imagem base do OpenJDK (mcr.microsoft.com/openjdk/jdk:17-ubuntu
), que fornece o ambiente de tempo de execução Java necessário.
O Dockerfile inclui as seguintes etapas:
- Declaração de volume: um volume temporário (
/tmp
) é definido, permitindo o armazenamento temporário de arquivos separado do sistema de arquivos principal do contêiner. - Copiando artefatos: o arquivo JAR do aplicativo (
email-processor.jar
) é copiado para o contêiner, juntamente com o agente do Application Insights (applicationinsights-agent.jar
) para monitoramento. - Definindo o ponto de entrada: o contêiner é configurado para executar o aplicativo com o agente do Application Insights habilitado, usando
java -javaagent
para monitorar o aplicativo durante o tempo de execução.
Esse Dockerfile mantém a imagem enxuta, incluindo apenas dependências de tempo de execução, adequadas para ambientes de implantação como Container Apps, que suportam contêineres baseados em 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"]
Implantar a implementação de referência
Implante a implementação de referência do Modern Web App Pattern for Java. Há instruções para desenvolvimento e implantação de produção no repositório. Depois de implantar, você pode simular e observar padrões de projeto.
Figura 3. Arquitetura da implementação de referência. Baixe um arquivo Visio desta arquitetura.