diff --git a/README.md b/README.md index 138b882..dc46f42 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ -# athletic-map-deploy +# Athletic Map - Deploy (GitOps) +Um diretorio por tenant em tenants/. +O ApplicationSet do ArgoCD gera uma Application por tenant (silo de pods). diff --git a/tenants/piloto/00-namespace-quota-netpol.yaml b/tenants/piloto/00-namespace-quota-netpol.yaml new file mode 100644 index 0000000..f23431f --- /dev/null +++ b/tenants/piloto/00-namespace-quota-netpol.yaml @@ -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) diff --git a/tenants/piloto/10-postgres.yaml b/tenants/piloto/10-postgres.yaml new file mode 100644 index 0000000..3e729e2 --- /dev/null +++ b/tenants/piloto/10-postgres.yaml @@ -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 diff --git a/tenants/piloto/20-keycloak.yaml b/tenants/piloto/20-keycloak.yaml new file mode 100644 index 0000000..bf9384e --- /dev/null +++ b/tenants/piloto/20-keycloak.yaml @@ -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 diff --git a/tenants/piloto/30-apps-stubs.yaml b/tenants/piloto/30-apps-stubs.yaml new file mode 100644 index 0000000..1e8421b --- /dev/null +++ b/tenants/piloto/30-apps-stubs.yaml @@ -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: | + + Athletic Map — Piloto + +

Athletic Map

+

Frontend (stub) — silo-piloto no k3s

+

tenant: piloto

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