API de micro e-commerce para gestão de pedidos e itens, construída em Python + FastAPI, publicada na Magalu Cloud com deploy automatizado via GitHub Actions, banco PostgreSQL gerenciado (DBaaS) e stack de observabilidade Prometheus + Grafana.
Projeto desenvolvido ao longo das Competências 3 a 6 do curso Move Tech — Magalu × Prósper Digital Skills (Formação em Cloud Computing).
- Visão geral
- Arquitetura
- Endpoints
- Stack técnica
- Estrutura do repositório
- Como rodar localmente
- Infraestrutura na nuvem
- Pipeline de CI/CD
- Banco de dados
- Observabilidade
- Resiliência
- Troubleshooting — problemas reais e soluções
- Decisões de arquitetura (ADRs)
- Próximos passos
A aplicação evoluiu em quatro etapas:
| Competência | Entrega |
|---|---|
| 3 — DevOps e Deploy | Containerização, Container Registry, cluster K3s e pipeline de deploy |
| 4 — Dados e Persistência | Banco PostgreSQL gerenciado (DBaaS) conectado via Kubernetes Secret |
| 5 — Observabilidade e Resiliência | Prometheus + Grafana, ServiceMonitor, probes de liveness/readiness |
| 6 — Arquitetura de Soluções | Diagrama C2, ADRs e análise de trade-offs (docs/architecture.md, docs/adr/) |
flowchart TB
subgraph GH["GitHub"]
Repo[Repositório] -->|push / workflow_dispatch| Actions[GitHub Actions]
end
subgraph MGC["Magalu Cloud — br-ne1"]
Actions -->|docker push| Registry[(Container Registry)]
Actions -->|kubectl apply| K3s
subgraph VM["VM K3s — single node"]
K3s[K3s API Server]
Pod1[Pod cloud-application #1]
Pod2[Pod cloud-application #2]
SVC[Service LoadBalancer :80]
SVC --> Pod1
SVC --> Pod2
Prom[Prometheus]
Graf[Grafana]
Prom -->|scrape /metrics via ServiceMonitor| SVC
Graf -->|query PromQL| Prom
end
Pod1 -->|DATABASE_URL| PG[(PostgreSQL DBaaS)]
Pod2 -->|DATABASE_URL| PG
end
User((Usuário)) -->|HTTP :80| SVC
User -->|HTTP :NodePort| Graf
| Componente | Serviço MGC | Função |
|---|---|---|
| API | K3s (VM single node) — 2 réplicas | Processa as requisições HTTP |
| Banco de dados | DBaaS PostgreSQL | Persiste pedidos e itens |
| Imagens | Container Registry | Armazena versões da aplicação |
| Tráfego externo | Klipper ServiceLB (IP da VM, porta 80) | Distribui entre as réplicas e expõe acesso externo |
| Monitoramento | Prometheus + Grafana (Helm) | Coleta e visualiza métricas |
| CI/CD | GitHub Actions | Automatiza testes, build e deploy |
| Requisito | Como medir | Alvo |
|---|---|---|
| Disponibilidade | Erros 5xx e uptime das probes no Grafana | 99,5% mensal |
| Latência | histogram_quantile(0.95, ...) do /metrics |
P95 < 500 ms |
| Escalabilidade | Teste de carga (k6) + rate(http_requests_total) |
300 req/s sem degradar |
Detalhamento completo em docs/architecture.md.
| Método | Rota | Descrição |
|---|---|---|
GET |
/health |
Verifica se a API e o banco estão saudáveis — {"status": "ok", "database": "ok"} |
GET |
/stats |
Estatísticas em tempo real (contagem de pedidos e itens) |
GET |
/metrics |
Métricas no formato Prometheus |
GET |
/docs |
Documentação interativa (Scalar) |
POST |
/orders |
Cria um novo pedido |
GET |
/orders |
Lista todos os pedidos |
GET |
/orders/{id} |
Retorna um pedido com seus itens |
DELETE |
/orders/{id} |
Cancela um pedido |
POST |
/orders/{id}/items |
Adiciona um item ao pedido |
GET |
/orders/{id}/items |
Lista os itens de um pedido |
- Linguagem/Framework: Python 3.11 + FastAPI
- ORM: SQLAlchemy
- Banco: PostgreSQL (produção) / SQLite (local, fallback sem
DATABASE_URL) - Empacotamento: Docker
- Orquestração: Kubernetes (K3s)
- CI/CD: GitHub Actions
- Observabilidade: Prometheus, Grafana, kube-prometheus-stack (Helm)
- Cloud: Magalu Cloud (Virtual Machine, Container Registry, Kubernetes/K3s, DBaaS)
.
├── app/ # Código-fonte da aplicação (FastAPI)
├── docs/
│ ├── data-model.md # Modelagem de dados (Competência 4)
│ ├── architecture.md # Diagrama C2, componentes e trade-offs (Competência 6)
│ └── adr/
│ ├── 001-kubernetes-deploy.md
│ └── 002-dbaas-postgresql.md
├── k8s/
│ ├── app.yaml # Deployment + Service
│ └── servicemonitor.yaml # ServiceMonitor para o Prometheus
├── tests/ # Testes automatizados
├── .github/workflows/deploy.yml # Pipeline de CI/CD
├── Dockerfile
├── docker-compose.yml
└── pyproject.toml
Pré-requisito: Docker Desktop ou Docker Engine.
docker compose up --buildAcesse a documentação interativa em http://localhost:8000/docs.
Sem DATABASE_URL configurada, a aplicação usa SQLite local — os dados são perdidos ao reiniciar o container (comportamento esperado fora de produção).
| Item | Valor |
|---|---|
| Região | br-ne1 (Nordeste) |
| Cluster | K3s single-node, provisionado via k3s-mgc |
| Tipo de VM | BV2-2-10 → redimensionada para maior capacidade de RAM após saturação de memória (ver Troubleshooting) |
| Security Group | sg-k3s-cluster — portas liberadas: 22 (SSH), 80 (HTTP), 6443 (API Kubernetes), 8000 (API direta), 3000/9090 (observabilidade), NodePort do Grafana |
| Container Registry | container-registry.br-ne1.magalu.cloud/<nome-do-registry> |
Provisionamento automatizado com:
~/k3s.sh kubernetes cluster create --name cloud-application-cluster
~/k3s.sh kubernetes cluster configure-registry --cluster-id <ID>
~/k3s.sh kubernetes cluster kubeconfig --cluster-id <ID> --raw > kubeconfig.yamlDefinido em .github/workflows/deploy.yml, disparado manualmente (workflow_dispatch):
- test — instala dependências e roda os testes automatizados (
pytest) - build-and-deploy (só se os testes passarem):
- Login no Container Registry da MGC
- Build e push da imagem Docker
- Configuração do
kubectla partir do SecretMGC_KUBECONFIG - Criação/atualização do Secret
db-secreta partir deDATABASE_URL - Aplicação dos manifests (
envsubstinjetaMGC_REGISTRY_NAME) e espera do rollout
| Secret | Descrição |
|---|---|
MGC_REGISTRY_USER |
Usuário do Container Registry |
MGC_REGISTRY_PASSWORD |
Senha do Container Registry |
MGC_REGISTRY_NAME |
Nome do registry na MGC |
MGC_KUBECONFIG |
Conteúdo do kubeconfig.yaml do cluster |
DATABASE_URL |
String de conexão PostgreSQL (postgresql://usuario:senha@host:5432/orders) |
- PostgreSQL gerenciado (DBaaS) na Magalu Cloud.
- O banco
ordersé criado manualmente no console do DBaaS (não é criado automaticamente); as tabelas (orders,items) são criadas automaticamente pela aplicação viaBase.metadata.create_all(). - A conexão é injetada no pod via Kubernetes Secret (
db-secret), nunca exposta em código ou manifests. - Modelagem completa em
docs/data-model.md.
Stack kube-prometheus-stack instalada via Helm no namespace monitoring:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install monitoring prometheus-community/kube-prometheus-stack \
--namespace monitoring --create-namespace \
--set grafana.service.type=LoadBalancer \
--set prometheus.service.type=LoadBalancer \
--set grafana.adminPassword=admin- Grafana: acessível pelo IP da VM na porta exposta pelo Service (NodePort, já que a porta 80 é usada pela aplicação — ver Troubleshooting)
- Prometheus:
http://<IP-DA-VM>:9090→ Status → Targets confirmacloud-applicationcomoUP - ServiceMonitor:
k8s/servicemonitor.yamlconecta o Prometheus ao endpoint/metricsda aplicação, usando o label obrigatóriorelease: monitoring
Logs estruturados em JSON, filtráveis via:
kubectl logs -l app=cloud-application -f
kubectl logs -l app=cloud-application | grep order_createdO k8s/app.yaml define liveness e readiness probes sobre /health:
- Liveness: se
/healthfalhar repetidamente, o Kubernetes reinicia o pod - Readiness: se
/healthfalhar, o pod para de receber tráfego até se recuperar
Testado com:
kubectl delete pod <nome-do-pod> # Kubernetes recria automaticamente
kubectl scale deployment cloud-application --replicas=0 # derruba tudo
kubectl scale deployment cloud-application --replicas=2 # restauraRegistro dos principais problemas enfrentados durante o deploy, para referência futura.
Nomes de registry são globalmente únicos na Magalu Cloud (como buckets S3). Nomes genéricos (cloud-application-registry) colidem com outras contas. Solução: usar um nome mais específico (ex: prefixado com usuário/projeto).
Ocorre quando a VM é recriada — o fingerprint SSH muda. Solução:
ssh-keygen -f '~/.ssh/known_hosts' -R '<IP>'O repositório do script evolui rapidamente; as opções de máquina disponíveis via prompt interativo podem não incluir exatamente o tipo citado na atividade (BV2-2-40). Solução: escolher a opção mais próxima em vCPU/RAM disponível no momento (ex: BV2-2-10).
A região é definida no perfil da mgc CLI, não no script de criação do cluster. Solução:
mgc config get region
mgc config set region br-ne1Recursos de regiões diferentes são isolados — é necessário recriar cluster/registry na região correta caso já tenham sido criados na região errada.
O K3s não tem credenciais para autenticar no Container Registry privado. Solução: criar o secret de autenticação e associá-lo ao service account:
kubectl create secret docker-registry mgc-registry-secret \
--docker-server=container-registry.br-ne1.magalu.cloud \
--docker-username=<usuario> --docker-password=<senha>
kubectl patch serviceaccount default \
-p '{"imagePullSecrets": [{"name": "mgc-registry-secret"}]}'Porta bloqueada no Security Group — não é erro de aplicação. O script k3s.sh só libera 22, 6443 e 8000 por padrão. Solução: abrir a porta manualmente:
mgc network security-groups rules create \
--security-group-id=<SG-ID> --direction=ingress --ethertype=IPv4 \
--protocol=tcp --port-range-min=80 --port-range-max=80 \
--remote-ip-prefix=0.0.0.0/0O PostgreSQL não cria o banco automaticamente — só as tabelas são criadas pela aplicação (create_all()). Solução: criar o banco manualmente no console do DBaaS antes do primeiro deploy com DATABASE_URL configurada.
Dois serviços LoadBalancer (aplicação e Grafana) competindo pela porta 80 no mesmo node — o ServiceLB do K3s não consegue alocar a porta duas vezes. Diagnóstico:
kubectl describe pod -n kube-system svclb-monitoring-grafana-...
# Events: "didn't have free ports for the requested pod ports"Solução aplicada: acessar o Grafana pelo NodePort alocado automaticamente (ex: http://<IP-DA-VM>:<NodePort>) em vez de esperar por um IP externo dedicado na porta 80.
Sessão não autenticada. Solução: acessar /login diretamente e usar as credenciais configuradas na instalação do Helm (admin / senha definida em grafana.adminPassword), ou recuperar via:
kubectl get secret -n monitoring monitoring-grafana \
-o jsonpath="{.data.admin-password}" | base64 -dVM saturada de memória (RAM 100% utilizada, sem swap) rodando K3s + aplicação + stack completo de monitoramento (Prometheus, Grafana, Alertmanager) em uma VM de baixa capacidade. Diagnóstico:
free -h # available próximo de 0
systemctl status k3s # uso de memória do processo k3s-server elevadoSolução: aumento da capacidade de RAM da VM via console da Magalu Cloud.
O ServiceMonitor seleciona um Service por label (app: cloud-application) e por nome de porta (http) — não basta o Deployment ter esses atributos. Causa raiz: o Service no k8s/app.yaml não tinha labels nem name: http na porta. Solução:
apiVersion: v1
kind: Service
metadata:
name: cloud-application
labels:
app: cloud-application # necessário para o selector do ServiceMonitor
spec:
type: LoadBalancer
selector:
app: cloud-application
ports:
- name: http # necessário: ServiceMonitor referencia a porta pelo nome
port: 80
targetPort: 8000A sintaxe da CLI é posicional, não key=value:
# Errado
mgc config set region=br-ne1
# Correto
mgc config set region br-ne1Sintoma de que o arquivo de kubeconfig não existe no caminho esperado ou não foi exportado na sessão atual. Solução:
~/k3s.sh kubernetes cluster kubeconfig --cluster-id <ID> --raw > ~/kubeconfig.yaml
export KUBECONFIG=~/kubeconfig.yamlA chave usada pelo script para provisionar o cluster pode não ser a chave SSH padrão (id_ed25519) da máquina cliente. Solução: identificar a chave certa (mgc profile ssh-keys list + comparar com ~/.ssh/*.pub) e especificá-la explicitamente:
ssh -i ~/.ssh/<chave-correta> ubuntu@<IP-da-VM>Nunca usar
sudo ssh— as chaves ficam no$HOMEdo usuário, não do root.
| ADR | Decisão |
|---|---|
| 001 — Kubernetes para deploy | K3s em VM única, em vez de MKS gerenciado — custo menor e provisionamento mais rápido |
| 002 — DBaaS PostgreSQL | Banco gerenciado, em vez de PostgreSQL em pod — backup e HA sem custo operacional |
Tabela completa de trade-offs em docs/architecture.md.
| Melhoria | Por quê |
|---|---|
| HTTPS / TLS | Toda API em produção deve ser acessada por HTTPS |
| Autoscaler (HPA) | Escala automaticamente conforme a carga |
Versionamento de API (/v1/orders) |
Evolução sem quebrar clientes |
| Rate limiting | Protege o banco de sobrecargas |
| Cache (Redis) | Reduz consultas repetidas ao banco |
| Migrações de schema (Alembic) | Controle de versão do banco |
| Testes de carga (k6) | Valida comportamento sob alto tráfego |
| Migrar para MKS | Alta disponibilidade real — manifests já são compatíveis |
Projeto desenvolvido como parte do curso Move Tech — Magalu × Prósper Digital Skills. Inspirado no tutorial Construindo APIs robustas utilizando Python, do LuizaLabs.