Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
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
32 changes: 32 additions & 0 deletions docs/adr/001-kubernetes-deploy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# 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
29 changes: 29 additions & 0 deletions docs/adr/002-dbaas-postgresql.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# 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
139 changes: 139 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
1. Estilo Arquitetural
A solução adota o estilo de Monolito em Camadas (Layered Monolith), estruturado logicamente em Apresentação → Serviço/Regra de Negócio → Dados, empacotado como um container único e implantado com 2 réplicas no Kubernetes.

Arquitetura Atual: Monolito em camadas implantado de forma redundante (2 pods) para resiliência básica e distribuição de carga HTTP.

Estilo-Alvo (Evolução do Domínio): Caso o domínio (ex: processamento de notificações/pedidos) cresça em volume ou complexidade, a estratégia arquitetural prevê a extração gradual de microsserviços orientados a eventos.

## Estilo Arquitetural

A solução adota o estilo **Monolito em Camadas (Layered Monolith)**, estruturado de forma clara em três responsabilidades principais:

* **Apresentação:** Tratamento das requisições e rotas HTTP.
* **Serviço:** Regras de negócio da aplicação.
* **Dados:** Comunicação com o banco de dados.

O sistema é implantado como um **container único** operando com **duas réplicas** no Kubernetes para garantir resiliência básica e distribuição de carga.

> **Evolução da Arquitetura:** Caso o domínio de notificações ou processamento de pedidos cresça em volume ou complexidade, a estratégia arquitetural prevê a extração gradual dessas responsabilidades para um segundo serviço independente.

2. Diagrama de Arquitetura (Mermaid)

Snippet de código
graph TD
subgraph GitHub ["External: GitHub"]
GHA["GitHub Actions (CI/CD)"]
end

subgraph MGC ["Magalu Cloud (MGC)"]
subgraph VM ["VM BV2-2-40 (K3s Single-Node)"]

SLB["Klipper ServiceLB\n(Porta 80)"]

subgraph K8s_Default ["Namespace: default"]
APP1["API Pod 1\n(Cloud Application)"]
APP2["API Pod 2\n(Cloud Application)"]
SVC_APP["Service: cloud-application\n(ClusterIP)"]
SM["ServiceMonitor\n(cloud-application)"]
end

subgraph K8s_Monitoring ["Namespace: monitoring"]
PROM["Prometheus Operator"]
GRAF["Grafana Dashboard"]
end

end

CR["Container Registry (MGC)"]
DB["DBaaS PostgreSQL"]
end

%% Conexões de Tráfego e CI/CD
User["Usuário / Cliente HTTP"] -->|HTTP / TCP 80| SLB
SLB -->|HTTP / TCP internal| SVC_APP
SVC_APP -->|HTTP / Load Balance| APP1
SVC_APP -->|HTTP / Load Balance| APP2

%% Persistência e Imagens
APP1 -->|SQL / TCP 5432| DB
APP2 -->|SQL / TCP 5432| DB

GHA -->|Push Docker Image / HTTPS 443| CR
GHA -->|kubectl apply / HTTPS 6443| VM
VM -->|Pull Image / HTTPS 443| CR

%% Monitoramento e Coleta
SM -.->|Labels Matching| SVC_APP
PROM -->|HTTP Scraping /metrics (30s)| APP1
PROM -->|HTTP Scraping /metrics (30s)| APP2
GRAF -->|PromQL / HTTP 9090| PROM

3. Componentes da Arquitetura

API K3s (VM single node) K3s (VM single node)
Banco de Dados DBaaS PostgreSQL Serviço gerenciado que persiste com segurança os dados de pedidos e itens.
Imagens Container Registry (MGC) Armazena e versiona as imagens Docker da aplicação.
Tráfego Externo Klipper ServiceLB Escuta no IP público da VM (porta 80) e distribui as requisições entre os Pods da API.
CI/CD GitHub Actions Esteira automatizada responsável pela validação, build da imagem e deploy no cluster.
Observabilidade Prometheus & Grafana Monitora a saúde do cluster e extrai métricas de negócio/infra via ServiceMonitor.

## 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 |

4. Requisitos Não-Funcionais (NFRs)

Disponibilidade Probes de Liveness/Readiness no Kubernetes + Proporção de respostas sem erro 5xx no Grafana (sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))) 99,5% mensal
Latência Cálculo de percentil no Prometheus via métricas /metrics (histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m]) by (le)))) P95 < 500 ms
Escalabilidade Teste de carga com k6 avaliando a vazão de requisições por segundo (rate(http_requests_total[1m])) 300 req/s sem degradação do P95 ou erros
Custo Custo mensal somado da VM BV2-2-40, instância DBaaS PostgreSQL e IP público conforme calculadora MGC Teto definido em ADR de Infraestrutura

## 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 |

## Tabela de Trade-offs Arquiteturais

| 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 |

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 |
56 changes: 56 additions & 0 deletions docs/data-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
Markdown
# Modelagem de Dados

## Entidades

### Pedido (`orders`)
Representa a entidade principal da compra efetuada por um cliente.

| Coluna | Tipo | Chave | Nulo? | Padrão / Comportamento | Descrição |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `id` | `VARCHAR` | PK | Não | UUID v4 (`str(uuid4())`) | Identificador único do pedido |
| `customer` | `VARCHAR` | - | Não | - | Nome/Identificação do cliente |
| `status` | `VARCHAR` | - | Sim | `"open"` | Status do pedido (ex: `open`, `closed`) |
| `created_at` | `TIMESTAMPTZ` | - | Sim | `datetime.now(timezone.utc)` | Data e hora de criação do registro (UTC) |

---

### Item (`items`)
Representa os itens que compõem um pedido específico.

| Coluna | Tipo | Chave | Nulo? | Padrão / Comportamento | Descrição |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `id` | `VARCHAR` | PK | Não | UUID v4 (`str(uuid4())`) | Identificador único do item |
| `order_id` | `VARCHAR` | FK | Não | Referência a `orders.id` | ID do pedido associado |
| `sku` | `VARCHAR` | - | Não | - | Código de identificação do produto (Stock Keeping Unit) |
| `description` | `VARCHAR` | - | Não | - | Descrição detalhada do produto |
| `quantity` | `INTEGER` | - | Não | - | Quantidade solicitada do produto |

---

## Relacionamento

* **Tipo:** **1:N (Um para Muitos)** entre `orders` e `items`.
* **Integridade Referencial & Cascata:**
* A tabela `items` referencia a tabela `orders` através da chave estrangeira `order_id`.
* Configuração do ORM: `cascade="all, delete-orphan"`. Se um registro em `orders` for removido, todos os itens (`items`) associados a ele serão deletados automaticamente.

text
+------------------+ 1 : N +-------------------+
| orders |---------------------->| items |
+------------------+ +-------------------+
| id (PK) | | id (PK) |
| customer | | order_id (FK) |
| status | | sku |
| created_at | | description |
+------------------+ | quantity |
+-------------------+

---

## Como as tabelas são criadas

A criação das tabelas é gerenciada pelo **SQLAlchemy ORM** através da classe base comum de metadados (`Base.metadata.create_all(bind=engine)`).

* **Em desenvolvimento/testes (SQLite):** As tabelas são criadas localmente no arquivo `orders.db` na inicialização do serviço, caso ainda não existam.
* **Em produção (PostgreSQL):** Ao definir a variável de ambiente `DATABASE_URL` apontando para o banco relacional, o ORM executa a DDL (Data Definition Language) de criação das tabelas no esquema do banco configurado no boot da aplicação (ou via migrações com Alembic).
53 changes: 53 additions & 0 deletions k8s/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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/cloud-application-registry123/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
spec:
type: LoadBalancer
selector:
app: cloud-application
ports:
- port: 80
targetPort: 8000
15 changes: 15 additions & 0 deletions k8s/servicemonitor.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: cloud-application
namespace: default
labels:
release: monitoring # obrigatório: identifica este ServiceMonitor para o kube-prometheus-stack
spec:
selector:
matchLabels:
app: cloud-application
endpoints:
- port: http
path: /metrics
interval: 30s