0
Arquitectura del Despliegue
neuroleads-prod namespace
🌍 Internet
api.neuroleads.io
api.neuroleads.io
↓ HTTPS :443
nginx Ingress
cert-manager TLS
cert-manager TLS
↓ :80
namespace: neuroleads-prod
Service ClusterIP
port 80 → 8000
port 80 → 8000
↓ load balance
Pod 1
api:2.1.0
api:2.1.0
Pod 2
api:2.1.0
api:2.1.0
Pod 3..8
HPA scale-out
HPA scale-out
↓
PostgreSQL
Cloud SQL / RDS
Cloud SQL / RDS
Redis
Memorystore
Memorystore
1
Namespace + ResourceQuota
Aislamiento y limites del equipo
00-namespace.yaml
YAML
apiVersion: v1 kind: Namespace metadata: name: neuroleads-prod labels: environment: production team: neuroleads managed-by: kubectl
01-resource-quota.yaml
YAML
apiVersion: v1 kind: ResourceQuota metadata: name: neuroleads-quota namespace: neuroleads-prod spec: hard: requests.cpu: "8" requests.memory: 8Gi limits.cpu: "16" limits.memory: 16Gi pods: "30"
2
ConfigMap + Secrets
Config no sensible y credenciales
Secrets en Produccion: Usar Sealed Secrets o External Secrets Operator
Los Secrets de Kubernetes son solo base64, NO cifrados en reposo por defecto. El YAML de abajo es la estructura correcta; los valores reales deben gestionarse via
kubectl create secret --from-literal o External Secrets Operator (ESO) conectado a GCP Secret Manager.
02-configmap.yaml
YAML
apiVersion: v1 kind: ConfigMap metadata: name: neuroleads-config namespace: neuroleads-prod data: LOG_LEVEL: "info" APP_ENV: "production" MAX_WORKERS: "4" PORT: "8000" CORS_ORIGINS: "https://app.neuroleads.io"
03-secrets.yaml
YAML
# CLI: kubectl create secret generic \ # neuroleads-secrets -n neuroleads-prod \ # --from-literal=DATABASE_URL='...' \ # --from-literal=REDIS_URL='...' \ # --from-literal=OPENAI_API_KEY='...' apiVersion: v1 kind: Secret metadata: name: neuroleads-secrets namespace: neuroleads-prod type: Opaque data: DATABASE_URL: <base64-encoded> REDIS_URL: <base64-encoded> OPENAI_API_KEY: <base64-encoded>
3
Deployment — Production Template
Rolling update · 3 probes · securityContext hardened
04-deployment.yaml
YAML
apiVersion: apps/v1 kind: Deployment metadata: name: neuroleads-api namespace: neuroleads-prod labels: app: neuroleads-api version: "2.1.0" environment: production spec: replicas: 2 # Minimo 2 para HA; HPA escala hasta 8 selector: matchLabels: app: neuroleads-api strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 # 1 pod extra durante actualizacion maxUnavailable: 0 # NUNCA reducir bajo las replicas deseadas template: metadata: labels: app: neuroleads-api version: "2.1.0" spec: serviceAccountName: neuroleads-sa automountServiceAccountToken: false # La API no llama a K8s API terminationGracePeriodSeconds: 30 securityContext: runAsNonRoot: true runAsUser: 1001 fsGroup: 1001 containers: - name: neuroleads-api image: ghcr.io/neuroleads/api:2.1.0 # Nunca :latest imagePullPolicy: IfNotPresent ports: - containerPort: 8000 protocol: TCP resources: requests: cpu: "200m" memory: "256Mi" limits: cpu: "1000m" memory: "512Mi" securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"] # --- 3 PROBES (startup → liveness → readiness) --- startupProbe: # Cubre arranque lento de FastAPI + deps httpGet: path: /health port: 8000 failureThreshold: 30 # 30*5s = 150s max arranque periodSeconds: 5 livenessProbe: # Reinicia si la app queda colgada httpGet: path: /health port: 8000 periodSeconds: 30 failureThreshold: 3 # 3*30s = 90s antes de restart readinessProbe: # Quita del Service si DB/Redis no responden httpGet: path: /ready # /ready comprueba DB + Redis port: 8000 periodSeconds: 10 failureThreshold: 2 envFrom: - configMapRef: name: neuroleads-config - secretRef: name: neuroleads-secrets volumeMounts: - name: tmp mountPath: /tmp # readOnlyRootFilesystem necesita /tmp volumes: - name: tmp emptyDir: {}
4
Service + Ingress TLS
api.neuroleads.io con Let's Encrypt
05-service.yaml
YAML
apiVersion: v1 kind: Service metadata: name: neuroleads-api namespace: neuroleads-prod labels: app: neuroleads-api spec: selector: app: neuroleads-api type: ClusterIP # Interno; Ingress expone al exterior ports: - port: 80 targetPort: 8000 protocol: TCP
06-ingress.yaml
YAML
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: neuroleads-api namespace: neuroleads-prod annotations: nginx.ingress.kubernetes.io/ssl-redirect: "true" cert-manager.io/cluster-issuer: "letsencrypt-prod" spec: ingressClassName: nginx tls: - hosts: ["api.neuroleads.io"] secretName: neuroleads-tls rules: - host: api.neuroleads.io http: paths: - path: / pathType: Prefix backend: service: name: neuroleads-api port: number: 80
5
RBAC — Minimo Privilegio
SA por app + rol de lectura para el equipo dev
07-serviceaccount.yaml
YAML
apiVersion: v1 kind: ServiceAccount metadata: name: neuroleads-sa namespace: neuroleads-prod # Pattern A: la API NO llama # a la Kubernetes API automountServiceAccountToken: false
08-dev-role.yaml
YAML
# Rol de lectura para equipo dev (3 personas) apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: neuroleads-dev-reader namespace: neuroleads-prod rules: - apiGroups: [""] resources: ["pods", "pods/log"] verbs: ["get","list","watch"] - apiGroups: ["apps"] resources: ["deployments"] verbs: ["get","list"]
09-rolebinding.yaml
YAML
apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: neuroleads-dev-binding namespace: neuroleads-prod subjects: - kind: User name: dev1@neuroleads.io - kind: User name: dev2@neuroleads.io - kind: User name: dev3@neuroleads.io roleRef: kind: Role name: neuroleads-dev-reader apiGroup: rbac.authorization.k8s.io
6
HPA + PodDisruptionBudget
Autoescalado en picos · seguridad en node drains
10-hpa.yaml
YAML
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: neuroleads-api-hpa namespace: neuroleads-prod spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: neuroleads-api minReplicas: 2 # Siempre 2+ para HA maxReplicas: 8 # Techo para Black Friday metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 # Scale si avg CPU > 70% - type: Resource resource: name: memory target: type: Utilization averageUtilization: 80
11-pdb.yaml
YAML
# PodDisruptionBudget: evita que # node drains dejen la API sin pods apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: neuroleads-api-pdb namespace: neuroleads-prod spec: minAvailable: 2 # Minimo 2 pods disponibles selector: # siempre durante drain/actualizacion matchLabels: app: neuroleads-api
7
Job de Migracion + CronJob de Limpieza
One-off y scheduled workloads
12-db-migrate-job.yaml
YAML
# Ejecutar ANTES de cada deploy apiVersion: batch/v1 kind: Job metadata: name: db-migrate-v2-1-0 namespace: neuroleads-prod spec: backoffLimit: 3 ttlSecondsAfterFinished: 3600 # Limpiar tras 1h template: spec: restartPolicy: OnFailure containers: - name: migrate image: ghcr.io/neuroleads/api:2.1.0 command: ["python", "-m", "alembic", "upgrade", "head"] envFrom: - secretRef: name: neuroleads-secrets resources: requests: cpu: "100m" memory: "256Mi"
13-cleanup-cronjob.yaml
YAML
# Limpieza de leads expirados: 2am diario apiVersion: batch/v1 kind: CronJob metadata: name: cleanup-expired-leads namespace: neuroleads-prod spec: schedule: "0 2 * * *" concurrencyPolicy: Forbid successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 1 jobTemplate: spec: template: spec: restartPolicy: OnFailure containers: - name: cleanup image: ghcr.io/neuroleads/api:2.1.0 command: ["python", "-m", "tasks.cleanup"] envFrom: - secretRef: name: neuroleads-secrets resources: requests: cpu: "50m" memory: "128Mi"
8
kubectl Debugging — NeuroLeads Cheatsheet
CrashLoopBackOff · OOMKilled · Pending · ImagePull
debug-comandos.sh
BASH
# Estado del namespace kubectl get pods -n neuroleads-prod kubectl get pods -n neuroleads-prod -o wide # Ver logs (actual y crasheado) kubectl logs <pod> -n neuroleads-prod kubectl logs <pod> -n neuroleads-prod --previous # Diagnosticar errores kubectl describe pod <pod> -n neuroleads-prod # Debug en vivo kubectl exec -it <pod> -n neuroleads-prod -- bash kubectl port-forward svc/neuroleads-api 8000:80 -n neuroleads-prod # Uso de recursos kubectl top pods -n neuroleads-prod kubectl top nodes # Rollback ante incidencia kubectl rollout history deployment/neuroleads-api -n neuroleads-prod kubectl rollout undo deployment/neuroleads-api -n neuroleads-prod # Validar YAML antes de aplicar kubectl apply -f manifests/ --dry-run=server -n neuroleads-prod
diagnose-errors.sh
BASH
# CrashLoopBackOff: container crashea repetidamente kubectl logs <pod> --previous -n neuroleads-prod # Buscar: error de config, missing env var, DB no conecta # OOMKilled: memoria insuficiente kubectl describe pod <pod> -n neuroleads-prod \ | grep -A5 "Last State" # Aumentar memory limit en Deployment si requerido # ImagePullBackOff: no puede descargar imagen kubectl describe pod <pod> -n neuroleads-prod # Ver sección Events: tag incorrecto, token GHCR caducado # Crear imagePullSecret si aplica: kubectl create secret docker-registry ghcr-secret \ --docker-server=ghcr.io \ --docker-username=<user> \ --docker-password=<token> \ -n neuroleads-prod # Pod Pending: recursos insuficientes en el cluster kubectl describe pod <pod> -n neuroleads-prod # Buscar: Insufficient cpu/memory, taint/toleration mismatch kubectl get events -n neuroleads-prod \ --sort-by='.lastTimestamp'
9
Checklist de Produccion — NeuroLeads
Seguridad · Fiabilidad · Observabilidad
📋 Estado del despliegue: 16/16 items
Seguridad
✓
Container no-root (runAsUser: 1001, runAsNonRoot: true)✓
readOnlyRootFilesystem: true + emptyDir en /tmp✓
allowPrivilegeEscalation: false✓
capabilities.drop: [ALL]✓
ServiceAccount dedicado (neuroleads-sa), no default✓
automountServiceAccountToken: false (Pattern A)✓
Secrets via secretRef (no en ConfigMap), guia ESO incluidaFiabilidad
✓
3 probes: startupProbe + livenessProbe + readinessProbe✓
resources.requests Y limits en todos los containers✓
minReplicas: 2 (Alta Disponibilidad)✓
PodDisruptionBudget: minAvailable 2✓
RollingUpdate strategy con maxUnavailable: 0✓
HPA 2→8 replicas por CPU+memoriaObservabilidad
✓
Endpoints /health (liveness) y /ready (readiness + DB + Redis) expuestos✓
Labels estandar: app, version, environment✓
Imagen pinada a tag semantico :2.1.0 (nunca :latest)10
Anti-Patrones Detectados y Corregidos
Del Docker Compose original a K8s produccion
✗ Antes (Docker Compose / K8s naif)
image: neuroleads-api:latest # Tag :latest = despliegues no deterministas securityContext: {} # Corre como root por defecto livenessProbe: initialDelaySeconds: 60 # Race condition si arranca mas de 60s resources: {} # Sin limits: un pod puede matar el nodo kind: ClusterRoleBinding name: cluster-admin # God-mode para la app — critico data: DB_PASSWORD: "mysecretpass" # Secret en ConfigMap en texto plano
✓ Ahora (Patrones de Produccion)
image: ghcr.io/neuroleads/api:2.1.0 # Tag semver pinado e inmutable runAsNonRoot: true / runAsUser: 1001 # + readOnlyRootFilesystem + no capabilities startupProbe: failureThreshold: 30 # 150s max arranque, sin race condition requests: cpu:200m mem:256Mi limits: cpu:1000m mem:512Mi # Scheduling predictable, no OOMKilled vecinos Role neuroleads-dev-reader # Solo get/list/watch pods+logs en namespace secretRef: name: neuroleads-secrets # Guia ESO/GCP Secret Manager incluida
11
Inventario de Recursos Kubernetes
13 objetos en neuroleads-prod
| Archivo | Kind | Nombre | Namespace | Proposito |
|---|---|---|---|---|
| 00-namespace.yaml | Namespace | neuroleads-prod | — | Aislamiento del equipo |
| 01-resource-quota.yaml | ResourceQuota | neuroleads-quota | neuroleads-prod | Limitar consumo de CPU/RAM/pods |
| 02-configmap.yaml | ConfigMap | neuroleads-config | neuroleads-prod | Variables no sensibles |
| 03-secrets.yaml | Secret | neuroleads-secrets | neuroleads-prod | DATABASE_URL, REDIS_URL, OPENAI_KEY |
| 04-deployment.yaml | Deployment | neuroleads-api | neuroleads-prod | App FastAPI, 3 probes, security hardened |
| 05-service.yaml | Service | neuroleads-api | neuroleads-prod | ClusterIP :80 → :8000 |
| 06-ingress.yaml | Ingress | neuroleads-api | neuroleads-prod | TLS, api.neuroleads.io, cert-manager |
| 07-serviceaccount.yaml | ServiceAccount | neuroleads-sa | neuroleads-prod | SA dedicado, no token injected |
| 08-dev-role.yaml | Role | neuroleads-dev-reader | neuroleads-prod | Lectura de pods/logs para equipo dev |
| 09-rolebinding.yaml | RoleBinding | neuroleads-dev-binding | neuroleads-prod | Vincula 3 devs al rol de lectura |
| 10-hpa.yaml | HorizontalPodAutoscaler | neuroleads-api-hpa | neuroleads-prod | Autoscaling CPU70% / Mem80%, 2→8 |
| 11-pdb.yaml | PodDisruptionBudget | neuroleads-api-pdb | neuroleads-prod | minAvailable: 2 durante drains |
| 12-db-migrate-job.yaml | Job | db-migrate-v2-1-0 | neuroleads-prod | Alembic migrate pre-deploy |
| 13-cleanup-cronjob.yaml | CronJob | cleanup-expired-leads | neuroleads-prod | Limpieza nocturna 2am diario |
Orden de aplicacion recomendado
kubectl apply -f 00-namespace.yaml && kubectl apply -f 02-configmap.yaml -f 03-secrets.yaml -f 07-serviceaccount.yaml -f 08-dev-role.yaml -f 09-rolebinding.yaml && kubectl apply -f 12-db-migrate-job.yaml && kubectl wait --for=condition=complete job/db-migrate-v2-1-0 -n neuroleads-prod && kubectl apply -f 04-deployment.yaml -f 05-service.yaml -f 06-ingress.yaml -f 10-hpa.yaml -f 11-pdb.yaml -f 13-cleanup-cronjob.yaml