diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..d48a01a --- /dev/null +++ b/.github/workflows/deploy.yml @@ -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 diff --git a/docs/adr/001-kubernetes-deploy.md b/docs/adr/001-kubernetes-deploy.md new file mode 100644 index 0000000..e5b3ccc --- /dev/null +++ b/docs/adr/001-kubernetes-deploy.md @@ -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 diff --git a/docs/adr/002-dbaas-postgresql.md b/docs/adr/002-dbaas-postgresql.md new file mode 100644 index 0000000..37800d8 --- /dev/null +++ b/docs/adr/002-dbaas-postgresql.md @@ -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 diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..328612d --- /dev/null +++ b/docs/architecture.md @@ -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 | diff --git a/docs/data-model.md b/docs/data-model.md new file mode 100644 index 0000000..3d27ef7 --- /dev/null +++ b/docs/data-model.md @@ -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). diff --git a/k8s/app.yaml b/k8s/app.yaml new file mode 100644 index 0000000..a063f41 --- /dev/null +++ b/k8s/app.yaml @@ -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 diff --git a/k8s/servicemonitor.yaml b/k8s/servicemonitor.yaml new file mode 100644 index 0000000..765a5a1 --- /dev/null +++ b/k8s/servicemonitor.yaml @@ -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