Este artículo está pensado para ingenieros, arquitectos y líderes técnicos que operan Kubernetes en producción, especialmente en entornos SaaS y multi-tenant.
Después de muchos años desplegando Open edX, Owly (nuestro asistente de IA) y otras cargas de trabajo productivas en la nube, acumulé una larga lista de lecciones aprendidas sobre cómo operar clusters de Kubernetes que sean performantes, resilientes y económicos.
Son recomendaciones prácticas, basadas en incidentes reales y en decisiones de compromiso. Cada tema podría merecer un artículo propio. No todos los consejos aplican a todos los casos, pero la mayoría son relevantes para aplicaciones SaaS web típicas, plataformas multi-tenant y cargas de trabajo de negocio.
1. Mantené las bases de datos fuera del cluster
No voy a decir que no se pueden correr bases de datos dentro de Kubernetes, aunque estoy tentado.
Salvo que tu producto se trate justamente de correr bases de datos en Kubernetes, solo voy a decir esto: si querés dormir tranquilo, mantené tus bases de datos fuera del cluster.
Para cargas OLTP, los servicios de bases de datos gestionadas de tu proveedor de nube suelen ser la mejor opción:
La infraestructura está optimizada para el rendimiento de la base de datos
Redundancia, backups y mantenimiento incluidos
Podés apagar o recrear el cluster sin perder datos
La carga de la base de datos no compite con las aplicaciones
Si los servicios gestionados no son una opción, corré las bases de datos en infraestructura dedicada, no dentro del mismo cluster que aloja los pods de tu aplicación.
2. Tené mucho cuidado con los StatefulSets
La escalabilidad de Kubernetes se basa en la idea de que las cargas de trabajo son stateless y descartables. Los pods se pueden crear, destruir y reprogramar en cualquier lugar y en cualquier momento.
Las cargas con estado rompen ese modelo.
Usar PersistentVolumes trae varios desafíos:
Tenés que resolver vos mismo el acceso concurrente y los bloqueos
Los volúmenes suelen estar atados a una sola zona de disponibilidad
Kubernetes no gestiona de forma nativa la replicación ni los backups de los PersistentVolumes; eso depende del proveedor de almacenamiento
Siempre que puedas, preferí:
Almacenamiento de objetos (S3, GCS, Azure Blob)
Sistemas compatibles con S3, como MinIO
Volúmenes efímeros, si perder los datos es aceptable
Incidente real:
Una vez tuvimos un pod con Caddy que guardaba los certificados TLS en un PV. Cuando el pod tuvo que reiniciarse, los únicos nodos disponibles estaban en otra AZ. La afinidad del volumen impidió programarlo y toda la aplicación se cayó.
La solución: pasar la gestión de certificados a cert-manager + NGINX ingress y eliminar el PV por completo, dejando Caddy solo como proxy. Ahora el pod puede reprogramarse en cualquier lugar.
3. Distribuí el cluster en varias zonas de disponibilidad y dimensioná bien las redes
Una sola zona de disponibilidad suele ser confiable, pero no infalible.
Distribuir el cluster en dos o más AZs da un buen equilibrio entre resiliencia y costo, sin la complejidad de un despliegue multi-región.
Dos puntos importantes para tener en cuenta:
Los pods y los nodos consumen direcciones IP
Los pods de sistema y de monitoreo también cuentan
Si tu rango CIDR es demasiado chico, te vas a quedar sin IPs antes de lo esperado.
Regla práctica:
Dimensioná la VPC y las subredes para la cantidad máxima esperada de nodos y pods, no para el tamaño inicial del cluster. Si te quedás sin direcciones IP, vas a tener que sumar redes o subredes, y eso es mucho más fácil de hacer al principio que con el cluster en producción.
4. Configurá el monitoreo desde el principio
Cuando el cluster crece, ajustarlo solo desde la línea de comandos se vuelve un suplicio.
Como mínimo, instalá:
metrics-server(necesario para HPA y el autoescalado)
Un stack de monitoreo sólido:
Prometheus para métricas
Grafana para visualización
Loki para logs
No se puede optimizar lo que no se ve.
5. Definí siempre requests y limits de CPU y memoria
Kubernetes decide dónde programar los pods en base a los requests, no al uso real.
Los limits, en cambio, definen qué pasa cuando algo sale mal.
Ejemplo:
resources:
requests:
cpu: "500m"
memory: "2Gi"
limits:
cpu: "1000m"
memory: "4Gi"
La suma de los requests de todos los pods asignados a un nodo va a ser menor o igual a la capacidad del nodo, tanto en CPU como en memoria.
Los pods pueden consumir recursos por encima de sus requests hasta los limits configurados, compitiendo con otros pods por los recursos disponibles del nodo.
En este ejemplo:
El pod puede usar más de 500m de CPU, pero se va a limitar (throttling) en 1000m
El throttling de CPU vuelve lentas a las aplicaciones y las expone a timeouts
La memoria se comporta distinto: si el pod supera los 4Gi aunque sea por un byte, se termina de inmediato (OOM)
Puntos clave:
Los requests determinan dónde se pueden programar los pods
Los limits de CPU provocan throttling
Los limits de memoria provocan OOM kills
Sin limits de memoria, un solo pod puede desestabilizar un nodo entero
Como barandas de seguridad, definí:
ResourceQuotapor namespaceValores por defecto con
LimitRangepara los contenedores
También podés activar VPA en modo recomendación (“off”) para observar los patrones de uso reales antes de aplicar cambios.
6. Usá un registro de contenedores privado
Los registros públicos funcionan… hasta que dejan de funcionar.
Una vez rotamos varios nodos al mismo tiempo. Los pods nuevos no pudieron arrancar porque superamos los límites de descarga de Docker Hub.
Descargas anónimas: ~100 cada 6 horas
Descargas autenticadas: ~200 cada 6 horas
En un cluster real, es sorprendentemente fácil llegar a ese límite.
Si estás en AWS, ECR es la opción obvia. Si no, usá cualquier registro privado o un plan pago de Docker Hub.
7. Usá Karpenter, pero aislá las cargas de sistema
El aprovisionamiento de nodos importa.
Los node groups gestionados tradicionales escalan en base a métricas a nivel de nodo.
Karpenter, en cambio, aprovisiona nodos en base a los requests de recursos de los pods, lo que da un control mucho más fino.
Sin embargo, Karpenter corre dentro del cluster que gestiona.
Eso es peligroso.
Buena práctica:
Un node group gestionado y chico para los pods de sistema
Uno o más node groups gestionados por Karpenter para las aplicaciones
Los nodos de sistema deberían alojar:
Karpenter
CoreDNS
Metrics server
Otros componentes críticos
Usá taints y tolerations para forzar esa separación.
Además: evitá las instancias burstable (t*) para los nodos. Los créditos de CPU se pueden agotar en el peor momento posible.
8. Configurá el HPA con cuidado (sobre todo con memoria)
El HPA escala los pods en base a los requests, no a los limits.
Podés escalar por:
CPU (recomendado)
Memoria (con precaución)
Un pico de CPU puede pasar de casi cero al limit configurado (o al 100% de la CPU del nodo si no hay límite) en una fracción de segundo y quedarse ahí mucho tiempo. Si no está bien controlado, eso puede disparar múltiples réplicas. Hasta un bug simple (por ejemplo, un loop infinito involuntario) puede tirar abajo todo el cluster.
El HPA por memoria es delicado porque el consumo base de memoria casi nunca es cero. Un pod en reposo puede estar en 0 de CPU, pero normalmente consume algo de memoria. Si el umbral está muy cerca de ese consumo base, los pods van a escalar hacia arriba pero nunca hacia abajo. Y ese consumo base se multiplica por la cantidad de réplicas, ocupando recursos de los nodos sin necesidad.
Regla práctica:
Poné los objetivos de memoria bastante por encima del consumo base (muchas veces más de 2×) para que pueda escalar hacia abajo.
Conclusión: el HPA por CPU suele ser seguro; el HPA por memoria requiere conocer muy bien el consumo base y conviene usarlo con moderación.
9. Protegé la disponibilidad con PodDisruptionBudgets
Los PodDisruptionBudgets (PDBs) controlan cómo se desalojan los pods durante:
El drenado de nodos
La consolidación de Karpenter
Los rolling updates
Sin PDBs, un deployment con una sola réplica puede desaparecer momentáneamente mientras se reprograma.
Ejemplo:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: myapp-pdb
namespace: mynamespace
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/name: myapp
Cuidado: los PDBs demasiado estrictos pueden bloquear despliegues y el autoescalado. Si algo se traba, subí las réplicas temporalmente para recuperar flexibilidad.
10. Usá bien los probes de liveness y readiness
Los probes le indican a Kubernetes si un pod:
Está vivo
Está listo para recibir tráfico
Buenas prácticas:
Liveness: barato y rápido (chequeo del proceso, un endpoint simple)
Readiness: salud a nivel de aplicación (dependencias, conexión a la base de datos)
Para arranques lentos, considerá un
startupProbe
Siempre que puedas, exponé:
/live→ devuelve200 OKde inmediato/ready→ valida las dependencias
Ejemplo:
livenessProbe:
exec:
command:
- sh
- -c
- "pgrep -f uwsgi"
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/
port: 5000
httpHeaders:
- name: Host
value: backend.myapp.com
- name: X-Forwarded-Proto
value: https
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
Acá se ejecuta pgrep -f uwsgi para verificar que el servicio uWSGI esté corriendo. Después se consulta el endpoint /health/ en el puerto 5000 para comprobar que la aplicación responde. Ese endpoint debería devolver 200 OK.
Esto mejora muchísimo la resiliencia y el comportamiento de los despliegues.
Reflexiones finales
Operar Kubernetes en producción no es trivial. Pero una vez que entendés cómo funcionan de verdad la programación de pods, el escalado y los modos de falla, se convierte en una plataforma increíblemente potente.
La mayoría de las caídas no las causa Kubernetes en sí, sino las suposiciones implícitas sobre cómo se comporta.
Kubernetes tiene sus propias reglas. El éxito en producción viene de entenderlas y diseñar con ellas, no en su contra.





