NeuroLeads API — Kubernetes Produccion

Manifiestos completos · namespace: neuroleads-prod · cluster: GKE · generado por CULTIVA IA

✓ Security Hardened HPA 2→8 replicas TLS + cert-manager
9Recursos K8s
2→8Replicas (HPA)
TLSLet's Encrypt
3Probes activos
0ClusterAdmin
neuroleads-prod · ghcr.io/neuroleads/api:2.1.0
FastAPI · Python · PostgreSQL · Redis · GKE
0
Arquitectura del Despliegue
neuroleads-prod namespace
🌍 Internet
api.neuroleads.io
↓ HTTPS :443
nginx Ingress
cert-manager TLS
↓ :80
namespace: neuroleads-prod
Service ClusterIP
port 80 → 8000
↓ load balance
Pod 1
api:2.1.0
Pod 2
api:2.1.0
Pod 3..8
HPA scale-out
PostgreSQL
Cloud SQL / RDS
Redis
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 incluida
Fiabilidad
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+memoria
Observabilidad
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.yamlNamespaceneuroleads-prodAislamiento del equipo
01-resource-quota.yamlResourceQuotaneuroleads-quotaneuroleads-prodLimitar consumo de CPU/RAM/pods
02-configmap.yamlConfigMapneuroleads-configneuroleads-prodVariables no sensibles
03-secrets.yamlSecretneuroleads-secretsneuroleads-prodDATABASE_URL, REDIS_URL, OPENAI_KEY
04-deployment.yamlDeploymentneuroleads-apineuroleads-prodApp FastAPI, 3 probes, security hardened
05-service.yamlServiceneuroleads-apineuroleads-prodClusterIP :80 → :8000
06-ingress.yamlIngressneuroleads-apineuroleads-prodTLS, api.neuroleads.io, cert-manager
07-serviceaccount.yamlServiceAccountneuroleads-saneuroleads-prodSA dedicado, no token injected
08-dev-role.yamlRoleneuroleads-dev-readerneuroleads-prodLectura de pods/logs para equipo dev
09-rolebinding.yamlRoleBindingneuroleads-dev-bindingneuroleads-prodVincula 3 devs al rol de lectura
10-hpa.yamlHorizontalPodAutoscalerneuroleads-api-hpaneuroleads-prodAutoscaling CPU70% / Mem80%, 2→8
11-pdb.yamlPodDisruptionBudgetneuroleads-api-pdbneuroleads-prodminAvailable: 2 durante drains
12-db-migrate-job.yamlJobdb-migrate-v2-1-0neuroleads-prodAlembic migrate pre-deploy
13-cleanup-cronjob.yamlCronJobcleanup-expired-leadsneuroleads-prodLimpieza 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