Qué vas a conseguir
Decidir si una aplicación interna debe pasar de Docker Compose a Kubernetes y, si pasa, desplegarla bien en un clúster gestionado: varias réplicas, configuración fuera de la imagen, recursos acotados, actualizaciones sin corte y la vuelta atrás en un comando. Y leer el problema más típico, un pod que se reinicia sin parar, en cinco minutos.
Antes de empezar
- La guía de Docker del Nivel 3: imágenes, registro y Compose.
- La imagen de la aplicación en un registro privado (aquí
acrcontoso.azurecr.io/inventario), con etiquetas de versión, nunca sololatest. kubectly la CLI del proveedor (azoaws), con acceso por tu identidad de Entra ID o IAM Identity Center.- Un responsable del clúster en el equipo: es una plataforma que hay que mantener.
1. ¿Compensa el salto?
| Situación | Recomendación |
|---|---|
| Una o dos herramientas internas, un servidor, se toleran unos minutos de corte al actualizar | Se queda en Compose. Copias, monitorización y un docker compose pull && up -d en ventana |
| Varios servicios, varios equipos que despliegan, se necesita alta disponibilidad | Kubernetes gestionado (AKS, EKS) |
| Muchas aplicaciones, requisitos de seguridad fuertes, se quiere un portal para desarrolladores | Red Hat OpenShift o similar, con un equipo de plataforma |
| Una sola web o API sin estado, sin ganas de gestionar nodos | Un servicio de contenedores sin clúster (Azure Container Apps, AWS App Runner o ECS con Fargate) |
Lo que da Kubernetes: réplicas que se reponen solas, actualizaciones progresivas y configuración declarativa en Git. Lo que cuesta: actualizar el clúster cada pocos meses, aprender otra herramienta y vigilar más piezas.
2. El clúster gestionado
En AKS, lo que importa al crear el clúster, mejor desde la guía de Terraform que a mano:
--tier standard(SLA del plano de control), nodos en tres zonas de disponibilidad, red Azure CNI Overlay y--attach-acr acrcontosopara leer el registro sin secretos.- Integración con Entra ID y Azure RBAC para Kubernetes, y
--disable-local-accounts: nadie entra con un certificado de administrador compartido. - La entrada HTTP: el complemento de enrutamiento de aplicaciones con Gateway API (
--enable-gateway-api --enable-app-routing-istio). El proyecto ingress-nginx se retiró en marzo de 2026 y Microsoft da parches para su versión gestionada hasta noviembre de 2026; un clúster nuevo empieza ya con Gateway API, el sucesor del recurso Ingress.
En EKS el equivalente es un clúster con nodos gestionados en tres zonas, acceso con EKS access entries ligadas a IAM Identity Center y el AWS Load Balancer Controller para la entrada.
3. La herramienta interna: Deployment, Service y Gateway
Un espacio de nombres por aplicación (herramientas) y cuatro objetos:
cat inventario.yamlapiVersion: apps/v1kind: Deploymentmetadata: name: inventario namespace: herramientasspec: replicas: 2 strategy: rollingUpdate: { maxSurge: 1, maxUnavailable: 0 } selector: matchLabels: { app: inventario } template: metadata: labels: { app: inventario } spec: containers: - name: inventario image: acrcontoso.azurecr.io/inventario:1.4.2 envFrom: - configMapRef: { name: inventario-config } - secretRef: { name: inventario-db } resources: requests: { cpu: 250m, memory: 256Mi } limits: { memory: 512Mi } readinessProbe: httpGet: { path: /healthz, port: 8080 }---apiVersion: v1kind: Servicemetadata: { name: inventario, namespace: herramientas }spec: selector: { app: inventario } ports: [{ port: 80, targetPort: 8080 }]---apiVersion: gateway.networking.k8s.io/v1kind: Gatewaymetadata: { name: gw-herramientas, namespace: herramientas }spec: gatewayClassName: approuting-istio infrastructure: annotations: service.beta.kubernetes.io/azure-load-balancer-internal: "true" listeners: [{ name: http, port: 80, protocol: HTTP }]---apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata: { name: inventario, namespace: herramientas }spec: parentRefs: [{ name: gw-herramientas }] hostnames: ["inventario.intranet.contoso.com"] rules: [{ backendRefs: [{ name: inventario, port: 80 }] }]- Deployment: cuántas réplicas, qué imagen y cómo se actualiza.
maxUnavailable: 0significa que nunca baja de dos réplicas sanas durante una actualización. - Service: un nombre y una IP estables dentro del clúster delante de pods que vienen y van.
- Gateway + HTTPRoute: la entrada HTTP. La anotación crea un balanceador interno: la herramienta solo se ve desde la red de la empresa.
- readinessProbe: el pod no recibe tráfico hasta que
/healthzresponde.
4. Configuración, secretos y recursos
- ConfigMap para lo que no es secreto (
DB_HOST, nivel de log), en el mismo repositorio que el manifiesto. - Secret para la contraseña de la base de datos. Un Secret está en base64, no cifrado para quien pueda leerlo. Lo correcto es guardarlo en Key Vault o Secrets Manager y montarlo con el Secrets Store CSI Driver (en AKS, el complemento Azure Key Vault provider) y una identidad de carga de trabajo. Nunca en el repositorio.
- requests: lo que el planificador reserva en el nodo. Sin ellas, los pods acaban donde no caben.
- limits de memoria: si el contenedor los supera, se mata con OOMKilled. Para la CPU se suele poner solo la petición: un límite de CPU frena la aplicación sin avisar.
5. Actualizar y volver atrás
kubectl -n herramientas set image deployment/inventario inventario=acrcontoso.azurecr.io/inventario:1.5.0deployment.apps/inventario image updatedkubectl -n herramientas rollout status deployment/inventario --timeout=3mWaiting for deployment "inventario" rollout to finish: 1 out of 2 new replicas have been updated...error: timed out waiting for the conditionkubectl -n herramientas get podsNAME READY STATUS RESTARTS AGEinventario-5d8b7f6c49-kq7zt 1/1 Running 0 6dinventario-5d8b7f6c49-w2m9r 1/1 Running 0 6dinventario-7c9f8d6b5-x2k4p 0/1 CrashLoopBackOff 5 (40s ago) 4mkubectl -n herramientas rollout undo deployment/inventariodeployment.apps/inventario rolled backGracias a maxUnavailable: 0 y a la sonda de disponibilidad, los dos pods de la 1.4.2 siguieron atendiendo: los usuarios no notaron nada. rollout undo vuelve a la revisión anterior; rollout history las lista. En el día a día la imagen se cambia desde el pipeline o con GitOps (Argo CD, Flux).
6. El ticket: "el pod está en CrashLoopBackOff"
CrashLoopBackOff no es la causa: el contenedor arranca, muere y Kubernetes lo reintenta cada vez más tarde. La causa está en tres sitios:
kubectl -n herramientas describe pod inventario-7c9f8d6b5-x2k4p State: Waiting Reason: CrashLoopBackOff Last State: Terminated Reason: Error Exit Code: 1 Restart Count: 5Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning BackOff 15s (x18 over 4m) kubelet Back-off restarting failed container inventario in pod inventario-7c9f8d6b5-x2k4pkubectl -n herramientas logs inventario-7c9f8d6b5-x2k4p --previous2026-10-28T09:41:07Z INFO inventario 1.5.0 arrancando2026-10-28T09:41:07Z FATAL falta la variable DATABASE_URL; desde 1.5.0 sustituye a DB_HOST y DB_NAMEkubectl -n herramientas get events --sort-by=.lastTimestamp | tail -5describe: el motivo y el código de salida del último intento, y los eventos del pod.logs --previous: lo que escribió el contenedor anterior, el que murió. Sin--previousves el intento actual, que a menudo aún no ha escrito nada.get events: lo que pasa alrededor (nodo sin recursos, volumen que no monta, imagen que no baja).
| Lo que ves | Qué suele ser |
|---|---|
| Error, código 1 | La aplicación falla al arrancar: configuración, una variable, la base de datos. Lo dicen los logs |
| OOMKilled, código 137 | Superó el límite de memoria. Subir el límite o revisar la aplicación |
| CreateContainerConfigError | Falta el ConfigMap o el Secret que referencia el pod |
| ImagePullBackOff | Otra familia: la etiqueta no existe o el clúster no puede leer el registro |
Aquí la versión 1.5.0 cambió el nombre de una variable y nadie actualizó el ConfigMap. Se añade DATABASE_URL (en el Secret, porque lleva la contraseña), se revisa en pruebas y se repite la actualización.
7. Lo que revisas cada mes
- Versión del clúster frente al calendario de soporte de AKS o EKS: la siguiente actualización tiene fecha, primero en pruebas.
- Pods con reinicios en los últimos 30 días y pods sin
requestso sin sondas. - Imágenes con vulnerabilidades críticas en el registro (Defender for Containers, Amazon Inspector).
Siguientes pasos
- La guía de Red Hat OpenShift si lo que se necesita es una plataforma completa con portal, compilaciones y políticas.
- La guía de Terraform para que el clúster, el registro y la red salgan de código revisado.
- La guía de Azure Monitor y Log Analytics para Container Insights y alertas sobre reinicios, y la de Grafana para cuadros de mando con Prometheus.
- La guía de Amazon AWS si el clúster va a ser EKS.
¿Falta algo o en tu entorno es distinto? Escríbeme y lo corrijo.
Tech Career Lab · newsletter
¿Te ha servido? Cada dos semanas envío una idea práctica para perfiles IT: LinkedIn, proyectos, networking y procesos de selección.
Apúntate y llévate las 33 ideas →