Aproveitando contêineres e orquestradores
Gorjeta
Este conteúdo é um excerto do eBook, Architecting Cloud Native .NET Applications for Azure, disponível no .NET Docs ou como um PDF transferível gratuito que pode ser lido offline.
Contêineres e orquestradores são projetados para resolver problemas comuns a abordagens de implantação monolíticas.
Desafios com implantações monolíticas
Tradicionalmente, a maioria dos aplicativos tem sido implantada como uma única unidade. Tais aplicações são referidas como um monólito. Essa abordagem geral de implantação de aplicativos como unidades únicas, mesmo que sejam compostas por vários módulos ou assemblies, é conhecida como arquitetura monolítica, como mostra a Figura 3-1.
Figura 3-1. Arquitetura monolítica.
Embora tenham o benefício da simplicidade, as arquiteturas monolíticas enfrentam muitos desafios:
Implementação
Além disso, eles exigem uma reinicialização do aplicativo, o que pode afetar temporariamente a disponibilidade se as técnicas de tempo de inatividade zero não forem aplicadas durante a implantação.
Dimensionamento
Um aplicativo monolítico é hospedado inteiramente em uma única instância de máquina, geralmente exigindo hardware de alta capacidade. Se qualquer parte do monólito exigir dimensionamento, outra cópia de todo o aplicativo deverá ser implantada em outra máquina. Com um monólito, você não pode dimensionar componentes de aplicativos individualmente - é tudo ou nada. O dimensionamento de componentes que não exigem dimensionamento resulta em uso de recursos ineficiente e dispendioso.
Environment
Os aplicativos monolíticos geralmente são implantados em um ambiente de hospedagem com um sistema operacional pré-instalado, tempo de execução e dependências de biblioteca. Esse ambiente pode não corresponder àquele no qual o aplicativo foi desenvolvido ou testado. Inconsistências em ambientes de aplicativos são uma fonte comum de problemas para implantações monolíticas.
Acoplamento
É provável que uma aplicação monolítica experimente um alto acoplamento entre seus componentes funcionais. Sem limites rígidos, as mudanças no sistema muitas vezes resultam em efeitos colaterais não intencionais e caros. Novos recursos/correções tornam-se complicados, demorados e caros de implementar. As atualizações exigem testes extensivos. O acoplamento também dificulta a refatoração de componentes ou a troca em implementações alternativas. Mesmo quando construída com uma separação estrita de preocupações, a erosão arquitetônica se instala à medida que a base de código monolítica se deteriora com "casos especiais" intermináveis.
Bloqueio da plataforma
Uma aplicação monolítica é construída com uma única pilha de tecnologia. Embora ofereça uniformidade, este compromisso pode tornar-se um obstáculo à inovação. Novos recursos e componentes serão construídos usando a pilha atual do aplicativo - mesmo quando tecnologias mais modernas podem ser uma escolha melhor. Um risco a longo prazo é a sua pilha de tecnologia tornar-se desatualizada e obsoleta. Rearquitetar um aplicativo inteiro para uma plataforma nova e mais moderna é, na melhor das hipóteses, caro e arriscado.
Quais são os benefícios dos contêineres e orquestradores?
Introduzimos os contentores no Capítulo 1. Destacamos como a Cloud Native Computing Foundation (CNCF) classifica a conteinerização como o primeiro passo em seu Cloud Native Trail Map - orientação para empresas que estão começando sua jornada nativa da nuvem. Nesta seção, discutimos os benefícios dos contêineres.
O Docker é a plataforma de gerenciamento de contêineres mais popular. Ele funciona com contêineres em Linux ou Windows. Os contêineres fornecem ambientes de aplicativos separados, mas reproduzíveis, que são executados da mesma maneira em qualquer sistema. Esse aspeto os torna perfeitos para desenvolver e hospedar serviços nativos da nuvem. Os contentores são isolados uns dos outros. Dois contêineres no mesmo hardware host podem ter versões diferentes de software, sem causar conflitos.
Os contêineres são definidos por arquivos simples baseados em texto que se tornam artefatos de projeto e são verificados no controle do código-fonte. Embora servidores completos e máquinas virtuais exijam esforço manual para atualização, os contêineres são facilmente controlados por versão. Os aplicativos criados para serem executados em contêineres podem ser desenvolvidos, testados e implantados usando ferramentas automatizadas como parte de um pipeline de compilação.
Os contentores são imutáveis. Depois de definir um contêiner, você pode recriá-lo e executá-lo exatamente da mesma maneira. Essa imutabilidade se presta ao design baseado em componentes. Se algumas partes de um aplicativo evoluem de forma diferente de outras, por que reimplantar o aplicativo inteiro quando você pode apenas implantar as partes que mudam com mais frequência? Diferentes recursos e preocupações transversais de um aplicativo podem ser divididos em unidades separadas. A Figura 3-2 mostra como um aplicativo monolítico pode tirar proveito de contêineres e microsserviços delegando determinados recursos ou funcionalidades. A funcionalidade restante no próprio aplicativo também foi conteinerizada.
Figura 3-2. Decompondo um aplicativo monolítico para abraçar microsserviços.
Cada serviço nativo da nuvem é criado e implantado em um contêiner separado. Cada um pode ser atualizado conforme necessário. Serviços individuais podem ser hospedados em nós com recursos apropriados para cada serviço. O ambiente em que cada serviço é executado é imutável, compartilhado entre ambientes de desenvolvimento, teste e produção e facilmente versionado. O acoplamento entre diferentes áreas do aplicativo ocorre explicitamente como chamadas ou mensagens entre serviços, não como dependências em tempo de compilação dentro do monólito. Você também pode escolher a tecnologia que melhor se adapta a um determinado recurso sem exigir alterações no restante do aplicativo.
Os serviços em contentores requerem uma gestão automatizada. Não seria viável administrar manualmente um grande conjunto de contêineres implantados de forma independente. Por exemplo, considere as seguintes tarefas:
- Como as instâncias de contêiner serão provisionadas em um cluster de muitas máquinas?
- Uma vez implantados, como os contêineres descobrirão e se comunicarão uns com os outros?
- Como os contêineres podem ser dimensionados sob demanda?
- Como você monitora a integridade de cada contêiner?
- Como você protege um contêiner contra falhas de hardware e software?
- Como atualizar contêineres para um aplicativo ativo com tempo de inatividade zero?
Os orquestradores de contêineres abordam e automatizam essas e outras preocupações.
No ecossistema nativo da nuvem, o Kubernetes tornou-se o orquestrador de contêineres de fato. É uma plataforma de código aberto gerida pela Cloud Native Computing Foundation (CNCF). O Kubernetes automatiza a implantação, o dimensionamento e as preocupações operacionais de cargas de trabalho em contêineres em um cluster de máquinas. No entanto, instalar e gerenciar o Kubernetes é notoriamente complexo.
Uma abordagem muito melhor é aproveitar o Kubernetes como um serviço gerenciado de um fornecedor de nuvem. A nuvem do Azure apresenta uma plataforma Kubernetes totalmente gerenciada intitulada Serviço Kubernetes do Azure (AKS). O AKS abstrai a complexidade e a sobrecarga operacional do gerenciamento do Kubernetes. Você consome o Kubernetes como um serviço de nuvem; A Microsoft assume a responsabilidade de gerenciá-lo e apoiá-lo. O AKS também se integra perfeitamente com outros serviços e ferramentas de desenvolvimento do Azure.
AKS é uma tecnologia baseada em cluster. Um pool de máquinas virtuais federadas, ou nós, é implantado na nuvem do Azure. Juntos, eles formam um ambiente altamente disponível, ou cluster. O cluster aparece como uma entidade única e perfeita para seu aplicativo nativo da nuvem. Nos bastidores, o AKS implanta seus serviços em contêineres nesses nós seguindo uma estratégia predefinida que distribui uniformemente a carga.
Quais são os benefícios de escala?
Os serviços criados em contêineres podem aproveitar os benefícios de dimensionamento fornecidos por ferramentas de orquestração como o Kubernetes. Por design, os recipientes só sabem sobre si mesmos. Depois de ter vários contêineres que precisam trabalhar juntos, você deve organizá-los em um nível mais alto. Organizar um grande número de contêineres e suas dependências compartilhadas, como a configuração de rede, é onde as ferramentas de orquestração entram para salvar o dia! O Kubernetes cria uma camada de abstração sobre grupos de contêineres e os organiza em pods. Os pods são executados em máquinas de trabalho conhecidas como nós. Esta estrutura organizada é referida como um cluster. A Figura 3-3 mostra os diferentes componentes de um cluster Kubernetes.
Figura 3-3. Componentes de cluster do Kubernetes.
O dimensionamento de cargas de trabalho em contêineres é um recurso fundamental dos orquestradores de contêineres. O AKS suporta dimensionamento automático em duas dimensões: instâncias de contêiner e nós de computação. Juntos, eles dão à AKS a capacidade de responder de forma rápida e eficiente a picos de demanda e adicionar recursos adicionais. Discutiremos o escalonamento no AKS mais adiante neste capítulo.
Declarativo versus imperativo
O Kubernetes suporta configuração declarativa e imperativa. A abordagem imperativa envolve a execução de vários comandos que dizem ao Kubernetes o que fazer em cada etapa do caminho. Execute esta imagem. Exclua este pod. Exponha esta porta. Com a abordagem declarativa, você cria um arquivo de configuração, chamado manifesto, para descrever o que deseja em vez do que fazer. O Kubernetes lê o manifesto e transforma o estado final desejado em estado final real.
Os comandos imperativos são ótimos para aprendizagem e experimentação interativa. No entanto, você desejará criar declarativamente arquivos de manifesto do Kubernetes para adotar uma infraestrutura como abordagem de código, fornecendo implantações confiáveis e repetíveis. O arquivo de manifesto se torna um artefato de projeto e é usado em seu pipeline de CI/CD para automatizar implantações do Kubernetes.
Se você já configurou seu cluster usando comandos imperativos, poderá exportar um manifesto declarativo usando kubectl get svc SERVICENAME -o yaml > service.yaml
. Este comando produz um manifesto semelhante ao mostrado abaixo:
apiVersion: v1
kind: Service
metadata:
creationTimestamp: "2019-09-13T13:58:47Z"
labels:
component: apiserver
provider: kubernetes
name: kubernetes
namespace: default
resourceVersion: "153"
selfLink: /api/v1/namespaces/default/services/kubernetes
uid: 9b1fac62-d62e-11e9-8968-00155d38010d
spec:
clusterIP: 10.96.0.1
ports:
- name: https
port: 443
protocol: TCP
targetPort: 6443
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
Ao usar a configuração declarativa, você pode visualizar as alterações que serão feitas antes de confirmá-las usando kubectl diff -f FOLDERNAME
na pasta onde os arquivos de configuração estão localizados. Quando tiver certeza de que deseja aplicar as alterações, execute kubectl apply -f FOLDERNAME
. Adicionar -R
para processar recursivamente uma hierarquia de pastas.
Você também pode usar a configuração declarativa com outros recursos do Kubernetes, sendo um deles as implantações. As implantações declarativas ajudam a gerenciar versões, atualizações e dimensionamento. Eles instruem o controlador de implantação do Kubernetes sobre como implantar novas alterações, dimensionar a carga ou reverter para uma revisão anterior. Se um cluster for instável, uma implantação declarativa retornará automaticamente o cluster ao estado desejado. Por exemplo, se um nó falhar, o mecanismo de implantação reimplantará uma substituição para atingir o estado desejado
O uso da configuração declarativa permite que a infraestrutura seja representada como código que pode ser verificado e versionado junto com o código do aplicativo. Ele fornece controle de alterações aprimorado e melhor suporte para implantação contínua usando um pipeline de compilação e implantação.
Que cenários são ideais para contentores e orquestradores?
Os cenários a seguir são ideais para o uso de contêineres e orquestradores.
Aplicativos que exigem alto tempo de atividade e escalabilidade
Aplicativos individuais com altos requisitos de tempo de atividade e escalabilidade são candidatos ideais para arquiteturas nativas da nuvem usando microsserviços, contêineres e orquestradores. Eles podem ser desenvolvidos em contêineres, testados em ambientes versionados e implantados em produção sem tempo de inatividade. O uso de clusters Kubernetes garante que esses aplicativos também possam ser dimensionados sob demanda e se recuperar automaticamente de falhas de nó.
Grande número de aplicações
As organizações que implantam e mantêm um grande número de aplicativos se beneficiam de contêineres e orquestradores. O esforço inicial de configuração de ambientes em contêineres e clusters Kubernetes é principalmente um custo fixo. Implantar, manter e atualizar aplicativos individuais tem um custo que varia de acordo com o número de aplicativos. Além de alguns aplicativos, a complexidade de manter aplicativos personalizados manualmente excede o custo de implementação de uma solução usando contêineres e orquestradores.
Quando você deve evitar o uso de contêineres e orquestradores?
Se você não conseguir criar seu aplicativo seguindo os princípios do aplicativo de doze fatores, considere evitar contêineres e orquestradores. Nesses casos, considere uma plataforma de hospedagem baseada em VM ou, possivelmente, algum sistema híbrido. Com ele, você sempre pode dividir certas partes da funcionalidade em contêineres separados ou até mesmo funções sem servidor.
Recursos para o desenvolvimento
Esta seção mostra uma pequena lista de recursos de desenvolvimento que podem ajudá-lo a começar a usar contêineres e orquestradores para seu próximo aplicativo. Se você estiver procurando orientação sobre como projetar seu aplicativo de arquitetura de microsserviços nativo da nuvem, leia o complemento deste livro, .NET Microservices: Architecture for Containerized .NET Applications.
Desenvolvimento Local do Kubernetes
As implantações do Kubernetes oferecem grande valor em ambientes de produção, mas também podem ser executadas localmente em sua máquina de desenvolvimento. Embora você possa trabalhar em microsserviços individuais de forma independente, pode haver momentos em que você precisará executar todo o sistema localmente - assim como ele será executado quando implantado na produção. Existem várias ferramentas que podem ajudar: Minikube e Docker Desktop. O Visual Studio também fornece ferramentas para o desenvolvimento do Docker.
Minikube
Qual é Minikube? O projeto Minikube diz "Minikube implementa um cluster Kubernetes local no macOS, Linux e Windows." Seus principais objetivos são "ser a melhor ferramenta para o desenvolvimento de aplicativos Kubernetes locais e suportar todos os recursos do Kubernetes que se encaixam". A instalação do Minikube é separada do Docker, mas o Minikube suporta hipervisores diferentes dos suportados pelo Docker Desktop. Os seguintes recursos do Kubernetes são atualmente suportados pelo Minikube:
- DNS
- NodePorts
- ConfigMaps e segredos
- Dashboards
- Tempos de execução do contêiner: Docker, rkt, CRI-O e containerd
- Habilitando a CNI (Container Network Interface, interface de rede de contêiner)
- Entrada
Depois de instalar o Minikube, você pode começar rapidamente a usá-lo executando o minikube start
comando, que baixa uma imagem e inicia o cluster Kubernetes local. Depois que o cluster é iniciado, você interage com ele usando os comandos padrão do Kubernetes kubectl
.
Área de trabalho do Docker
Você também pode trabalhar com o Kubernetes diretamente do Docker Desktop no Windows. É a sua única opção se estiver a utilizar Contentores do Windows e é também uma excelente opção para contentores que não sejam do Windows. A Figura 3-4 mostra como habilitar o suporte local ao Kubernetes ao executar o Docker Desktop.
Figura 3-4. Configuração do Kubernetes no Docker Desktop.
O Docker Desktop é a ferramenta mais popular para configurar e executar aplicativos em contêineres localmente. Ao trabalhar com o Docker Desktop, você pode desenvolver localmente exatamente o mesmo conjunto de imagens de contêiner do Docker que implantará na produção. O Docker Desktop foi projetado para "criar, testar e enviar" aplicativos em contêineres localmente. Ele suporta contêineres Linux e Windows. Depois de enviar suas imagens por push para um registro de imagem, como o Azure Container Registry ou o Docker Hub, o AKS pode extrair e implantá-las na produção.
Ferramentas do Docker do Visual Studio
O Visual Studio oferece suporte ao desenvolvimento do Docker para aplicativos baseados na Web. Ao criar um novo aplicativo ASP.NET Core, você tem uma opção para configurá-lo com suporte ao Docker, como mostra a Figura 3-5.
Figura 3-5. Visual Studio Habilitar Suporte ao Docker
Quando essa opção é selecionada, o projeto é criado com um Dockerfile
em sua raiz, que pode ser usado para criar e hospedar o aplicativo em um contêiner do Docker. Um exemplo de Dockerfile é mostrado na Figura 3-6.
FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
COPY ["eShopWeb/eShopWeb.csproj", "eShopWeb/"]
RUN dotnet restore "eShopWeb/eShopWeb.csproj"
COPY . .
WORKDIR "/src/eShopWeb"
RUN dotnet build "eShopWeb.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "eShopWeb.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "eShopWeb.dll"]
Figura 3-6. Dockerfile gerado pelo Visual Studio
Depois que o suporte for adicionado, você poderá executar seu aplicativo em um contêiner do Docker no Visual Studio. A Figura 3-7 mostra as diferentes opções de execução disponíveis a partir de um novo projeto ASP.NET Core criado com suporte ao Docker adicionado.
Figura 3-7. Opções de execução do Docker do Visual Studio
Além disso, a qualquer momento, você pode adicionar suporte ao Docker a um aplicativo ASP.NET Core existente. No Gerenciador de Soluções do Visual Studio, clique com o botão direito do mouse no projeto e selecione Add>Docker Support, conforme mostrado na Figura 3-8.
Figura 3-8. Adicionando suporte ao Docker ao Visual Studio
Ferramentas do Docker de código do Visual Studio
Há muitas extensões disponíveis para o Visual Studio Code que oferecem suporte ao desenvolvimento do Docker.
A Microsoft fornece a extensão Docker for Visual Studio Code. Essa extensão simplifica o processo de adição de suporte a contêineres para aplicativos. Ele cria scaffolds de arquivos necessários, cria imagens do Docker e permite que você depure seu aplicativo dentro de um contêiner. A extensão apresenta um explorador visual que facilita a execução de ações em contêineres e imagens, como iniciar, parar, inspecionar, remover e muito mais. A extensão também suporta Docker Compose permitindo que você gerencie vários contêineres em execução como uma única unidade.