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.
- Explicar o que é um contêiner e por que ele inicia mais rápido que uma máquina virtual
- 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
- Diferenciar uma aplicação monolítica de uma arquitetura de microsserviços pela forma como cada uma é implantada e escalada
- Identificar o que a orquestração de contêineres adiciona sobre rodar contêineres individuais na mão
- 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.
