Kubernetes para developers: lo esencial sin volverte SRE
No necesitas saber operar un clúster para trabajar bien sobre Kubernetes. Necesitas entender cuatro piezas y saber leer un log cuando algo falla. Esto es lo que de verdad importa.
Kubernetes tiene fama merecida de ser enorme. Tiene CRDs, operators, admission controllers, mallas de servicio, políticas de red, mecanismos de autoscaling en tres capas distintas y una terminología que parece diseñada para intimidar. Si tu trabajo es construir producto, no operar el clúster, casi nada de eso te hace falta el día a día. Lo que sí te hace falta es un puñado de conceptos concretos —y saber leer un error cuando tu servicio no arranca— para no depender de un ticket a la plataforma cada vez que algo se comporta raro.
Este artículo cubre exactamente eso: lo que un developer necesita para entender dónde vive su aplicación, cómo llega el tráfico hasta ella, cómo le pasa su configuración y sus secretos, y cómo diagnosticar un fallo sin tener que convertirse en SRE. Deliberadamente deja fuera operators, service mesh, políticas de red y autoscaling avanzado: eso es trabajo de quien construye la plataforma, no de quien construye el servicio que corre encima.
El contenedor no se despliega solo: el Pod
Un contenedor Docker, por sí mismo, no es una unidad que Kubernetes sepa gestionar directamente. La unidad mínima que Kubernetes programa, arranca y vigila es el Pod: un grupo de uno o más contenedores que comparten red y almacenamiento, y que siempre se despliegan y destruyen juntos.
En la inmensa mayoría de los casos un Pod contiene un único contenedor —tu aplicación—, y pensar “Pod” como sinónimo de “instancia de mi contenedor corriendo” es una simplificación razonable el 90% del tiempo. El otro 10%, un Pod agrupa un contenedor principal con uno o más sidecars: un proxy que gestiona el tráfico, un agente que exporta métricas, un proceso que sincroniza certificados. Todos ellos comparten la misma IP y el mismo localhost, lo que permite que se comuniquen entre sí sin red externa.
Lo importante que hay que interiorizar sobre un Pod es que es efímero por diseño. Puede morir por falta de recursos en el nodo, por un despliegue nuevo, porque el nodo físico se reinicia o porque el propio sistema decide reprogramarlo en otro sitio. Un Pod nunca se “repara”: cuando falla, se destruye y se crea uno nuevo desde cero, con una IP distinta. Esto no es un defecto, es la base de por qué Kubernetes puede ser resiliente: si asumes que cualquier Pod puede desaparecer en cualquier momento, dejas de escribir aplicaciones que dependen de que una instancia concreta siga viva.
Quién mantiene vivos tus Pods: el Deployment
Un Deployment describe, de forma declarativa, cuántas réplicas de tu Pod quieres que existan y qué versión de tu contenedor deben ejecutar. Es la pieza que traduce “quiero 3 instancias de mi API corriendo siempre” en la realidad: si un Pod muere, el Deployment crea otro para reemplazarlo; si escalas a 5 réplicas, crea dos más; si despliegas una versión nueva, orquesta el reemplazo gradual de las réplicas antiguas por las nuevas.
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-pedidos
spec:
replicas: 3
selector:
matchLabels:
app: api-pedidos
template:
metadata:
labels:
app: api-pedidos
spec:
containers:
- name: api-pedidos
image: registry.miempresa.com/api-pedidos:1.4.2
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
Dos detalles de ese manifiesto que suelen pasar desapercibidos a quien viene del mundo de “solo escribo código”:
selector.matchLabelses cómo el Deployment sabe qué Pods son “suyos”. La etiqueta (label)app: api-pedidosen eltemplatetiene que coincidir con el selector. Si las cambias por separado sin cuidado, el Deployment deja de reconocer sus propios Pods.resources.requestsyresources.limitsno son opcionales en la práctica, aunque Kubernetes te deje omitirlos.requestses lo que el planificador reserva para tu Pod al decidir en qué nodo colocarlo;limitses el techo que no puede superar. Omitirlos es la causa número uno de nodos que se quedan sin memoria de forma impredecible, y de facturas de nube más altas de lo necesario porque nadie sabe cuánto reservar realmente.
Cuando despliegas una versión nueva, el Deployment por defecto usa una estrategia de rolling update: apaga Pods viejos y levanta Pods nuevos de forma gradual, verificando que los nuevos estén listos antes de seguir, para no tener downtime. Esa verificación depende de que tu aplicación exponga correctamente sus health checks (readinessProbe y livenessProbe); si no los configuras, Kubernetes asume que el contenedor está listo en cuanto arranca el proceso, lo cual no siempre es cierto —una app que todavía está calentando una caché o conectando a la base de datos puede empezar a recibir tráfico antes de estar realmente preparada.
Cómo llega el tráfico: el Service
Aquí está el problema que resuelve el tercer concepto clave. Si cada Pod tiene una IP propia y esa IP cambia cada vez que el Pod se recrea, ¿cómo sabe el resto de tu sistema —u otro servicio, u otro equipo— a qué dirección debe hablar? La respuesta es el Service: una IP estable y un nombre DNS interno que balancean tráfico automáticamente hacia el conjunto de Pods vivos que coincidan con su selector, sin que a nadie fuera de ese Service le importe cuántas réplicas hay ni cuáles son sus IPs reales en cada momento.
graph LR Cliente["Otro servicio / cliente"] -->|"petición a api-pedidos"| Service["Service: api-pedidos"] Service -->|"balancea"| Pod1["Pod (réplica 1)"] Service -->|"balancea"| Pod2["Pod (réplica 2)"] Service -->|"balancea"| Pod3["Pod (réplica 3)"] Deployment["Deployment: api-pedidos"] -->|"crea y vigila"| Pod1 Deployment -->|"crea y vigila"| Pod2 Deployment -->|"crea y vigila"| Pod3
apiVersion: v1
kind: Service
metadata:
name: api-pedidos
spec:
selector:
app: api-pedidos
ports:
- port: 80
targetPort: 8080
type: ClusterIP
El campo type determina desde dónde es alcanzable ese Service, y es la fuente de más confusión entre quien empieza:
ClusterIP(el valor por defecto): solo alcanzable desde dentro del clúster. Es lo que usas para que un servicio hable con otro servicio interno —tu API hablando con tu servicio de autenticación, por ejemplo.NodePort: abre un puerto fijo en cada nodo del clúster para exponer el Service hacia fuera. Se usa poco en producción de forma directa; suele ser un paso intermedio o una solución para entornos pequeños.LoadBalancer: pide al proveedor cloud (AWS, GCP, Azure) que aprovisione un balanceador de carga externo real y lo conecte al Service. Es la forma habitual de exponer un servicio a internet.- Además, casi cualquier clúster real usa un Ingress (o la Gateway API, su sucesora más moderna) por encima de los Services para gestionar rutas HTTP, dominios y TLS de forma centralizada, en vez de crear un
LoadBalancerpor cada servicio.
El nombre DNS interno que Kubernetes asigna automáticamente a un Service (api-pedidos.default.svc.cluster.local, o simplemente api-pedidos si estás en el mismo namespace) es, en la práctica, lo único que necesitas escribir en la configuración de otro servicio para que le hable. Nunca hardcodees una IP de Pod en tu código: no sobrevivirá al siguiente despliegue.
Configuración y secretos: ConfigMap y Secret
Tu aplicación necesita configuración —URLs de otros servicios, flags de features, niveles de log— y credenciales —contraseñas de base de datos, API keys, certificados—. Kubernetes separa ambos conceptos en dos recursos con la misma forma pero tratamiento distinto: ConfigMap para lo no sensible, Secret para lo que sí lo es.
apiVersion: v1
kind: ConfigMap
metadata:
name: api-pedidos-config
data:
LOG_LEVEL: "info"
FEATURE_NUEVO_CHECKOUT: "true"
---
apiVersion: v1
kind: Secret
metadata:
name: api-pedidos-secrets
type: Opaque
stringData:
DATABASE_URL: "postgres://usuario:password@db-interna:5432/pedidos"
Ambos se inyectan en tu Pod de la misma manera —como variables de entorno o como archivos montados en un volumen—, pero un Secret tiene protecciones que un ConfigMap no tiene: Kubernetes puede cifrarlo en reposo dentro de etcd, y cuando se monta como volumen se guarda en memoria (tmpfs), nunca en disco. Aun así, conviene ser preciso sobre lo que un Secret no hace por defecto: los valores viajan codificados en Base64, no cifrados, así que cualquiera con permisos de lectura sobre ese Secret en el clúster puede verlos en texto plano con un simple kubectl get secret ... -o jsonpath. Base64 no es seguridad, es solo un formato de transporte; la seguridad real depende de restringir con RBAC quién puede leer Secrets, y de no versionar nunca un Secret en texto plano en un repositorio Git —para eso existen herramientas como Sealed Secrets o SOPS, que ya mencionamos al hablar de GitOps.
containers:
- name: api-pedidos
image: registry.miempresa.com/api-pedidos:1.4.2
envFrom:
- configMapRef:
name: api-pedidos-config
- secretRef:
name: api-pedidos-secrets
Leer logs y depurar sin llamar a nadie
Aquí está la parte que más tiempo ahorra en el día a día: saber diagnosticar por qué tu Pod no arranca antes de escalar el problema a la plataforma.
# ¿Qué Pods existen y en qué estado están?
kubectl get pods -l app=api-pedidos
# Logs del contenedor (añade -f para seguirlos en vivo)
kubectl logs api-pedidos-7d9f8c6b5-x2k9p -f
# Logs del intento ANTERIOR, si el contenedor ya se reinició
kubectl logs api-pedidos-7d9f8c6b5-x2k9p --previous
# Eventos del Pod: por qué no arranca, por qué no se programa
kubectl describe pod api-pedidos-7d9f8c6b5-x2k9p
# Una shell dentro del contenedor, si necesitas inspeccionar en vivo
kubectl exec -it api-pedidos-7d9f8c6b5-x2k9p -- sh
Los estados que verás con más frecuencia, y lo que casi siempre significan:
| Estado | Causa habitual | Dónde mirar primero |
|---|---|---|
Pending | El planificador no encuentra un nodo con recursos suficientes, o falta un volumen/recurso que el Pod necesita | kubectl describe pod → sección Events |
ImagePullBackOff | La imagen no existe con ese tag, o faltan credenciales para el registro privado | Nombre y tag de la imagen, imagePullSecrets |
CrashLoopBackOff | El proceso dentro del contenedor arranca y termina (crashea) repetidamente | kubectl logs --previous, casi siempre un error de arranque de la propia app |
OOMKilled | El contenedor superó su memory limit y el kernel lo mató | Ajustar resources.limits.memory o corregir una fuga de memoria real |
Running pero sin tráfico | El Service no encuentra el Pod: las labels no coinciden con el selector, o el readinessProbe nunca pasa | kubectl get endpoints <service>, comparar labels |
Para depurar contenedores construidos sobre imágenes mínimas o distroless, que no incluyen ni siquiera una shell, kubectl debug permite adjuntar temporalmente un contenedor de depuración (por ejemplo busybox) al mismo Pod, sin modificar la imagen original — útil para inspeccionar el sistema de archivos o la red sin reconstruir nada.
Lo que deliberadamente no necesitas saber (todavía)
Todo esto es territorio de quien opera la plataforma, no de quien construye el servicio, y está bien delegarlo:
- Operators y CRDs: extensiones que enseñan a Kubernetes a gestionar recursos con lógica propia (una base de datos, un certificado). Los consumes, no los escribes, salvo que tu trabajo sea precisamente construir plataforma.
- Service mesh (Istio, Linkerd): capas de red avanzada para mTLS automático, políticas de tráfico y observabilidad entre servicios. Se nota cuando existe, pero rara vez lo configuras tú mismo.
- Políticas de red y RBAC detallado: quién puede hablar con quién y quién puede leer qué recurso. Alguien las define a nivel de clúster o de namespace.
- Autoscaling de nodos e infraestructura subyacente: cuántos servidores físicos o virtuales componen el clúster y cómo escalan. El Horizontal Pod Autoscaler, que ajusta el número de réplicas de tu Deployment según CPU o métricas custom, sí puede interesarte, pero se configura una vez y rara vez se toca a diario.
Si tu organización tiene una plataforma interna que ya resuelve la parte de plantillas, CI/CD y observabilidad por ti, es probable que trabajes con Kubernetes sin escribir manifiestos a mano casi nunca — ese es exactamente el objetivo de Platform Engineering. Pero entender qué es un Pod, qué hace un Deployment, cómo enruta un Service y por dónde empezar a mirar cuando algo falla sigue siendo la diferencia entre depender de un ticket para cada duda y resolver el 80% de los problemas del día a día por tu cuenta.
Artículos relacionados
Arquitectura de microservicios: cuándo tiene sentido y cuándo es un error
Ni la solución universal de 2015 ni el error universal de 2020. Repasamos el coste real de los microservicios -complejidad operativa, latencia de red, consistencia distribuida- frente al beneficio real, y cuándo cada uno pesa más.
GitOps con Git como fuente de verdad: guía práctica
GitOps no es 'usar Git para desplegar'. Es un modelo concreto donde un controlador dentro del clúster tira de los cambios en vez de que un pipeline externo los empuje. La diferencia importa para la seguridad y la fiabilidad.
Platform Engineering en 2026: qué es y por qué está reemplazando al DevOps tradicional
DevOps prometía que cada equipo fuera dueño de su infraestructura. En la práctica, eso se ha traducido en desarrolladores agotados de configurar YAML. Platform Engineering es la respuesta organizativa a ese problema.