Fundamentos da computação em nuvem

Contêineres e Microsserviços

O que um contêiner empacota de verdade, por que dividir um monólito em microsserviços muda como um time entrega e escala, e o que um orquestrador como o Kubernetes adiciona quando você tem mais contêineres do que consegue administrar na mão.

Intermediário 19 minutos 5 Objetivos de aprendizado
  1. Explicar o que é um contêiner e por que ele inicia mais rápido que uma máquina virtual
  2. Descrever como uma imagem de contêiner mantém o ambiente de execução idêntico do notebook de um desenvolvedor até a produção
  3. Diferenciar uma aplicação monolítica de uma arquitetura de microsserviços pela forma como cada uma é implantada e escalada
  4. Identificar o que a orquestração de contêineres adiciona sobre rodar contêineres individuais na mão
  5. Reconhecer as trocas operacionais que os microsserviços trazem em troca de escalonamento e deploy independentes

A última lição respondeu quem roda o código. Esta pergunta como ele é construído

A última lição deixou a computação resolvida: uma função serverless, uma máquina virtual, ou algo no meio do caminho, roda o seu código, e o provedor cuida de mais ou menos trabalho ao redor dependendo do que você escolhe. Nada disso diz como a própria aplicação está montada. Uma aplicação que começou como uma base de código grande só, um deploy só, um time só, esbarra em um problema específico conforme cresce: uma correção pequena no fluxo de checkout significa testar e fazer o deploy da aplicação inteira de novo, catálogo, contas e tudo mais, e um pico de tráfego em uma parte só da aplicação te obriga a escalar tudo junto. Contêineres e microsserviços são 2 ideias separadas que, juntas, resolvem exatamente esse problema.

O que um contêiner empacota de verdade

Um contêiner é um processo isolado empacotado com tudo que precisa para rodar: o seu código, o runtime dele, e as dependências, tudo em uma unidade autocontida. Empacote uma aplicação Node.js em uma imagem de contêiner, e essa imagem roda idêntica não importa se ela começou no notebook de um desenvolvedor, em um servidor de teste, ou em um cluster de produção, porque a imagem carrega o próprio runtime em vez de depender do que estiver instalado no host.

FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
CMD ["node", "server.js"]
docker build -t checkout-service:1.0 .
docker run -p 3000:3000 checkout-service:1.0

Esse Dockerfile descreve a imagem; docker build empacota; docker run inicia um contêiner a partir dela. A lição de computação já posicionou os contêineres no espectro de controle versus conveniência, iniciando em segundos em vez dos minutos de uma máquina virtual, porque um contêiner compartilha o kernel do sistema operacional do host em vez de inicializar um sistema operacional convidado completo próprio.

Do monólito aos microsserviços

Um monólito é uma base de código só, um deploy só, e geralmente um banco de dados compartilhado só, não importa quantas funcionalidades distintas ele contenha. Uma arquitetura de microsserviços divide essa mesma aplicação em um conjunto de serviços pequenos e implantáveis de forma independente, cada um organizado em torno de uma capacidade de negócio (catálogo, carrinho, checkout, contas de usuário) e se comunicando por APIs leves em vez de chamadas de função dentro do mesmo processo. Cada serviço costuma ser dono do próprio banco de dados em vez de compartilhar um, e é isso que o torna implantável e escalável por conta própria.

Volte ao cenário da promoção relâmpago. Em um monólito, triplicar o tráfego de checkout obriga você a escalar o deploy inteiro, catálogo e contas incluídos, porque existe uma única unidade implantável para escalar. Dividido em microsserviços, o serviço de checkout escala sozinho, e catálogo e contas ficam exatamente como estavam, porque cada um agora é uma peça separada e implantável por conta própria.

A troca que você aceita: independência por coordenação

Nada disso é de graça. Dividir um monólito em microsserviços troca a simplicidade de uma base de código só por um conjunto de problemas novos que só aparecem quando os serviços estão separados. Uma chamada do checkout para o serviço de usuário agora cruza uma rede em vez de chamar uma função no mesmo processo, o que significa que ela pode dar timeout ou falhar de formas que uma chamada dentro do processo nunca falharia. Dados descentralizados, cada serviço com o próprio banco de dados, significam abrir mão de uma transação única compartilhada em toda a requisição em troca de consistência eventual entre serviços. E uma requisição de cliente que passa por 5 serviços é mais difícil de depurar do que uma que passa por um só, porque não existe mais um stack trace único para ler. Mais serviços não é automaticamente melhor, é uma troca deliberada de simplicidade por independência, uma troca que só compensa quando o time já tem as ferramentas operacionais para administrá-la, exatamente o que a próxima lição cobre.

Orquestração: administrar mais contêineres do que uma pessoa consegue rastrear

Rode um contêiner só na mão e docker run já basta. Rode 3 réplicas de um serviço de checkout por redundância, mais um serviço de catálogo, mais um serviço de carrinho, cada um com as próprias réplicas, e vigiar tudo isso na mão para de funcionar rápido. Um orquestrador de contêineres como o Kubernetes pega uma descrição declarativa do que você quer e trabalha continuamente para fazer a realidade bater com ela.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: checkout-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: checkout-service
  template:
    metadata:
      labels:
        app: checkout-service
    spec:
      containers:
        - name: checkout
          image: checkout-service:2.1
          ports:
            - containerPort: 8080

Essa linha replicas: 3 é uma declaração, não um comando de uma vez só. Se uma das 3 réplicas cair, o Kubernetes percebe o desencontro entre o estado declarado e a realidade e sobe uma substituta automaticamente, uma capacidade chamada autorrecuperação. O Kubernetes também cuida de service discovery e balanceamento de carga entre essas réplicas, e distribui atualizações aos poucos em vez de todas de uma vez, então um time para de cuidar de contêineres na mão e passa a descrever o estado que quer.

Pistas de exame: identificando o encaixe em um cenário

O cenário diz...Aponta para
"Empacotar uma vez, rodar idêntico em todo lugar"Contêiner
"Times independentes fazem deploy de forma independente, cada um dono dos próprios dados"Microsserviços
"Reiniciar instâncias que falharam automaticamente, distribuir atualizações aos poucos"Orquestração de contêineres (Kubernetes)
"Uma base de código só, um deploy só, um banco de dados só"Monólito

Onde isso deixa você

Dividir uma aplicação em microsserviços e empacotar cada peça como contêiner são 2 decisões separadas que acabam reforçando uma à outra: a capacidade de deploy independente que os microsserviços prometem só se sustenta se cada peça conseguir mesmo ser entregue e escalada por conta própria, e um contêiner é exatamente o que torna isso prático. Nada disso, os builds, os deploys, a quantidade sempre crescente de peças em movimento, fica gerenciável na mão por muito tempo. A próxima lição cobre as práticas e as ferramentas, DevOps e infraestrutura como código, que mantêm isso gerenciável.