Deployment — leadpulse-api
Workload principal · 3 réplicas · FastAPI · Puerto 8080 · Security Context completo
apps/v1
3 replicas
deployment.yaml
--- apiVersion: apps/v1 kind: Deployment metadata: name: leadpulse-api namespace: production labels: app.kubernetes.io/name: leadpulse-api app.kubernetes.io/instance: leadpulse-api-prod app.kubernetes.io/version: "2.4.1" app.kubernetes.io/component: backend app.kubernetes.io/part-of: leadpulse app.kubernetes.io/managed-by: kubectl annotations: description: "API REST de scoring de leads con IA para equipos de ventas" contact: "platform@leadpulse.ai" prometheus.io/scrape: "true" prometheus.io/port: "9090" prometheus.io/path: "/metrics" spec: replicas: 3 selector: matchLabels: app.kubernetes.io/name: leadpulse-api app.kubernetes.io/instance: leadpulse-api-prod strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 template: metadata: labels: app.kubernetes.io/name: leadpulse-api app.kubernetes.io/instance: leadpulse-api-prod app.kubernetes.io/version: "2.4.1" spec: ## Pod-level security context securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 fsGroup: 1000 seccompProfile: type: RuntimeDefault containers: - name: leadpulse-api image: gcr.io/leadpulse/leadpulse-api:v2.4.1 imagePullPolicy: Always ports: - containerPort: 8080 name: http protocol: TCP - containerPort: 9090 name: metrics protocol: TCP ## Container-level security context securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: - ALL ## Resource requests y limits resources: requests: memory: "256Mi" cpu: "250m" limits: memory: "1Gi" cpu: "1000m" ## Health checks livenessProbe: httpGet: path: /health port: http initialDelaySeconds: 30 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: httpGet: path: /ready port: http initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 3 successThreshold: 1 ## Variables de entorno desde ConfigMap y Secret envFrom: - configMapRef: name: leadpulse-api-config - secretRef: name: leadpulse-api-secret ## Volumen para cache de modelos ML volumeMounts: - name: models-cache mountPath: /var/lib/leadpulse/models - name: tmp mountPath: /tmp volumes: - name: models-cache persistentVolumeClaim: claimName: leadpulse-api-models - name: tmp emptyDir: {} terminationGracePeriodSeconds: 60 restartPolicy: Always
Service — leadpulse-api
ClusterIP · Puerto 80 → 8080 · Acceso interno desde ingress controller
ClusterIP
v1
service.yaml
--- apiVersion: v1 kind: Service metadata: name: leadpulse-api namespace: production labels: app.kubernetes.io/name: leadpulse-api app.kubernetes.io/instance: leadpulse-api-prod app.kubernetes.io/component: backend app.kubernetes.io/part-of: leadpulse annotations: description: "Servicio interno para leadpulse-api" spec: type: ClusterIP selector: app.kubernetes.io/name: leadpulse-api app.kubernetes.io/instance: leadpulse-api-prod ports: - name: http port: 80 targetPort: http protocol: TCP - name: metrics port: 9090 targetPort: metrics protocol: TCP sessionAffinity: None
ConfigMap — leadpulse-api-config
Configuracion no sensible · 7 variables de entorno · Separacion config/codigo
v1
non-secret
configmap.yaml
--- apiVersion: v1 kind: ConfigMap metadata: name: leadpulse-api-config namespace: production labels: app.kubernetes.io/name: leadpulse-api app.kubernetes.io/instance: leadpulse-api-prod app.kubernetes.io/component: config annotations: description: "Configuracion no sensible de leadpulse-api" data: ## Modo y logging APP_MODE: "production" LOG_LEVEL: "info" ## Base de datos DATABASE_HOST: "postgres.leadpulse.internal" DATABASE_NAME: "leadpulse_prod" DATABASE_PORT: "5432" ## Cache REDIS_URL: "redis://redis.leadpulse.internal:6379" ## Modelo IA OPENAI_MODEL: "gpt-4o-mini" MODEL_CACHE_PATH: "/var/lib/leadpulse/models" ## Metricas METRICS_PORT: "9090" METRICS_PATH: "/metrics"
Secret — leadpulse-api-secret
Datos sensibles · 3 secretos · Usar External Secrets Operator en produccion real
Opaque
NO commit en Git
secret.yaml
⚠ Usar Sealed Secrets / ESO en produccion
--- apiVersion: v1 kind: Secret metadata: name: leadpulse-api-secret namespace: production labels: app.kubernetes.io/name: leadpulse-api app.kubernetes.io/instance: leadpulse-api-prod app.kubernetes.io/component: secrets annotations: description: "Credenciales sensibles de leadpulse-api" secret-manager: "gcp-secret-manager" rotation-period: "90d" type: Opaque stringData: ## Reemplazar con valores reales via CI/CD o External Secrets Operator DATABASE_PASSWORD: "<inyectado-por-eso>" OPENAI_API_KEY: "<inyectado-por-eso>" JWT_SECRET_KEY: "<inyectado-por-eso>" ## ----------------------------------------------- ## PRODUCCION: Usar ExternalSecret con GCP Secret Manager: ## ## apiVersion: external-secrets.io/v1beta1 ## kind: ExternalSecret ## metadata: ## name: leadpulse-api-secret ## namespace: production ## spec: ## refreshInterval: 1h ## secretStoreRef: ## name: gcp-secret-store ## kind: ClusterSecretStore ## target: ## name: leadpulse-api-secret ## data: ## - secretKey: DATABASE_PASSWORD ## remoteRef: ## key: leadpulse/prod/database-password ## - secretKey: OPENAI_API_KEY ## remoteRef: ## key: leadpulse/prod/openai-api-key ## - secretKey: JWT_SECRET_KEY ## remoteRef: ## key: leadpulse/prod/jwt-secret-key ## -----------------------------------------------
PersistentVolumeClaim — leadpulse-api-models
Cache de modelos ML · 20Gi · ssd-premium · ReadWriteOnce
20Gi
ssd-premium
pvc.yaml
--- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: leadpulse-api-models namespace: production labels: app.kubernetes.io/name: leadpulse-api app.kubernetes.io/instance: leadpulse-api-prod app.kubernetes.io/component: storage annotations: description: "Cache de modelos ML para leadpulse-api" backup-policy: "daily" backup-retention: "7d" spec: accessModes: - ReadWriteOnce storageClassName: ssd-premium resources: requests: storage: 20Gi
Validacion y buenas practicas
Checklist de produccion verificado contra las guias cloud-native de Kubernetes
Seguridad
✓ runAsNonRoot: true (uid 1000)
✓ allowPrivilegeEscalation: false
✓ readOnlyRootFilesystem: true
✓ capabilities.drop: [ALL]
✓ seccompProfile: RuntimeDefault
✓ Secretos no hardcodeados en imagen
⚠ Usar External Secrets Operator en prod
Disponibilidad
✓ 3 replicas para alta disponibilidad
✓ RollingUpdate con maxUnavailable: 0
✓ livenessProbe configurado
✓ readinessProbe configurado
✓ terminationGracePeriodSeconds: 60
✓ Imagen con tag especifico (v2.4.1)
Recursos
✓ requests.memory: 256Mi
✓ requests.cpu: 250m
✓ limits.memory: 1Gi
✓ limits.cpu: 1000m
✓ PVC 20Gi ssd-premium para ML
✓ EmptyDir /tmp para escritura
Observabilidad
✓ Labels estandar app.kubernetes.io/*
✓ Anotaciones prometheus.io/scrape
✓ Puerto 9090 expuesto para metrics
✓ Anotacion contact y description
✓ Version en labels (2.4.1)
✓ Backup annotations en PVC
📋 Comandos de aplicacion
- kubectl create namespace production # crear namespace si no existe
- kubectl apply -f pvc.yaml --dry-run=server -n production # validar PVC
- kubectl apply -f configmap.yaml -f secret.yaml -n production # config primero
- kubectl apply -f deployment.yaml -f service.yaml -n production # workload
- kubectl rollout status deployment/leadpulse-api -n production # verificar rollout
- kubectl get pods,svc,pvc -n production -l app.kubernetes.io/name=leadpulse-api # estado final