feat: tenant piloto (namespace, postgres, keycloak, stubs, ingress)

This commit is contained in:
ATM Platform
2026-06-15 20:33:49 +00:00
parent 3f153b13ac
commit eba21e91ac
5 changed files with 345 additions and 1 deletions
+3 -1
View File
@@ -1,2 +1,4 @@
# athletic-map-deploy
# Athletic Map - Deploy (GitOps)
Um diretorio por tenant em tenants/<cliente>.
O ApplicationSet do ArgoCD gera uma Application por tenant (silo de pods).
@@ -0,0 +1,59 @@
# Silo-piloto Athletic Map — isolamento do tenant (namespace + quota + limites + rede)
# Modelo: 1 namespace por cliente (ver arquitetura-multitenant-pods-por-cliente.md)
apiVersion: v1
kind: Namespace
metadata:
name: piloto-prod
labels:
name: piloto-prod
tenant: piloto
athleticmap.io/tier: pilot
---
# Cota de recursos do tenant — protege o cluster contra consumo excessivo de um cliente
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-quota
namespace: piloto-prod
spec:
hard:
requests.cpu: "2"
requests.memory: 2Gi
limits.cpu: "4"
limits.memory: 6Gi
pods: "20"
persistentvolumeclaims: "4"
---
# Defaults de recursos por container (para a cota ser satisfeita sem declarar em cada pod)
apiVersion: v1
kind: LimitRange
metadata:
name: defaults
namespace: piloto-prod
spec:
limits:
- type: Container
default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 128Mi
---
# Isolamento de rede: nega tráfego cross-tenant; permite intra-namespace + ingress (Traefik) + egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-cross-tenant
namespace: piloto-prod
spec:
podSelector: {}
policyTypes: [Ingress, Egress]
ingress:
- from:
- podSelector: {} # mesma namespace
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system # Traefik (ingress)
egress:
- {} # egress liberado (DNS, Postgres, internet)
+83
View File
@@ -0,0 +1,83 @@
# PostgreSQL dedicado do tenant (banco da aplicação + banco do Keycloak)
apiVersion: v1
kind: ConfigMap
metadata:
name: pg-initdb
namespace: piloto-prod
data:
01-keycloak.sql: |
CREATE DATABASE keycloak;
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
namespace: piloto-prod
spec:
accessModes: [ReadWriteOnce]
storageClassName: local-path
resources:
requests:
storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: piloto-prod
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
value: athleticmap
- name: POSTGRES_USER
value: atm
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
- name: initdb
mountPath: /docker-entrypoint-initdb.d
readinessProbe:
exec:
command: ["pg_isready", "-U", "atm", "-d", "athleticmap"]
initialDelaySeconds: 10
periodSeconds: 5
volumes:
- name: data
persistentVolumeClaim:
claimName: postgres-data
- name: initdb
configMap:
name: pg-initdb
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: piloto-prod
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
+78
View File
@@ -0,0 +1,78 @@
# Keycloak dedicado do tenant (IdP do silo) — modo dev para piloto, persistindo no Postgres
apiVersion: apps/v1
kind: Deployment
metadata:
name: keycloak
namespace: piloto-prod
spec:
replicas: 1
selector:
matchLabels:
app: keycloak
template:
metadata:
labels:
app: keycloak
spec:
containers:
- name: keycloak
image: quay.io/keycloak/keycloak:26.0
args: ["start-dev"]
env:
- name: KC_DB
value: postgres
- name: KC_DB_URL
value: "jdbc:postgresql://postgres:5432/keycloak"
- name: KC_DB_USERNAME
value: atm
- name: KC_DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
- name: KC_BOOTSTRAP_ADMIN_USERNAME
value: admin
- name: KC_BOOTSTRAP_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-admin
key: password
- name: KC_HEALTH_ENABLED
value: "true"
- name: KC_HTTP_ENABLED
value: "true"
- name: KC_PROXY_HEADERS
value: xforwarded
- name: KC_HOSTNAME
value: "auth.187.77.37.184.nip.io"
- name: KC_HOSTNAME_STRICT
value: "false"
ports:
- containerPort: 8080
- containerPort: 9000
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi
readinessProbe:
httpGet:
path: /health/ready
port: 9000
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 40
---
apiVersion: v1
kind: Service
metadata:
name: keycloak
namespace: piloto-prod
spec:
selector:
app: keycloak
ports:
- port: 8080
targetPort: 8080
+122
View File
@@ -0,0 +1,122 @@
# Esqueletos (stubs) de Backend, BFF e Frontend — validam o roteamento ponta-a-ponta do silo.
# Backend e BFF usam traefik/whoami (eco de requisição); Frontend é um nginx com página placeholder.
# Substituir pelas imagens reais nas Fases 1+ do roadmap.
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: backend, namespace: piloto-prod }
spec:
replicas: 1
selector: { matchLabels: { app: backend } }
template:
metadata: { labels: { app: backend } }
spec:
containers:
- name: whoami
image: traefik/whoami:latest
args: ["--name", "athletic-map-backend (stub)"]
ports: [{ containerPort: 80 }]
---
apiVersion: v1
kind: Service
metadata: { name: backend, namespace: piloto-prod }
spec:
selector: { app: backend }
ports: [{ port: 80, targetPort: 80 }]
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: bff, namespace: piloto-prod }
spec:
replicas: 1
selector: { matchLabels: { app: bff } }
template:
metadata: { labels: { app: bff } }
spec:
containers:
- name: whoami
image: traefik/whoami:latest
args: ["--name", "athletic-map-bff (stub)"]
ports: [{ containerPort: 80 }]
---
apiVersion: v1
kind: Service
metadata: { name: bff, namespace: piloto-prod }
spec:
selector: { app: bff }
ports: [{ port: 80, targetPort: 80 }]
---
apiVersion: v1
kind: ConfigMap
metadata: { name: frontend-index, namespace: piloto-prod }
data:
index.html: |
<!doctype html>
<html lang="pt-br"><head><meta charset="utf-8"><title>Athletic Map — Piloto</title>
<style>body{font-family:system-ui,sans-serif;background:#0a2547;color:#fff;display:flex;
min-height:100vh;align-items:center;justify-content:center;margin:0}
.c{text-align:center}h1{color:#37ca37}a{color:#188bf6}</style></head>
<body><div class="c"><h1>Athletic Map</h1>
<p>Frontend (stub) — silo-piloto no k3s</p>
<p>tenant: <b>piloto</b></p></div></body></html>
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: frontend, namespace: piloto-prod }
spec:
replicas: 1
selector: { matchLabels: { app: frontend } }
template:
metadata: { labels: { app: frontend } }
spec:
containers:
- name: nginx
image: nginx:alpine
ports: [{ containerPort: 80 }]
volumeMounts:
- { name: html, mountPath: /usr/share/nginx/html }
volumes:
- name: html
configMap: { name: frontend-index }
---
apiVersion: v1
kind: Service
metadata: { name: frontend, namespace: piloto-prod }
spec:
selector: { app: frontend }
ports: [{ port: 80, targetPort: 80 }]
---
# Ingress Traefik com hosts nip.io (resolvem para o IP do servidor sem configurar DNS)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: piloto
namespace: piloto-prod
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: traefik
tls:
- hosts:
- piloto.187.77.37.184.nip.io
- auth.187.77.37.184.nip.io
- api.187.77.37.184.nip.io
- bff.187.77.37.184.nip.io
secretName: piloto-tls
rules:
- host: piloto.187.77.37.184.nip.io
http:
paths:
- { path: /, pathType: Prefix, backend: { service: { name: frontend, port: { number: 80 } } } }
- host: auth.187.77.37.184.nip.io
http:
paths:
- { path: /, pathType: Prefix, backend: { service: { name: keycloak, port: { number: 8080 } } } }
- host: api.187.77.37.184.nip.io
http:
paths:
- { path: /, pathType: Prefix, backend: { service: { name: backend, port: { number: 80 } } } }
- host: bff.187.77.37.184.nip.io
http:
paths:
- { path: /, pathType: Prefix, backend: { service: { name: bff, port: { number: 80 } } } }