Microsserviços em contêineres
Observação
Este eBook foi publicado na primavera de 2017 e não foi atualizado desde então. Há muito no livro que permanece valioso, mas parte do material está desatualizado.
O desenvolvimento de aplicativos cliente-servidor resultou em um foco na criação de aplicativos em camadas que usam tecnologias específicas em cada camada. Esses aplicativos geralmente são chamados de aplicativos monolíticos e são empacotados em hardware pré-dimensionado para cargas de pico. As principais desvantagens dessa abordagem de desenvolvimento são o acoplamento estrito entre componentes em cada camada, o fato de os componentes individuais não poderem ser facilmente dimensionados e o custo do teste. Uma atualização simples pode ter efeitos imprevistos no restante da camada e, portanto, uma alteração em um componente de aplicativo exige que toda a camada seja retestada e reimplantada.
Particularmente preocupante na era da nuvem, é que componentes individuais não podem ser facilmente dimensionados. Um aplicativo monolítico contém funcionalidade específica do domínio e normalmente é dividido por camadas funcionais, como front-end, lógica de negócios e armazenamento de dados. Um aplicativo monolítico é dimensionado clonando todo o aplicativo em vários computadores, conforme ilustrado na Figura 8-1.
Figura 8-1: abordagem de dimensionamento de aplicativos monolíticos
Microsserviços
Os microsserviços oferecem uma abordagem diferente para desenvolvimento e implantação de aplicativos, uma abordagem adequada aos requisitos de agilidade, escala e confiabilidade de aplicativos de nuvem modernos. Um aplicativo de microsserviços é decomposto em componentes independentes que trabalham juntos para fornecer a funcionalidade geral do aplicativo. O termo microsserviço enfatiza que os aplicativos devem ser compostos por serviços pequenos o suficiente para refletir preocupações independentes, de modo que cada microsserviço implemente uma única função. Além disso, cada microsserviço tem contratos bem definidos para que outros microsserviços possam se comunicar e compartilhar dados com ele. Exemplos típicos de microsserviços incluem cestas de compras, processamento de inventário, subsistemas de compra e processamento de pagamentos.
Os microsserviços podem escalar horizontalmente de forma independente, em comparação com aplicativos monolíticos gigantes que são dimensionados juntos. Isso significa que uma área funcional específica, que requer mais capacidade de processamento ou largura de banda de rede para dar suporte à demanda, pode ser dimensionada em vez de escalar desnecessariamente outras áreas do aplicativo. A Figura 8-2 ilustra essa abordagem, em que os microsserviços são implantados e dimensionados de forma independente, criando instâncias de serviços entre computadores.
Figura 8-2: Abordagem de dimensionamento de aplicativos de microsserviços
A expansão de microsserviços pode ser quase instantânea, permitindo que um aplicativo se adapte às cargas de alteração. Por exemplo, um único microsserviço na funcionalidade voltada para a Web de um aplicativo pode ser o único microsserviço no aplicativo que precisa ser dimensionado para lidar com tráfego de entrada adicional.
O modelo clássico de escalabilidade do aplicativo é ter uma camada sem estado e com balanceamento de carga com um armazenamento de dados externo compartilhado para armazenar dados persistentes. Os microsserviços com estado gerenciam seus próprios dados persistentes, geralmente armazenando-os localmente nos servidores nos quais são colocados, para evitar a sobrecarga de acesso à rede e a complexidade das operações entre serviços. Isso permite o processamento mais rápido possível de dados e pode eliminar a necessidade de sistemas de cache. Além disso, microsserviços escalonáveis com estado geralmente particionam dados entre suas instâncias, para gerenciar o tamanho dos dados e transferir a taxa de transferência além da qual um único servidor pode dar suporte.
Os microsserviços também dão suporte a atualizações independentes. Esse acoplamento flexível entre microsserviços fornece uma evolução rápida e confiável do aplicativo. Sua natureza independente e distribuída dá suporte a atualizações sem interrupção, em que apenas um subconjunto de instâncias de um único microsserviço será atualizado a qualquer momento. Portanto, se um problema for detectado, uma atualização com bug poderá ser revertida antes que todas as instâncias sejam atualizadas com o código ou a configuração com falha. Da mesma forma, os microsserviços normalmente usam o controle de versão de esquema, para que os clientes vejam uma versão consistente quando as atualizações estão sendo aplicadas, independentemente da instância de microsserviço com a qual estão se comunicando.
Portanto, os aplicativos de microsserviço têm muitos benefícios em relação a aplicativos monolíticos:
- Cada microsserviço é relativamente pequeno, fácil de gerenciar e desenvolver.
- Cada microsserviço pode ser desenvolvido e implantado de forma independente de outros serviços.
- Cada microsserviço pode ser colocado em escala de forma independente. Por exemplo, um serviço de catálogo ou serviço de cesta de compras pode precisar ser expandido mais do que um serviço de pedidos. Portanto, a infraestrutura resultante consumirá recursos com mais eficiência na expansão.
- Cada microsserviço isola eventuais problemas. Por exemplo, se houver um problema em um serviço, ele só afetará esse serviço. Os outros serviços podem continuar a lidar com solicitações.
- Cada microsserviço pode usar as tecnologias mais recentes. Como os microsserviços são autônomos e executados lado a lado, as tecnologias e estruturas mais recentes podem ser usadas em vez de terem a obrigação de usar uma estrutura mais antiga que pode ser usada por um aplicativo monolítico.
No entanto, uma solução baseada em microsserviço também tem possíveis desvantagens:
- A escolha de como particionar um aplicativo em microsserviços pode ser desafiadora, pois cada microsserviço precisa ser completamente autônomo, de ponta a ponta, incluindo a responsabilidade por suas fontes de dados.
- Os desenvolvedores precisam implementar a comunicação entre serviços, o que adiciona complexidade e latência ao aplicativo.
- Geralmente, as transações atômicas entre vários microsserviços não são possíveis. Portanto, os requisitos de negócios precisam adotar uma eventual consistência entre microsserviços.
- Na produção, há uma complexidade operacional na implantação e no gerenciamento de um sistema composto de muitos serviços independentes.
- A comunicação direta entre cliente e microsserviço pode dificultar a refatoração dos contratos de microsserviços. Por exemplo, ao longo do tempo, o modo como o sistema é particionado em serviços pode precisar ser alterado. Um único serviço pode ser dividido em dois ou mais serviços e dois serviços podem se mesclar. Quando os clientes se comunicam diretamente com microsserviços, esse trabalho de refatoração pode interromper a compatibilidade com aplicativos cliente.
Transporte em contêineres
A conteinerização é uma abordagem de desenvolvimento de software na qual um aplicativo e seu conjunto de dependências com controle de versão, além de sua configuração de ambiente abstraída como arquivos de manifesto de implantação, são empacotados juntos como uma imagem de contêiner, testados como uma unidade e implantados em um sistema operacional host.
Um contêiner é um ambiente operacional isolado, controlado por recursos e portátil, no qual um aplicativo pode ser executado sem tocar nos recursos de outros contêineres ou no host. Assim, um contêiner parece e age como um computador físico recém-instalado ou uma máquina virtual.
Há muitas semelhanças entre contêineres e máquinas virtuais, conforme ilustrado na Figura 8-3.
Figura 8-3: comparação de máquinas virtuais e contêineres
Um contêiner executa um sistema operacional, tem um sistema de arquivos e pode ser acessado por meio de uma rede como se fosse uma máquina virtual. No entanto, a tecnologia e os conceitos usados pelos contêineres são muito diferentes das máquinas virtuais. As máquinas virtuais incluem os aplicativos, as dependências necessárias e um sistema operacional convidado completo. Os contêineres incluem o aplicativo e suas dependências, mas compartilham o sistema operacional com outros contêineres, sendo executados como processos isolados no sistema operacional host (além dos contêineres do Hyper-V que são executados dentro de uma máquina virtual especial em cada contêiner). Portanto, os contêineres compartilham recursos e normalmente exigem menos recursos do que as máquinas virtuais.
A vantagem de uma abordagem de desenvolvimento e implantação voltada para contêineres é que ela elimina a maioria dos problemas que surgem de configurações de ambiente inconsistentes e dos problemas que vêm com elas. Além disso, os contêineres permitem a funcionalidade de expansão rápida de aplicativos com a criação de instâncias de novos contêineres conforme a necessidade.
Os principais conceitos ao criar e trabalhar com contêineres são:
- Host de Contêiner: a máquina virtual ou física configurada para hospedar contêineres. O host do contêiner executará um ou mais contêineres.
- Imagem de Contêiner: uma imagem consiste em uma união de sistemas de arquivos em camadas empilhados uns sobre os outros e é a base de um contêiner. Uma imagem não tem estado e nunca muda, pois é implantada em ambientes diferentes.
- Contêiner: um contêiner é uma instância de runtime de uma imagem.
- Imagem do sistema operacional do contêiner: contêineres são implantados a partir de imagens. A imagem do sistema operacional do contêiner é a primeira potencialmente de muitas camadas de imagem que compõem um contêiner. Um sistema operacional de contêiner é imutável e não pode ser modificado.
- Repositório de Contêiner: sempre que uma imagem de contêiner é criada, a imagem e suas dependências são armazenadas em um repositório local. Essas imagens podem ser reutilizadas várias vezes no host do contêiner. As imagens de contêiner também podem ser armazenadas em um registro público ou privado, como o Docker Hub, para que possam ser usadas em hosts de contêiner diferentes.
As empresas estão cada vez mais adotando contêineres ao implementar aplicativos baseados em microsserviço, e o Docker se tornou a implementação de contêiner padrão que foi adotada pela maioria das plataformas de software e fornecedores de nuvem.
O aplicativo de referência eShopOnContainers usa o Docker para hospedar quatro microsserviços de back-end conteinerizados, conforme ilustrado na Figura 8-4.
Figura 8-4: eShopOnContainers referenciam microsserviços de back-end do aplicativo
A arquitetura dos serviços de back-end no aplicativo de referência é decomposta em vários subsistemas autônomos na forma de microsserviços e contêineres colaboradores. Cada microsserviço fornece uma única área de funcionalidade: um serviço de identidade, um serviço de catálogo, um serviço de pedidos e um serviço de cesta.
Cada microsserviço tem seu próprio banco de dados, permitindo que ele seja totalmente separado dos outros microsserviços. Quando necessário, a consistência entre bancos de dados de microsserviços diferentes é obtida usando eventos no nível do aplicativo. Para obter mais informações, consulte Comunicação entre microsserviços.
Para obter mais informações sobre o aplicativo de referência, confira Microsserviços do .NET: arquitetura para aplicativos .NET conteinerizados.
Comunicação entre cliente e microsserviços
O aplicativo móvel eShopOnContainers se comunica com os microsserviços de back-end conteinerizados usando a comunicação direta entre cliente e microsserviço , que é mostrada na Figura 8-5.
Figura 8-5: Comunicação direta entre cliente e microsserviço
Com a comunicação direta entre cliente e microsserviço, o aplicativo móvel faz solicitações para cada microsserviço diretamente por meio de seu ponto de extremidade público, com uma porta TCP diferente por microsserviço. Na produção, o ponto de extremidade normalmente é mapeado para o balanceador de carga do microsserviço, que distribui solicitações entre as instâncias disponíveis.
Dica
Considere usar a comunicação do gateway de API. A comunicação direta entre cliente e microsserviço pode ter desvantagens ao criar um aplicativo baseado em microsserviço grande e complexo, mas é mais do que adequado para um aplicativo pequeno. Ao criar um aplicativo baseado em microsserviço grande com dezenas de microsserviços, considere usar a comunicação do gateway de API. Para obter mais informações, confira Microsserviços do .NET: Arquitetura do .NET para Aplicativos Conteinerizados.
Comunicação entre microsserviços
Um aplicativo baseado em microsserviços é um sistema distribuído, potencialmente em execução em vários computadores. Cada instância de serviço geralmente é um processo. Portanto, os serviços precisam interagir usando um protocolo de comunicação entre processos, como HTTP, TCP, AMQP (Advanced Message Queuing Protocol) ou protocolos binários, dependendo da natureza de cada serviço.
As duas abordagens comuns para comunicação de microsserviço para microsserviço são comunicação REST baseada em HTTP ao consultar dados e mensagens assíncronas leves ao comunicar atualizações entre vários microsserviços.
A comunicação controlada por eventos baseada em mensagens assíncrona é essencial ao propagar alterações em vários microsserviços. Com essa abordagem, um microsserviço publica um evento quando algo notável acontece, por exemplo, quando ele atualiza uma entidade de negócios. Outros microsserviços assinam esses eventos. Em seguida, quando um microsserviço recebe um evento, ele atualiza suas próprias entidades comerciais, o que, por sua vez, pode levar a mais eventos sendo publicados. Essa funcionalidade de publicação-assinatura geralmente é obtida com um barramento de eventos.
Um barramento de eventos permite a comunicação de publicação-assinatura entre microsserviços, sem exigir que os componentes estejam explicitamente cientes uns dos outros, conforme mostrado na Figura 8-6.
Figura 8-6: Publicar-assinar com um barramento de eventos
Do ponto de vista do aplicativo, o barramento de eventos é simplesmente um canal de publicação e assinatura exposto por meio de uma interface. No entanto, a maneira como o barramento de eventos é implementado pode variar. Por exemplo, uma implementação de barramento de eventos pode usar RabbitMQ, Barramento de Serviço do Azure ou outro barramento de serviço, como NServiceBus e MassTransit. A Figura 8-7 mostra como um barramento de eventos é usado no aplicativo de referência eShopOnContainers.
Figura 8-7: Comunicação controlada por eventos assíncrona no aplicativo de referência
O barramento de eventos eShopOnContainers, implementado usando o RabbitMQ, fornece funcionalidades assíncronas de publicação-assinatura de um para muitos. Isso significa que, depois de publicar um evento, pode haver vários assinantes escutando o mesmo evento. A Figura 8-9 ilustra essa relação.
Figura 8-9: comunicação de um para muitos
Essa abordagem de comunicação um com muitos usa eventos para implementar transações comerciais que abrangem vários serviços, o que garante a consistência eventual entre os serviços. Uma transação eventual-consistente consiste em uma série de etapas distribuídas. Portanto, quando o microsserviço de perfil de usuário recebe o comando UpdateUser, ele atualiza os detalhes do usuário em seu banco de dados e publica o evento UserUpdated no barramento de eventos. O microsserviço de cesta e o microsserviço de pedidos assinaram para receber esse evento e, em resposta, atualizam suas informações de comprador em seus respectivos bancos de dados.
Observação
O barramento de eventos eShopOnContainers, implementado com RabbitMQ, destina-se a ser usado apenas como uma prova de conceito. Para sistemas de produção, implementações alternativas do barramento de eventos devem ser consideradas.
Para obter informações sobre a implementação do barramento de eventos, consulte Microsserviços do .NET: arquitetura para aplicativos .NET em contêineres.
Resumo
Os microsserviços oferecem uma abordagem de desenvolvimento e implantação de aplicativos que é adequada aos requisitos de agilidade, escala e confiabilidade de aplicativos de nuvem modernos. Uma das vantagens main dos microsserviços é que eles podem ser dimensionados de forma independente, o que significa que uma área funcional específica pode ser dimensionada, o que requer mais capacidade de processamento ou largura de banda de rede para dar suporte à demanda, sem dimensionar desnecessariamente áreas do aplicativo que não estão enfrentando maior demanda.
Um contêiner é um ambiente operacional isolado, controlado por recursos e portátil, no qual um aplicativo pode ser executado sem tocar nos recursos de outros contêineres ou no host. As empresas estão adotando cada vez mais contêineres ao implementar aplicativos baseados em microsserviço, e o Docker se tornou a implementação de contêiner padrão que foi adotada pela maioria das plataformas de software e fornecedores de nuvem.