Desarrollo en Kubernetes: 5 Claves para Apps Eficientes

Kubernetes DevOps Best Practices Desarrollo
9 minutos de lectura

Hay un momento en la vida de todo equipo de desarrollo donde alguien dice: “ya tenemos el cluster de Kubernetes andando, ahora hay que meter la app”. Y ahí empieza un descubrimiento que nadie anticipa: desarrollar una aplicación que corre en Kubernetes no es lo mismo que desarrollar una que corre en un servidor.

No se trata de Docker — dockerizar tu app es el paso más fácil. Se trata de que Kubernetes introduce un conjunto de reglas y comportamientos que tu aplicación necesita respetar. Cosas que aparecen a las 3 AM cuando algo se rompe en producción y nadie entiende por qué.

Este post es la guía que nos hubiera gustado tener antes de poner nuestra primera aplicación en un cluster.

// 01

Tu aplicación va a tener múltiples réplicas. Preparate.

En un servidor tradicional, tu app corre como un único proceso. Un solo estado en memoria. Un solo sistema de archivos. En Kubernetes, tu aplicación va a tener 2, 3, 10 o más réplicas corriendo al mismo tiempo — cada pod independiente, sin compartir memoria ni disco con los demás.

Problemas comunes

Estado en memoria: si un usuario hace login en el pod A y el siguiente request cae en el pod B, el pod B no tiene idea de quién es. Las sesiones en memoria, los caches locales y los contadores en variables globales no funcionan con múltiples réplicas.

Archivos locales: si tu app guarda algo en /tmp/reports/, ese archivo solo existe en ese pod. Las otras réplicas no lo ven. Y cuando el pod muere, desaparece.

Tareas de inicio: si tu app corre migraciones al arrancar y tenés 5 réplicas levantando al mismo tiempo, tenés 5 migraciones en paralelo — con resultados que van de inofensivos a catastróficos.

Cómo resolverlo

Todo estado compartido vive fuera de tu app: sesiones en Redis, cache en Memcached, archivos en S3 o un PersistentVolume compartido. Tu app debe poder responder cualquier request en cualquier réplica sin asumir que el anterior cayó en el mismo pod.

Las migraciones deben ser un Job separado que corre antes del deploy, no parte del startup de tu aplicación.

// 02

🔄Durante un deploy, dos versiones de tu app conviven.

Con Rolling Update, los pods se reemplazan de a uno. Esto significa que durante segundos — o minutos — van a estar corriendo pods con la versión vieja y la nueva al mismo tiempo. Este es probablemente el punto más importante y el menos intuitivo para equipos que vienen del mundo de “un servidor, un deploy”.

El problema con los cambios destructivos

Si la versión nueva renombra una columna de base de datos, los pods viejos (que todavía están sirviendo tráfico) van a explotar porque esperan el esquema anterior. Lo mismo aplica para cambios en contratos de API entre servicios.

Los cambios destructivos de esquema necesitan hacerse en múltiples deploys:

Deploy 1
Agregar la columna nueva — sin eliminar la vieja. Ambas versiones coexisten sin errores.
Deploy 2
Migrar el código para usar la columna nueva. La vieja sigue presente como fallback.
Deploy 3
Eliminar la columna vieja — recién cuando ya no hay pods que la usen.
Reglas de retrocompatibilidad

APIs y mensajes: los cambios en contratos deberían ser siempre aditivos (agregar campos, agregar endpoints) y nunca destructivos — al menos no sin un período de coexistencia.

Feature flags: permiten activar funcionalidad nueva solo cuando el 100% de los pods ya están en la versión nueva. Esto desacopla el deploy del release.

// 03

💀Tu aplicación va a morir. Muchas veces. Aceptalo.

En un servidor tradicional, tu proceso corre durante meses. En Kubernetes, tus pods pueden morir y renacer constantemente — y esto es comportamiento normal, no un error.

Por qué puede morir tu pod

Rolling Update, autoscaler bajando réplicas, Karpenter consolidando nodos, una Spot Instance interrumpiéndose, el liveness probe fallando, OOMKilled por exceder el memory limit, o un problema de hardware en el nodo.

  • Implementá graceful shutdown: cuando Kubernetes envía SIGTERM, tu app debería dejar de aceptar conexiones nuevas, terminar los requests en vuelo, cerrar conexiones a bases de datos y colas, y recién entonces terminar. Sin esto, los requests en vuelo se pierden.
  • Startup rápido y repetible: levantá el proceso, conectate a las dependencias críticas, reportá que estás listo, empezá a servir. Todo lo que no sea necesario para el primer request puede hacerse de forma asincrónica.
  • No schedules tareas internas con setInterval para dentro de 6 horas — ese pod probablemente no va a existir. Usá CronJobs de Kubernetes o una cola externa para tareas diferidas.
// 04

⚙️La configuración no va en el código. Va en el entorno.

Tu aplicación no debería saber si está corriendo en desarrollo, staging o producción mirando un if en el código. Debería comportarse diferente porque recibe configuración diferente del entorno.

Qué debe ser variable de entorno

URL de la base de datos, credenciales de servicios externos, feature flags, nivel de logging, URLs de APIs internas, configuración de cache. Kubernetes tiene ConfigMaps para configuración no sensible y Secrets para credenciales.

El mismo build de Docker debería funcionar en cualquier entorno. Si tenés que buildear una imagen diferente para staging y producción, algo está mal. El artefacto es el mismo; lo que cambia es la configuración que recibe. Esto hace que tus tests en staging sean representativos de producción — porque es literalmente el mismo código.

// 05

❤️Los health checks no son opcionales. Son tu contrato con Kubernetes.

Kubernetes necesita saber tres cosas sobre tu aplicación. Cada una con una probe específica:

startup probe
¿Arrancó correctamente?
Le dice a Kubernetes “todavía estoy levantando, no me mates”. Crítico para apps con startup lento — sin esto, Kubernetes puede reiniciarla en loop infinito.
readiness probe
¿Está lista para recibir tráfico?
Si devuelve error (porque perdió conexión a la BD), Kubernetes deja de enviarle tráfico temporalmente. La app no se reinicia — solo se saca de la rotación.
liveness probe
¿Está viva y funcionando?
Si deja de responder (deadlock, memory leak, thread pool agotado), Kubernetes la reinicia. Es la red de seguridad para problemas que no se arreglan solos.
El error más común

Usar el mismo endpoint para readiness y liveness. Si tu readiness check valida la conexión a la BD y la BD tiene un problema momentáneo, querés que Kubernetes saque el pod de la rotación (readiness), no que lo mate y reinicie (liveness) — reiniciarlo no va a arreglar un problema en la base de datos.

Recomendación

Liveness: endpoint básico que valida que el proceso está respondiendo (devuelve 200). Readiness: valida que la app puede servir requests correctamente — conexión a dependencias críticas, estado interno sano.

// 06

📋Los logs van a stdout. Siempre.

En un servidor tradicional escribís logs en archivos. En Kubernetes, no hagas esto. Los archivos de log dentro de un pod desaparecen cuando el pod muere. Y el pod va a morir.

Convención en Kubernetes

Las aplicaciones escriben sus logs a stdout y stderr. Kubernetes los captura automáticamente y los hace disponibles via kubectl logs. Si tenés Loki, recolecta los logs de stdout de cada pod automáticamente — sin que tu app tenga que saber que Loki existe.

  • Formato estructurado (JSON): cuando tenés 50 pods generando logs simultáneamente, poder filtrar por campos (level, service, request_id) es la diferencia entre encontrar el problema en segundos o en horas.
  • Correlation ID en cada log: cuando un request atraviesa múltiples servicios, el correlation ID te permite seguir el rastro completo a través de todos los pods involucrados.
  • No loguees información sensible — los logs centralizados son accesibles por más personas que el servidor original. El blast radius de un log con credenciales es mucho mayor.
// 07

📊Definí tus recursos. No es opcional.

Cada pod debería tener requests y limits de CPU y memoria configurados. Sin ellos, Kubernetes no puede tomar buenas decisiones sobre dónde colocar tus pods, el autoscaler no puede dimensionar los nodos, y un pod con memory leak puede tumbar a todos los pods que comparten su nodo.

Los dos peores errores

No poner recursos: Kubernetes trata tu pod como “best effort” — el primero en morir cuando hay presión en el nodo. El autoscaler no tiene información para dimensionar correctamente.

Adivinar los valores: poner 1Gi de memoria porque “suena bien” es tan malo como no poner nada. Los valores deberían basarse en datos reales de uso — de un dashboard de observabilidad que muestre consumo bajo carga normal y picos.

Diferencia entre requests y limits

Requests es lo mínimo que tu pod necesita. Kubernetes lo usa para decidir en qué nodo colocar el pod. Limits es el máximo que puede consumir — si excede la memoria, el pod es OOMKilled; si excede la CPU, es throttled pero no muere.

// 08

🔗Pensá en la comunicación entre servicios

En un monolito, llamar a otra parte de la app es una función. En microservicios dentro de Kubernetes, llamar a otro servicio es un request de red. Y la red falla.

  • Retries con backoff exponencial: si un request a otro servicio falla, no lo reintentes inmediatamente en loop. Usá backoff con jitter para no generar una avalancha de reintentos que empeore el problema.
  • Timeouts en todas las llamadas externas: cada request HTTP, query a BD, conexión a Redis. Sin timeouts, un servicio colgado puede bloquear a todos los que dependen de él — efecto dominó.
  • Circuit breakers: si un servicio downstream falla consistentemente, tu app debería dejar de llamarlo temporalmente. Esto protege a ambos servicios y le da tiempo para recuperarse.
  • DNS no siempre resuelve instantáneamente: bajo carga o durante cambios de topología puede haber latencia. Cachear resoluciones DNS y manejar errores de resolución es importante para servicios de alta frecuencia.
Checklist — Aplicación lista para Kubernetes
Área Qué validar Estado
Estado Sin estado en memoria ni en filesystem local entre requests Crítico
Migraciones Corren como Job separado, no al startup de cada réplica Crítico
Retrocompat. Cambios de esquema y API en múltiples deploys aditivos Crítico
Shutdown Escucha SIGTERM, termina requests en vuelo, cierra conexiones Crítico
Config ConfigMaps y Secrets, mismo build para todos los entornos Crítico
Health checks Startup, readiness y liveness probes distintos y correctos Crítico
Logging stdout, JSON estructurado, correlation ID, sin datos sensibles Importante
Recursos requests y limits basados en datos reales de observabilidad Importante
Comunicación Retries con backoff, timeouts explícitos, circuit breakers Importante

La infraestructura que acompaña a tu equipo

Desde SleakOps, la infraestructura de Kubernetes viene configurada con las mejores prácticas para que tu equipo pueda enfocarse en estos patrones de aplicación — sin preocuparse además por la complejidad del cluster, el networking, el autoescalado y la observabilidad.

Conocé SleakOps →

¿Listo para optimizar tu nube?

Agenda una demo con uno de nuestros expertos para mostrarte cómo Sleakops puede transformar tu operación en la nube.

Artículos relacionados