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

710 lines
15 KiB
Markdown
Raw Normal View History

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