diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..483e196 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,50 @@ +name: Deploy +on: + workflow_dispatch: +env: + REGISTRY: container-registry.br-se1.magalu.cloud + IMAGE_NAME: cloud-application +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Instalar dependências + run: | + pip install poetry + poetry install --no-root + - name: Executar testes + run: poetry run pytest + build-and-deploy: + needs: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Login no Container Registry + run: | + docker login https://${{ env.REGISTRY }} \ + -u ${{ secrets.MGC_REGISTRY_USER }} \ + -p ${{ secrets.MGC_REGISTRY_PASSWORD }} + - name: Build e push da imagem + run: | + IMAGE=${{ env.REGISTRY }}/${{ secrets.MGC_REGISTRY_NAME }}/${{ env.IMAGE_NAME }}:latest + docker build -t $IMAGE . + docker push $IMAGE + - name: Configurar kubectl + run: | + echo "${{ secrets.MGC_KUBECONFIG }}" > kubeconfig.yaml + - name: Criar Secret do banco + run: | + export KUBECONFIG=kubeconfig.yaml + kubectl create secret generic db-secret \ + --from-literal=url=${{ secrets.DATABASE_URL }} \ + --dry-run=client -o yaml | kubectl apply -f - + - name: Deploy no Kubernetes + run: | + export KUBECONFIG=kubeconfig.yaml + export MGC_REGISTRY_NAME=${{ secrets.MGC_REGISTRY_NAME }} + envsubst < k8s/app.yaml | kubectl apply -f - + kubectl rollout status deployment/cloud-application diff --git a/docs/data-model.md b/docs/data-model.md new file mode 100644 index 0000000..f8fdb51 --- /dev/null +++ b/docs/data-model.md @@ -0,0 +1,26 @@ +# Modelagem de Dados + +## Entidades + +### Pedido (orders) + +| Coluna | Tipo | Descrição | +|--------------|--------------------------------|----------------------------------------------------------------------------| +| id | String (UUID) | Chave primária. Gerado automaticamente (`uuid4`) na criação do pedido | +| customer | String | Nome do cliente que fez o pedido. Obrigatório | +| status | String | Status do pedido. Padrão: `"open"` | +| created_at | DateTime (com timezone, UTC) | Data/hora de criação do pedido. Preenchido automaticamente (`now(UTC)`) | + +### Item (items) + +| Coluna | Tipo | Descrição | +|--------------|-----------|-----------------------------------------------------------------------------| +| id | String (UUID) | Chave primária. Gerado automaticamente (`uuid4`) na criação do item | +| order_id | String | Chave estrangeira para `orders.id`. Obrigatório | +| sku | String | Código (SKU) do produto. Obrigatório | +| description | String | Descrição do item. Obrigatório | +| quantity | Integer | Quantidade do item no pedido. Obrigatório | + +## Relacionamento + +Relacionamento **1:N** entre `orders` e `items`: um pedido (`Order`) pode ter vários itens (`Item`), e cada item pertence a exatamente um pedido, via a chave estrangeira `items.order_id → orders.id`. diff --git a/docs/docs/adr/001-kubernetes-deploy.md b/docs/docs/adr/001-kubernetes-deploy.md new file mode 100644 index 0000000..5f2a90d --- /dev/null +++ b/docs/docs/adr/001-kubernetes-deploy.md @@ -0,0 +1,35 @@ +# ADR 001 — Usar K3s para deploy da aplicação + +**Status:** Aceito +**Data:** 2026-08-04 + +## Contexto + +A aplicação precisa ser implantada na Magalu Cloud de forma acessível publicamente, resiliente a falhas e com capacidade de escalar. + +## Alternativas consideradas + +- **K3s em VM** — Kubernetes leve; cobra só a VM; provisionamento < 2 min; sem HA nativa. +- **MKS (Kubernetes Gerenciado)** — control plane e HA gerenciados; custo maior; provisionamento 5-10 min. +- **VM com Docker Compose** — mais simples de subir; sem orquestração, self-healing nem escala declarativa. + +## Decisão + +Usar K3s em uma VM BV2-2-40 (Ubuntu 24.04) com Klipper ServiceLB para expor a aplicação na porta 80 do IP público da VM. + +O script `k3s-mgc` automatiza todo o provisionamento. Critério: menor custo e provisionamento mais rápido, com manifests idênticos a qualquer Kubernetes. + +## Consequências + +**Positivas:** +- Custo menor que o MKS (cobra apenas pela VM e não pelo control plane) +- Provisionamento em menos de 2 minutos +- Manifests YAML idênticos a qualquer Kubernetes padrão (sem lock-in) +- Restart automático em caso de falha (liveness probe) +- Escalabilidade horizontal simples (basta aumentar o número de réplicas) + +**Negativas:** +- Single point of failure: sem alta disponibilidade nativa (tudo em uma VM) +- Armazenamento efêmero: volumes locais desaparecem se a VM for recriada +- Sem auto-scaling de nós: capacidade fixa (2 vCPU, 2 GB) +- IP público muda se a VM for substituída diff --git a/docs/docs/adr/002-dbaas-postgresql.md b/docs/docs/adr/002-dbaas-postgresql.md new file mode 100644 index 0000000..4dde7db --- /dev/null +++ b/docs/docs/adr/002-dbaas-postgresql.md @@ -0,0 +1,30 @@ +# ADR 002 — Usar DBaaS PostgreSQL da Magalu Cloud + +**Status:** Aceito +**Data:** 2026-08-04 + +## Contexto + +A aplicação precisa de persistência de dados. O banco precisa sobreviver a reinicializações de containers e estar disponível para múltiplas réplicas da API simultaneamente. + +## Alternativas consideradas + +- **DBaaS PostgreSQL gerenciado (externo)** — backup, patch e HA pelo provedor; custo maior; menos controle fino. +- **PostgreSQL em pod com PVC** — custo baixo, tudo em um lugar; volume, backup e recuperação por nossa conta; o dado morre junto com o cluster. + +## Decisão + +Usar o serviço DBaaS PostgreSQL da Magalu Cloud — banco gerenciado, sem necessidade de operar o servidor de banco de dados. Critério: disponibilidade e custo de operação (estado é caro de operar manualmente). + +## Consequências + +**Positivas:** +- Backup automático gerenciado pelo provedor +- Sem custo operacional de administração do banco +- Conexões simultâneas de múltiplos pods sem conflito +- Alta disponibilidade incluída no serviço + +**Negativas:** +- Custo por hora de uso, mesmo com pouco tráfego +- Menor controle sobre configurações avançadas do PostgreSQL +- Dependência do provedor para upgrades de versão diff --git a/docs/docs/architecture.md b/docs/docs/architecture.md new file mode 100644 index 0000000..3afb538 --- /dev/null +++ b/docs/docs/architecture.md @@ -0,0 +1,97 @@ +# Arquitetura da Solução + +## Diagrama de arquitetura (C4 — Nível de Container) + +```mermaid +graph LR + User["Usuário / Cliente HTTP"] + + subgraph GH["GitHub"] + GA["GitHub Actions
(CI/CD)"] + end + + subgraph MGC["Magalu Cloud"] + CR[("Container Registry")] + + subgraph VM["VM BV2-2-40 · K3s"] + LB["Klipper ServiceLB
IP público · porta 80"] + API1["Pod API
réplica 1 · :8000"] + API2["Pod API
réplica 2 · :8000"] + end + + DB[("DBaaS PostgreSQL
IP privado · :5432")] + end + + User -->|"HTTP :80"| LB + LB -->|"HTTP :8000"| API1 + LB -->|"HTTP :8000"| API2 + API1 -->|"SQL/TCP :5432 (rede privada)"| DB + API2 -->|"SQL/TCP :5432 (rede privada)"| DB + GA -->|"docker push (HTTPS)"| CR + GA -->|"kubectl apply (via kubeconfig)"| VM + VM -->|"docker pull (HTTPS)"| CR +``` + +## Componentes da arquitetura + +| 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 fornece acesso externo | +| CI/CD | GitHub Actions | Automatiza testes, build e deploy | + +## Requisitos não-funcionais + +| 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 | +| Custo | VM + DBaaS + IP na calculadora MGC | Teto definido em ADR | + +## Estilo arquitetural + +A solução segue um **monolito em camadas** (apresentação → serviço → dados), implantado como container único com duas réplicas atrás de um LoadBalancer. Não há separação de bounded contexts nem comunicação assíncrona entre serviços — toda a lógica de pedidos e itens vive num único processo FastAPI. + +Estilo-alvo, caso o domínio de notificações cresça: extrair um segundo serviço (ex.: um serviço de notificações desacoplado, comunicando-se via fila ou evento), evoluindo de monolito para um conjunto pequeno de serviços. + +## Trade-offs + +| Aspecto | Decisão tomada | Alternativa não escolhida | Motivo da escolha | +|---|---|---|---| +| Deploy | K3s em VM | MKS (Kubernetes Gerenciado) | Custo menor, provisionamento < 2 min, manifests idênticos | +| Banco | DBaaS gerenciado | PostgreSQL em container | Backup automático, sem administração | +| CI/CD | GitHub Actions | Deploy manual | Consistência e rastreabilidade | +| Réplicas | 2 pods | 1 pod | Disponibilidade mínima sem custo excessivo | +| API | FastAPI (Python) | Node.js, Go, Java | Curva de aprendizado baixa, alta produtividade | + +## Pontos de melhoria + +### Escalabilidade + +A aplicação é stateless, então escala na horizontal — mais réplicas atrás do balanceador. Hoje são 2 réplicas fixas; o próximo passo natural é o HPA (Horizontal Pod Autoscaler), que ajusta esse número automaticamente pela utilização de CPU (ex.: mínimo 2, máximo 6, alvo de 70%). Vale registrar também que mais réplicas não resolvem um gargalo de banco — o PostgreSQL escala na vertical e costuma saturar primeiro. + +### Próximos passos naturais + +| 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` permite evoluir sem quebrar clientes | +| Rate limiting | Evita abuso e protege o banco de sobrecargas | +| Cache (Redis) | Reduz consultas repetidas ao banco | +| Migrações de schema (Alembic) | Controle de versão das mudanças no banco | +| Testes de carga | Valida o comportamento sob alto tráfego | +| Migrar para MKS | Quando precisar de HA real: basta trocar o kubeconfig — os manifests YAML são idênticos | + +### Custo estimado na Magalu Cloud + +| Recurso | Especificação | Observação | +|---|---|---| +| VM K3s | BV2-2-40 (2 vCPU, 2 GB) | Cobrada por hora de uso | +| DBaaS PostgreSQL | Instância pequena | Cobrado por hora de uso | +| Container Registry | Por armazenamento | Baixo para imagens < 500 MB | + +Consulte os preços atualizados em: https://magalu.cloud/precos/ diff --git a/k8s/app.yaml b/k8s/app.yaml new file mode 100644 index 0000000..9001a99 --- /dev/null +++ b/k8s/app.yaml @@ -0,0 +1,54 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cloud-application + labels: + app: cloud-application +spec: + replicas: 2 + selector: + matchLabels: + app: cloud-application + template: + metadata: + labels: + app: cloud-application + spec: + containers: + - name: cloud-application + image: container-registry.br-se1.magalu.cloud/${MGC_REGISTRY_NAME}/cloud-application:latest + ports: + - containerPort: 8000 + env: + - name: DATABASE_URL + valueFrom: + secretKeyRef: + name: db-secret + key: url + livenessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 10 + periodSeconds: 30 + readinessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 5 + periodSeconds: 10 +--- +apiVersion: v1 +kind: Service +metadata: + name: cloud-application + labels: + app: cloud-application +spec: + type: LoadBalancer + selector: + app: cloud-application + ports: + - name: http + port: 80 + targetPort: 8000