provisioning-outreach/presentations/rust-laspalmas-250926/info/selfhosted_setup_done.md

15 KiB

🎯 ARQUITECTURA PROPUESTA PARA KUBERNETES

Stack Definitivo:

  • Pre/Post-Evento: Typebot + Formbricks (ambos en K8s)

  • Durante Evento: Claper (en K8s)

  • Datos: PostgreSQL (con operador en K8s)

  • Storage: PVC para persistencia

    📦 CONFIGURACIONES KUBERNETES

    1. Typebot Deployment

typebot-deployment.yaml

apiVersion: v1 kind: Namespace metadata: name: rust-meetup

apiVersion: v1 kind: ConfigMap metadata: name: typebot-config namespace: rust-meetup data: DATABASE_URL: "postgresql://typebot:password@postgres-service:5432/typebot" NEXTAUTH_URL: "https://typebot.rustmeetup.com" NEXT_PUBLIC_VIEWER_URL: "https://typebot.rustmeetup.com" ENCRYPTION_SECRET: "your-encryption-secret-min-32-chars" ADMIN_EMAIL: "admin@rustmeetup.com"

apiVersion: apps/v1 kind: Deployment metadata: name: typebot namespace: rust-meetup spec: replicas: 2 selector: matchLabels: app: typebot template: metadata: labels: app: typebot spec: containers: - name: typebot image: baptistearno/typebot:latest ports: - containerPort: 3000 envFrom: - configMapRef: name: typebot-config resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "1Gi" cpu: "500m" volumeMounts: - name: typebot-storage mountPath: /app/uploads volumes: - name: typebot-storage persistentVolumeClaim: claimName: typebot-pvc

apiVersion: v1 kind: Service metadata: name: typebot-service namespace: rust-meetup spec: selector: app: typebot ports: - port: 80 targetPort: 3000 type: ClusterIP

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: typebot-pvc namespace: rust-meetup spec: accessModes: - ReadWriteMany resources: requests: storage: 10Gi storageClassName: standard

  1. Formbricks Deployment

formbricks-deployment.yaml

apiVersion: v1 kind: ConfigMap metadata: name: formbricks-config namespace: rust-meetup data: DATABASE_URL: "postgresql://formbricks:password@postgres-service:5432/formbricks" NEXTAUTH_SECRET: "your-nextauth-secret-32-chars" NEXTAUTH_URL: "https://surveys.rustmeetup.com" MAIL_FROM: "noreply@rustmeetup.com" SMTP_HOST: "smtp.gmail.com" SMTP_PORT: "587" SMTP_SECURE: "false"

apiVersion: apps/v1 kind: Deployment metadata: name: formbricks namespace: rust-meetup spec: replicas: 2 selector: matchLabels: app: formbricks template: metadata: labels: app: formbricks spec: containers: - name: formbricks image: formbricks/formbricks:latest ports: - containerPort: 3000 envFrom: - configMapRef: name: formbricks-config resources: requests: memory: "512Mi" cpu: "250m" limits: memory: "1Gi" cpu: "500m" volumeMounts: - name: formbricks-storage mountPath: /home/nextjs/apps/web/uploads volumes: - name: formbricks-storage persistentVolumeClaim: claimName: formbricks-pvc

apiVersion: v1 kind: Service metadata: name: formbricks-service namespace: rust-meetup spec: selector: app: formbricks ports: - port: 80 targetPort: 3000 type: ClusterIP

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: formbricks-pvc namespace: rust-meetup spec: accessModes: - ReadWriteMany resources: requests: storage: 5Gi

  1. Claper Deployment

claper-deployment.yaml

apiVersion: v1 kind: ConfigMap metadata: name: claper-config namespace: rust-meetup data: DATABASE_URL: "postgresql://claper:password@postgres-service:5432/claper" SECRET_KEY_BASE: "your-secret-key-base-64-chars" PHX_HOST: "claper.rustmeetup.com" PORT: "4000"

apiVersion: apps/v1 kind: Deployment metadata: name: claper namespace: rust-meetup spec: replicas: 2 selector: matchLabels: app: claper template: metadata: labels: app: claper spec: containers: - name: claper image: claperco/claper:latest ports: - containerPort: 4000 envFrom: - configMapRef: name: claper-config resources: requests: memory: "256Mi" cpu: "200m" limits: memory: "512Mi" cpu: "400m" volumeMounts: - name: claper-uploads mountPath: /app/uploads volumes: - name: claper-uploads persistentVolumeClaim: claimName: claper-pvc

apiVersion: v1 kind: Service metadata: name: claper-service namespace: rust-meetup spec: selector: app: claper ports: - port: 80 targetPort: 4000 type: ClusterIP

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: claper-pvc namespace: rust-meetup spec: accessModes: - ReadWriteMany resources: requests: storage: 20Gi

  1. PostgreSQL con Operador

postgres-operator.yaml

apiVersion: v1 kind: ConfigMap metadata: name: postgres-config namespace: rust-meetup data: init.sql: | CREATE DATABASE typebot; CREATE USER typebot WITH ENCRYPTED PASSWORD 'password'; GRANT ALL PRIVILEGES ON DATABASE typebot TO typebot;

  CREATE DATABASE formbricks;
  CREATE USER formbricks WITH ENCRYPTED PASSWORD 'password';
  GRANT ALL PRIVILEGES ON DATABASE formbricks TO formbricks;

  CREATE DATABASE claper;
  CREATE USER claper WITH ENCRYPTED PASSWORD 'password';
  GRANT ALL PRIVILEGES ON DATABASE claper TO claper;

apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres namespace: rust-meetup spec: serviceName: postgres-service replicas: 1 selector: matchLabels: app: postgres template: metadata: labels: app: postgres spec: containers: - name: postgres image: postgres:15-alpine ports: - containerPort: 5432 env: - name: POSTGRES_PASSWORD value: "superpassword" - name: POSTGRES_USER value: "postgres" - name: PGDATA value: /var/lib/postgresql/data/pgdata volumeMounts: - name: postgres-storage mountPath: /var/lib/postgresql/data - name: postgres-init mountPath: /docker-entrypoint-initdb.d resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "512Mi" cpu: "500m" volumes: - name: postgres-init configMap: name: postgres-config volumeClaimTemplates: - metadata: name: postgres-storage spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 10Gi

apiVersion: v1 kind: Service metadata: name: postgres-service namespace: rust-meetup spec: selector: app: postgres ports: - port: 5432 type: ClusterIP

  1. Ingress Configuration

ingress.yaml

apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: rust-meetup-ingress namespace: rust-meetup annotations: cert-manager.io/cluster-issuer: "letsencrypt-prod" nginx.ingress.kubernetes.io/proxy-body-size: "50m" nginx.ingress.kubernetes.io/proxy-read-timeout: "600" nginx.ingress.kubernetes.io/proxy-send-timeout: "600" spec: ingressClassName: nginx tls: - hosts: - typebot.rustmeetup.com - surveys.rustmeetup.com - claper.rustmeetup.com secretName: rust-meetup-tls rules: - host: typebot.rustmeetup.com http: paths: - path: / pathType: Prefix backend: service: name: typebot-service port: number: 80 - host: surveys.rustmeetup.com http: paths: - path: / pathType: Prefix backend: service: name: formbricks-service port: number: 80 - host: claper.rustmeetup.com http: paths: - path: / pathType: Prefix backend: service: name: claper-service port: number: 80

🚀 HELM CHARTS ALTERNATIVOS

Opción con Helm (Más Mantenible)

values-typebot.yaml

replicaCount: 2 image: repository: baptistearno/typebot tag: latest pullPolicy: IfNotPresent

service: type: ClusterIP port: 80

ingress: enabled: true className: nginx hosts: - host: typebot.rustmeetup.com paths: - path: / pathType: Prefix tls: - secretName: typebot-tls hosts: - typebot.rustmeetup.com

postgresql: enabled: false external: host: postgres-service port: 5432 database: typebot user: typebot

persistence: enabled: true size: 10Gi storageClass: standard

resources: requests: cpu: 250m memory: 512Mi limits: cpu: 500m memory: 1Gi

autoscaling: enabled: true minReplicas: 2 maxReplicas: 10 targetCPUUtilizationPercentage: 70

📊 MONITOREO Y OBSERVABILIDAD

monitoring-stack.yaml

apiVersion: v1 kind: ServiceMonitor metadata: name: typebot-metrics namespace: rust-meetup spec: selector: matchLabels: app: typebot endpoints: - port: metrics interval: 30s

Grafana Dashboard ConfigMap

apiVersion: v1 kind: ConfigMap metadata: name: grafana-dashboards namespace: rust-meetup data: rust-meetup-dashboard.json: | { "dashboard": { "title": "Rust Meetup 2025 Metrics", "panels": [ { "title": "Active Survey Sessions", "targets": [ { "expr": "sum(typebot_active_sessions)" } ] }, { "title": "Response Rate", "targets": [ { "expr": "rate(typebot_responses_total[5m])" } ] }, { "title": "Live Poll Participation", "targets": [ { "expr": "sum(claper_active_participants)" } ] } ] } }

🔧 SCRIPTS DE DEPLOYMENT

Deploy Script

#!/bin/bash

deploy.sh - Deploy completo del stack

echo "🚀 Deploying Rust Meetup 2025 Stack on Kubernetes"

Create namespace

kubectl create namespace rust-meetup --dry-run=client -o yaml | kubectl apply -f -

Deploy PostgreSQL

echo "📊 Deploying PostgreSQL..." kubectl apply -f postgres-operator.yaml

Wait for PostgreSQL to be ready

kubectl wait --for=condition=ready pod -l app=postgres -n rust-meetup --timeout=300s

Deploy applications

echo "🎯 Deploying Typebot..." kubectl apply -f typebot-deployment.yaml

echo "📝 Deploying Formbricks..." kubectl apply -f formbricks-deployment.yaml

echo "🎤 Deploying Claper..." kubectl apply -f claper-deployment.yaml

Deploy Ingress

echo "🌐 Configuring Ingress..." kubectl apply -f ingress.yaml

Check deployment status

echo " Checking deployment status..." kubectl get pods -n rust-meetup kubectl get svc -n rust-meetup kubectl get ingress -n rust-meetup

echo "🎉 Deployment complete!"

Backup Script

#!/bin/bash

backup.sh - Backup de datos

NAMESPACE="rust-meetup" BACKUP_DIR="/backups/$(date +%Y%m%d-%H%M%S)"

Backup PostgreSQL

kubectl exec -n $NAMESPACE postgres-0 -- pg_dumpall -U postgres > $BACKUP_DIR/postgres-backup.sql

Backup persistent volumes

kubectl get pvc -n $NAMESPACE -o json > $BACKUP_DIR/pvcs.json

Upload to S3

aws s3 sync $BACKUP_DIR s3://rust-meetup-backups/

💡 MEJORES PRÁCTICAS

  1. Seguridad

Network Policy

apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: rust-meetup-network-policy namespace: rust-meetup spec: podSelector: {} policyTypes: - Ingress - Egress ingress: - from: - namespaceSelector: matchLabels: name: ingress-nginx egress: - to: - podSelector: matchLabels: app: postgres - to: - namespaceSelector: {} podSelector: matchLabels: k8s-app: kube-dns ports: - protocol: UDP port: 53

  1. Auto-scaling

apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: typebot-hpa namespace: rust-meetup spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: typebot minReplicas: 2 maxReplicas: 20 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80 behavior: scaleUp: stabilizationWindowSeconds: 60 policies: - type: Percent value: 100 periodSeconds: 60

📋 CHECKLIST DE PRODUCCIÓN

Pre-Deployment

  • Cluster Kubernetes ready (EKS/GKE/AKS/on-prem)

  • Ingress controller instalado (nginx/traefik)

  • Cert-manager configurado

  • Storage class disponible

  • Secrets management (Sealed Secrets/Vault)

    Deployment

  • Deploy PostgreSQL y verificar

  • Deploy apps y verificar pods running

  • Configurar DNS para subdominios

  • Verificar certificados SSL

  • Test end-to-end de cada aplicación

    Post-Deployment

  • Configurar backups automáticos

  • Setup monitoring (Prometheus/Grafana)

  • Configurar alertas

  • Load testing con k6/Locust

  • Documentar runbooks

    🎯 VENTAJAS DE ESTA ARQUITECTURA

    1. Escalabilidad: Auto-scaling basado en métricas
    2. Resiliencia: Multi-replica deployments
    3. Portabilidad: Funciona en cualquier K8s
    4. Observabilidad: Métricas y logs centralizados
    5. Seguridad: Network policies y RBAC
    6. Cost-Effective: Optimización de recursos

    💰 ESTIMACIÓN DE COSTOS

    Componente Recursos Costo Mensual
    K8s Cluster (3 nodes) 6 vCPU, 12GB RAM $150
    Storage 50GB SSD $10
    Load Balancer 1 LB $20
    Backups S3 100GB $5
    TOTAL $185/mes

    ¿Te gustaría que detalle algún aspecto específico o prepare los templates de Typebot para los cuestionarios?