Sabes usar las dos. Has levantado Aurora Libros con compose.yaml y overrides por entorno en el módulo 4, y la has desplegado en un clúster con StatefulSet, Ingress, HPA y rollback ensayado en el módulo 6. Esta lección no viene a decirte cuál gana, sino a darte los criterios para decidir cuál necesita tu proyecto, con las equivalencias exactas entre ambos formatos y una cifra honesta del coste de mantener un clúster.
Contenido
- No son la misma categoría de herramienta
- La pregunta correcta
- Comparativa a fondo, dimensión por dimensión
- Equivalencias conceptuales
- El mismo
aurora-api, en los dos formatos - Árbol de decisión
- El camino intermedio: Compose en producción
- Swarm como escalón, y los servicios que ejecutan Compose
- Kompose y los límites reales de la conversión
- El coste oculto de Kubernetes
- Aurora Libros a tres tamaños
- No son la misma categoría de herramienta
Comparar Compose con Kubernetes es como comparar una receta con una cocina industrial. Ambas cosas sirven para comer, pero una es un documento y la otra es un sistema con personal.
| Docker Compose | Kubernetes | |
|---|---|---|
| Qué es | Un descriptor de aplicación multicontenedor y una CLI que lo aplica | Un orquestador distribuido con plano de control propio |
| Dónde vive | En tu máquina, hablando con un daemon | En un clúster de nodos, con etcd y controladores |
| Qué hace cuando algo cae | Reinicia el contenedor según restart: |
Reprograma la carga en otro nodo |
| Modelo de ejecución | Un docker compose up que ejecutas tú |
Bucles de reconciliación permanentes |
| Estado deseado | Existe mientras dure el comando | Persistente en etcd, vigilado siempre |
| Unidad mínima | El contenedor | El Pod (uno o más contenedores) |
La diferencia estructural está en la cuarta fila. Compose ejecuta lo que le pides y termina; Kubernetes guarda tu intención y no deja de compararla con la realidad. Ese bucle es lo que te da que un nodo se caiga a las cuatro de la mañana y los Pods reaparezcan en otro sin que nadie despierte. También es lo que te obliga a mantener un plano de control.
- La pregunta correcta
No es «¿cuál es mejor?». Es una batería de preguntas concretas:
- ¿Puedes permitirte que la plataforma esté caída quince minutos mientras reinicias una máquina?
- ¿Cuántos nodos vas a tener de verdad dentro de un año: uno, tres o treinta?
- ¿Hay alguien en el equipo que sepa depurar un
CrashLoopBackOffsin buscar en Internet? - ¿Quién está de guardia a las tres de la mañana, y cobra por estarlo?
- ¿Tu carga es constante o tiene picos de ×8 que exigen autoescalado?
- ¿El presupuesto aguanta el plano de control más los nodos con margen libre?
Si has respondido «quince minutos son aceptables, un nodo, nadie, nadie, constante, no» —que es la situación de la mayoría de proyectos—, Compose es la respuesta profesional, y decirlo en voz alta te ahorrará un año de sufrimiento. Si has respondido lo contrario en tres o más preguntas, Kubernetes empieza a pagar su coste.
- Comparativa a fondo, dimensión por dimensión
| Dimensión | Docker Compose | Kubernetes |
|---|---|---|
| Modelo mental | Un fichero, servicios, up/down |
API declarativa, objetos, controladores, selectores |
| Curva de aprendizaje | Horas | Semanas, y meses para operarlo bien |
| Alcance | Un host (o Swarm, con deploy:) |
Decenas o miles de nodos |
| Alta disponibilidad | La del host: si cae, cae todo | Reprograma en otro nodo automáticamente |
| Reprogramación ante caídas | No existe | El Deployment la garantiza |
| Escalado manual | --scale api=4 en el mismo host |
kubectl scale, en todo el clúster |
| Autoescalado | No | HPA, VPA y Cluster Autoscaler |
| Red | Bridge con DNS por nombre de servicio | CNI, Service, NetworkPolicy, malla opcional |
| Descubrimiento | DNS interno de Docker | DNS del clúster + Service estables |
| Balanceo | Round-robin del DNS o un proxy propio | Service (L4) + Ingress (L7) |
| Almacenamiento | Volúmenes locales del host | PV/PVC, StorageClass, provisión dinámica |
| Configuración | environment, env_file |
ConfigMap, montables o inyectables |
| Secretos | Fichero en disco o secrets: (Swarm) |
Secret + integración con gestores externos |
| Actualizaciones | Recrea el contenedor: hay corte | Rolling update con maxSurge/maxUnavailable |
| Rollback | Volver a la etiqueta anterior a mano | kubectl rollout undo, con historial |
| Sondas | healthcheck (una sola) |
Liveness, readiness y startup separadas |
| Observabilidad | Logs del daemon, Prometheus si lo montas | Métricas y eventos nativos, ecosistema enorme |
| Extensibilidad | Ninguna real | CRDs, operadores, webhooks |
| Multi-tenencia | No | Namespaces, RBAC, cuotas |
| Coste de infraestructura | El de una VM | Plano de control + nodos + margen libre |
| Coste operativo | Casi cero | Alto y permanente |
| Quién lo mantiene | Cualquiera del equipo | Alguien que sepa, de guardia |
| Madurez de equipo necesaria | Un desarrollador | Al menos una persona con experiencia real |
Las tres últimas filas deciden más proyectos que todas las anteriores juntas, y son las que menos aparecen en las comparativas de Internet.
- Equivalencias conceptuales
Casi todo lo que escribiste en el compose.yaml de Aurora Libros tiene traducción. Lo que cambia no es la idea, sino cuántos objetos hacen falta para expresarla.
| Compose | Kubernetes | Nota |
|---|---|---|
services: api: |
Deployment + Service |
Dos objetos donde había uno |
image: |
spec.containers[].image |
Idéntico; misma imagen OCI |
ports: "8080:8080" |
Service (interno) o Ingress (externo) |
La exposición se parte en dos capas |
environment: |
ConfigMap + envFrom |
La configuración sale del descriptor |
secrets: / .env |
Secret (+ gestor externo) |
Secret es base64, no cifrado por defecto |
volumes: (nombrado) |
PersistentVolumeClaim |
Con StorageClass y modo de acceso |
volumes: (bind mount) |
hostPath (evítalo) o ConfigMap montado |
Rara vez lo que quieres |
deploy.replicas |
spec.replicas |
Compose solo lo respeta en Swarm |
deploy.resources.limits |
resources.requests / limits |
K8s distingue lo que pide de lo que topa |
healthcheck: |
livenessProbe + readinessProbe + startupProbe |
De una sonda a tres semánticas |
depends_on: condition: |
initContainers + readinessProbe |
No hay orden global; hay reintentos |
restart: unless-stopped |
restartPolicy: Always (implícito) |
El Deployment ya lo garantiza |
networks: |
NetworkPolicy |
En Compose aísla; en K8s hay que declararlo |
profiles: |
Overlays de Kustomize | Ambos activan subconjuntos |
compose.prod.yaml (override) |
overlays/prod con parches |
Misma idea, distinta mecánica |
docker compose up -d |
kubectl apply -k overlays/prod |
Uno aplica y sale; el otro deja controladores |
Dos filas merecen un aviso. La de depends_on: en Kubernetes no existe el arranque ordenado global; el Pod de la API arrancará aunque PostgreSQL no esté, fallará, y CrashLoopBackOff lo reintentará hasta que la base de datos responda. Es feo de ver y es lo correcto: obliga a que la aplicación tolere que sus dependencias no estén, que es justo lo que aprendiste en 06-01. Y la de Secret: está codificado en base64, no cifrado; sin cifrado en reposo de etcd y RBAC bien puesto, no es un secreto, es un texto incómodo de leer.
- El mismo
aurora-api, en los dos formatos
aurora-api, en los dos formatosCompose, tal como quedó tras el módulo 4:
# compose.yaml (fragmento)
services:
api:
image: ghcr.io/auroralibros/aurora-api:2.0.0
restart: unless-stopped
environment:
DB_HOST: aurora-db
DB_NAME: aurora_libros
DB_USER: aurora
REDIS_URL: redis://aurora-cache:6379
LOG_LEVEL: info
secrets: [db_password]
ports: ["8080:8080"]
depends_on:
aurora-db: { condition: service_healthy }
aurora-cache: { condition: service_started }
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://localhost:8080/salud/vivo')"]
interval: 10s
timeout: 3s
retries: 3
start_period: 20s
deploy:
replicas: 3
resources:
limits: { cpus: "1.0", memory: 512M }
read_only: true
cap_drop: [ALL]
security_opt: ["no-new-privileges:true"]Veintiocho líneas. Ahora lo mismo en Kubernetes, sin recortar nada esencial:
# k8s/base/api.yaml
apiVersion: v1
kind: ConfigMap
metadata: { name: aurora-config, namespace: aurora }
data:
DB_HOST: aurora-db
DB_NAME: aurora_libros
DB_USER: aurora
REDIS_URL: redis://aurora-cache:6379
LOG_LEVEL: info
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: aurora-api, namespace: aurora }
spec:
replicas: 3
selector:
matchLabels: { app: aurora-api }
template:
metadata:
labels: { app: aurora-api }
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
containers:
- name: api
image: ghcr.io/auroralibros/aurora-api:2.0.0
ports: [{ containerPort: 8080, name: http }]
envFrom:
- configMapRef: { name: aurora-config }
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef: { name: aurora-secrets, key: db-password }
resources:
requests: { cpu: "250m", memory: "256Mi" }
limits: { cpu: "1000m", memory: "512Mi" }
startupProbe:
httpGet: { path: /salud/vivo, port: http }
failureThreshold: 30
periodSeconds: 2
livenessProbe:
httpGet: { path: /salud/vivo, port: http }
periodSeconds: 10
readinessProbe:
httpGet: { path: /salud/listo, port: http }
periodSeconds: 5
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities: { drop: [ALL] }
---
apiVersion: v1
kind: Service
metadata: { name: aurora-api, namespace: aurora }
spec:
selector: { app: aurora-api }
ports: [{ port: 80, targetPort: http }]| Compose | Kubernetes | |
|---|---|---|
| Líneas para el mismo servicio | 28 | 63 |
| Objetos declarados | 1 | 3 (ConfigMap, Deployment, Service) |
| Ficheros en un proyecto completo | 1 + overrides | Decenas, más los overlays |
| Lo que ganas | — | Reprogramación, sondas separadas, RBAC, HPA |
La proporción de más del doble de YAML no es un detalle estético: se multiplica por cada servicio, y con cuatro servicios y tres entornos tienes un directorio que ya nadie lee entero. A cambio, cada línea de más compra algo real. La pregunta es si necesitas lo que compra.
- Árbol de decisión
graph TD
A["¿Cuánto corte toleras?"] -->|"Minutos, sin drama"| B["¿Más de un nodo?"]
A -->|"Segundos o ninguno"| E["¿Hay alguien que sepa K8s?"]
B -->|No| C["¿Picos de carga imprevisibles?"]
B -->|Sí| E
C -->|No| D["**Compose en un host**<br/>bien montado"]
C -->|Sí| F["¿Cabe en un servicio<br/>gestionado de contenedores?"]
F -->|Sí| G["**Cloud Run / Container Apps**<br/>autoescala sin clúster"]
F -->|No| E
E -->|"No, y no se puede contratar"| H["**Gestionado o Compose**<br/>nunca K8s autogestionado"]
E -->|Sí| I["¿Presupuesto para<br/>plano de control + margen?"]
I -->|No| H
I -->|Sí| J["**Kubernetes gestionado**<br/>módulo 6 tal cual"]
Cinco criterios y ninguno es «lo que se lleva». Fíjate en que el camino hacia Kubernetes exige dos síes seguidos —conocimiento y presupuesto— y que el nodo H existe porque la peor decisión posible es un clúster autogestionado que nadie sabe reparar.
- El camino intermedio: Compose en producción
Hay una idea muy extendida y falsa: que Compose «no es para producción». Compose en un host bien montado sirve a millones de peticiones diarias en empresas reales. Lo que hace falta es montarlo con criterio:
# compose.prod.yaml — lo que convierte un juguete en producción
services:
api:
image: ghcr.io/auroralibros/aurora-api@sha256:9f2c... # digest, no etiqueta
restart: unless-stopped # sobrevive al reinicio
logging:
driver: json-file
options: { max-size: "10m", max-file: "3" } # el disco no se llena
deploy:
resources:
limits: { cpus: "1.0", memory: 512M } # nadie se come el host
healthcheck: { test: ["CMD", "node", "healthcheck.js"], interval: 10s }| Requisito de producción | Cómo se cumple con Compose |
|---|---|
| Arrancar tras reiniciar el host | restart: unless-stopped + Docker con systemd |
| No perder datos | Volumen aurora-datos + copia externa probada |
| No llenar el disco | Rotación de logs + docker system prune programado |
| Imagen reproducible | Referencia por digest, nunca :latest |
| Actualizar sin corte largo | docker compose up -d --pull always (segundos) |
| Vuelta atrás | El digest anterior en Git; up -d de nuevo |
| Vigilancia | cAdvisor + Prometheus + alertas (05-06) |
| Copia de seguridad | pg_dump programado a almacenamiento remoto |
| Certificado TLS | Caddy o Traefik delante, con renovación automática |
Los dos huecos que no puede tapar: el host es un punto único de fallo (si muere la máquina, muere el servicio hasta que arranques otra) y no hay autoescalado. Si el negocio tolera esos dos huecos —y muchísimos negocios los toleran—, has resuelto la plataforma con un fichero, sin plano de control y sin guardias.
Una regla honesta de dimensionamiento: una VM de 4 vCPU y 8 GB con la pila de Aurora Libros bien afinada atiende con holgura varios cientos de peticiones por segundo. Antes de dar por hecho que necesitas un clúster, mide con la prueba de carga de 06-06.
- Swarm como escalón, y los servicios que ejecutan Compose
Si el problema es solo el punto único de fallo, hay un escalón intermedio antes del salto grande.
| Opción | Qué resuelve | Qué cuesta | Estado en 2026 |
|---|---|---|---|
| Docker Swarm | Varios nodos, reprogramación, rolling updates | Aprender poco: deploy: en el mismo fichero |
Mantenido, sin apenas evolución |
| Compose + host de reserva | Reponer rápido tras una caída | Un procedimiento manual bien ensayado | Trivial |
| ECS con Compose | Ejecuta un compose.yaml en AWS |
Atarse al proveedor | En uso |
| Cloud Run / Container Apps | Autoescalado sin clúster, incluso a cero | Menos control de red y de estado | Muy maduros |
| Kubernetes gestionado | Todo lo del módulo 6 | Coste y conocimiento | El estándar |
Swarm (06-03) merece un párrafo de honestidad: es sencillo, funciona, y llevar el compose.yaml a tres nodos cuesta añadir deploy: y docker stack deploy. Pero su ecosistema está congelado, contratar a alguien que lo conozca es más difícil cada año y la documentación de terceros escasea. Como escalón temporal es razonable; como apuesta a cinco años, piénsalo dos veces.
Los servicios tipo Cloud Run son la novedad que cambia el árbol de decisión: autoescalan de cero a cientos de instancias sin que exista ningún clúster que mantener. Para ghcr.io/auroralibros/aurora-api:2.0.0 —sin estado, con sondas, doce factores y apagado ordenado— encajan sin tocar nada. El estado se va a una base de datos gestionada, y el problema de la disponibilidad deja de ser tuyo.
- Kompose y los límites reales de la conversión
kompose traduce un compose.yaml a manifiestos de Kubernetes. Es útil como punto de partida y peligroso como resultado final.
kompose convert -f compose.yaml -o k8s/
# INFO Kubernetes file "aurora-api-service.yaml" created
# INFO Kubernetes file "aurora-api-deployment.yaml" created
# WARN Volume mount on the host "./datos" isn't supported - ignoring
# WARN Service "aurora-db" won't be created because 'ports' is not specified| Qué convierte bien | Qué convierte mal o ignora |
|---|---|
image, command, ports |
depends_on (lo pierde: no hay orden) |
environment → variables sueltas |
healthcheck → no genera las tres sondas |
deploy.replicas |
Bind mounts → advertencia y a tu cuenta |
restart → restartPolicy |
secrets → los deja como ficheros o los pierde |
networks (parcialmente) |
profiles, extends, develop.watch |
Ningún Ingress, HPA, PDB ni NetworkPolicy |
La conclusión práctica: kompose te ahorra el primer 60 % del tecleo y no te ahorra nada del pensamiento. Todo lo que hace que un despliegue sea apto para producción —sondas separadas, requests y limits medidos, PDB, política de red, Ingress con TLS— sigue siendo trabajo tuyo. Trátalo como un borrador, revísalo línea a línea y no lo apliques nunca directamente contra un clúster real.
- El coste oculto de Kubernetes
Lo que no sale en las presentaciones:
| Coste | Qué significa en la práctica |
|---|---|
| El clúster | Plano de control (gestionado o no) + nodos + margen para reprogramar |
| Recursos ociosos | Necesitas capacidad libre para que quepan los Pods de un nodo caído |
| Actualizaciones | Versiones nuevas cada pocos meses, con soporte limitado y APIs que se retiran |
| La deuda de YAML | Decenas de ficheros por entorno; los overlays tapan pero no eliminan |
| Modos de fallo nuevos | CrashLoopBackOff, ImagePullBackOff, Pending por falta de recursos, Evicted por presión de memoria, PVC que no se enlaza, DNS del clúster que falla |
| Depuración más difícil | El problema puede estar en la app, el Pod, el nodo, el CNI, el CSI o el Ingress |
| Herramientas alrededor | Helm o Kustomize, un GitOps, un gestor de secretos, un sistema de métricas |
| Formación continua | El ecosistema cambia más rápido de lo que se consolida el conocimiento |
| Las guardias | Alguien tiene que responder a las tres de la mañana |
La última fila es la que decide. Formúlala así en la reunión: «si el clúster se rompe un sábado a las tres de la mañana, ¿quién lo arregla, en cuánto tiempo y cobrando qué?». Si no hay una respuesta con un nombre propio, Kubernetes no es una opción todavía, por muy bien que quede en la arquitectura.
Y conviene decirlo también al revés: cuando la respuesta existe, Kubernetes devuelve con creces lo que cuesta. Autoescalado real, despliegues sin corte, reprogramación automática, cuotas por equipo y un ecosistema que resuelve problemas que ni sabías que tenías. El error no es elegir Kubernetes; es elegirlo antes de tiempo.
- Aurora Libros a tres tamaños
| Tienda pequeña | En crecimiento | Con picos de campaña | |
|---|---|---|---|
| Tráfico | 5-20 req/s | 100-300 req/s | 40 req/s con picos ×8-15 |
| Equipo | 2 desarrolladores | 6 personas, 1 con sistemas | 4 desarrolladores |
| Guardias | No | Horario laboral | No |
| Corte tolerable | 30 min | 5 min | Cero en campaña |
| Recomendación | Compose en un host | Kubernetes gestionado | Cloud Run o similar |
| Por qué | Un fichero, cero clúster, coste mínimo | El tráfico y el equipo ya lo justifican | Autoescala sin clúster que mantener |
| Base de datos | En el mismo host, con copias probadas | Gestionada, con réplica | Gestionada, obligatorio |
| Riesgo asumido | El host es punto único de fallo | Coste operativo permanente | Dependencia del proveedor |
| Siguiente paso | Monitorización y copias probadas | PDB, HPA y presupuesto de errores | Medir el coste por petición en pico |
Fíjate en la tercera columna: mucho tráfico en pico no implica Kubernetes. Implica autoescalado, y hay más de una forma de conseguirlo. Y en la segunda, lo que inclina la balanza no es solo el tráfico: es que existe una persona con perfil de sistemas. Sin esa persona, la recomendación sería otra aunque el tráfico fuese el mismo.
Errores Comunes y Consejos
- Elegir Kubernetes por currículum. Es una razón real y humana, y es la peor de todas para el proyecto. Si quieres aprenderlo, monta un clúster de laboratorio con kind, no la plataforma de producción de la empresa.
- Creer que «Compose no es para producción». Con digests, límites, rotación de logs, copias probadas y vigilancia, Compose sostiene negocios reales. Lo que no da es tolerancia a la caída del host ni autoescalado.
- Aplicar la salida de
komposesin revisarla. Pierdedepends_on, no genera las tres sondas ni Ingress, HPA o PDB, e ignora los bind mounts. - Migrar a Kubernetes sin haber medido. Antes de justificar un clúster por rendimiento, ejecuta la prueba de carga de 06-06 sobre el host actual. El límite real suele estar en la base de datos, y un clúster no lo mueve.
- Traducir
depends_onesperando arranque ordenado. No existe. La aplicación tiene que reintentar; si no lo hace, arréglala antes de migrar. - Tratar los
Secretcomo cifrados. Son base64. Sin cifrado en reposo de etcd y RBAC estricto, no protegen nada. - Consejo: escribe la decisión y sus motivos en un documento breve dentro del repositorio, con fecha. Dentro de un año querrás saber por qué se eligió, y si las premisas siguen siendo ciertas.
- Consejo: mantén el
compose.yamlvivo aunque despliegues en Kubernetes. Es la mejor forma de levantar la pila entera en local para desarrollar y depurar. - Consejo: si dudas, empieza por Compose. Migrar de Compose a Kubernetes con una imagen bien hecha es un trabajo de días; desmontar un clúster que nadie sabe operar cuesta meses.
Ejercicios
Ejercicio 1 — Traduce un servicio completo. Toma el servicio aurora-cache (redis:7-alpine, sin exponer al exterior, con volumen para la persistencia opcional, límite de 256 MB de memoria y un healthcheck con redis-cli ping) tal como está en el compose.yaml. Escríbelo en Kubernetes: Deployment, Service interno y PersistentVolumeClaim. Indica qué elemento del original no tiene equivalente directo y cómo lo resuelves.
Ejercicio 2 — Aplica el árbol de decisión. Para cada escenario, recorre el árbol de §6, indica el nodo final y justifica la decisión en tres frases. (a) Una API interna de facturación que usan 30 empleados en horario de oficina, con un desarrollador que también hace de administrador. (b) Una plataforma de venta de entradas que agota un concierto en 90 segundos, con equipo de plataforma de cinco personas y guardias. (c) Un blog corporativo con 400 visitas al día que un proveedor externo quiere desplegar en un clúster gestionado «porque es lo estándar».
Ejercicio 3 — El coste real de la decisión. Aurora Libros crece y la dirección pregunta si «hay que pasar a Kubernetes». Prepara una comparativa de una página con: coste mensual estimado de infraestructura en ambos escenarios (usa cifras aproximadas de proveedor y explícita tus supuestos), tiempo de aprendizaje y de puesta en marcha, qué mejora medible aporta el clúster, qué riesgo nuevo introduce, y tu recomendación con la condición que tendría que cumplirse para cambiarla.
Soluciones
Solución 1.
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: aurora-cache-datos, namespace: aurora }
spec:
accessModes: [ReadWriteOnce]
resources: { requests: { storage: 1Gi } }
---
apiVersion: apps/v1
kind: Deployment
metadata: { name: aurora-cache, namespace: aurora }
spec:
replicas: 1
strategy: { type: Recreate } # un solo PVC ReadWriteOnce
selector: { matchLabels: { app: aurora-cache } }
template:
metadata: { labels: { app: aurora-cache } }
spec:
containers:
- name: redis
image: redis:7-alpine
args: ["--maxmemory", "200mb", "--maxmemory-policy", "allkeys-lru"]
ports: [{ containerPort: 6379, name: redis }]
resources:
requests: { cpu: "50m", memory: "128Mi" }
limits: { cpu: "500m", memory: "256Mi" }
livenessProbe:
exec: { command: ["redis-cli", "ping"] }
periodSeconds: 10
readinessProbe:
exec: { command: ["redis-cli", "ping"] }
periodSeconds: 5
volumeMounts: [{ name: datos, mountPath: /data }]
volumes:
- name: datos
persistentVolumeClaim: { claimName: aurora-cache-datos }
---
apiVersion: v1
kind: Service
metadata: { name: aurora-cache, namespace: aurora }
spec:
selector: { app: aurora-cache }
ports: [{ port: 6379, targetPort: redis }]Lo que no tiene equivalente directo es el healthcheck único de Compose: aquí se ha desdoblado en liveness y readiness, ambas con redis-cli ping pero con periodos distintos, porque en Kubernetes «está vivo» y «puede recibir tráfico» son preguntas separadas. Tres decisiones más que el enunciado no daba y hay que razonar: strategy: Recreate en lugar de rolling, porque un PVC ReadWriteOnce no puede montarse en dos Pods a la vez y el rolling se quedaría bloqueado; el --maxmemory de Redis puesto por debajo del limits.memory del contenedor, para que Redis expulse claves antes de que el kernel mate el proceso por OOM; y la ausencia de ports expuestos hacia fuera, ya que el Service sin tipo es ClusterIP y solo se alcanza desde dentro del clúster, que es exactamente lo que se pedía.
Solución 2.
(a) API interna de facturación. Corte tolerable: horas, porque fuera del horario de oficina no hay nadie usándola. Un solo nodo basta y no hay picos. Nodo final: Compose en un host bien montado. El único administrador no puede sostener un clúster, y un despliegue con restart: unless-stopped, copias probadas y monitorización básica cubre el requisito con creces. Añadir Kubernetes aquí multiplicaría el coste operativo sin mejorar nada perceptible.
(b) Venta de entradas. El corte tolerable es cero durante los 90 segundos que importan, hay equipo de plataforma y hay guardias. El pico es brutal pero previsible en el instante, lo que permite preescalar antes del evento en lugar de esperar a que reaccione el HPA. Nodo final: Kubernetes gestionado. Es el caso de libro: alta disponibilidad real, autoescalado, despliegues sin corte y un equipo que puede operarlo. Aquí el clúster devuelve lo que cuesta.
(c) Blog corporativo. 400 visitas al día son unas 0,005 peticiones por segundo de media. El corte tolerable es de horas. Nodo final: Compose en un host, o directamente alojamiento estático si el contenido lo permite. Un clúster gestionado para esto cuesta más al mes que el propio blog genera de valor y añade una dependencia que nadie del equipo sabrá reparar. Que sea «lo estándar» no es un requisito: pide al proveedor que justifique la decisión con los mismos criterios del árbol y la conversación se acaba sola.
Solución 3. Estructura de la comparativa (los importes son ilustrativos y hay que sustituirlos por los del proveedor y la región reales):
| Concepto | Compose en un host | Kubernetes gestionado |
|---|---|---|
| Cómputo | 1 VM de 4 vCPU / 8 GB | 3 nodos de 2 vCPU / 4 GB + margen |
| Plano de control | 0 | Coste fijo mensual del proveedor |
| Base de datos | En el host (o gestionada) | Gestionada, obligatoria en la práctica |
| Balanceador y TLS | Traefik en el mismo host | Balanceador del proveedor, facturado aparte |
| Infraestructura | Base | Entre 2,5 y 4 veces la base |
| Puesta en marcha | Ya está hecho | 2-4 semanas de trabajo real |
| Formación | 0 | 1-3 meses hasta operar con soltura |
| Mantenimiento | Parches del host | Parches + actualizaciones de clúster |
Mejoras medibles del clúster: recuperación automática ante caída de nodo (de «minutos con intervención humana» a «segundos sin ella»), autoescalado de 3 a 12 réplicas ante picos, y despliegues sin corte con rollback en un comando. Riesgos nuevos: dependencia de un conocimiento que hoy no existe en el equipo, más superficie de configuración que puede fallar, y coste fijo que no baja cuando baja el tráfico.
Recomendación: seguir en Compose y revisar la decisión con un disparador concreto, no con una fecha. Por ejemplo: «pasamos a Kubernetes gestionado cuando se cumplan dos de estas tres condiciones: superar de forma sostenida el 60 % de CPU del host, que el negocio fije un objetivo de disponibilidad por encima del 99,9 %, o que se incorpore al equipo una persona con experiencia real operando clústeres». Escribir el disparador convierte una discusión de opiniones en una condición verificable, y ese es el verdadero entregable del ejercicio.
Conclusión
Ya puedes defender la decisión con criterios en lugar de con modas. Tienes claro lo primero y lo más olvidado: no son la misma categoría de herramienta. Compose es un descriptor que ejecutas y termina; Kubernetes guarda tu intención en etcd y no deja de reconciliarla con la realidad. De esa diferencia salen todas las demás, incluidas las buenas —reprogramación automática, autoescalado, despliegues sin corte— y las caras.
Tienes la comparativa por dimensiones, con las tres filas que deciden más proyectos que ninguna otra: coste de infraestructura, coste operativo y madurez del equipo. Tienes la tabla de equivalencias completa, con sus dos avisos importantes: depends_on no tiene traducción porque en Kubernetes no existe el arranque ordenado, y un Secret es base64, no cifrado. Y has visto el mismo aurora-api en los dos formatos: 28 líneas frente a 63, un objeto frente a tres, con la pregunta correcta encima —¿necesitas lo que compran esas líneas de más?—.
El árbol de decisión te da cinco criterios concretos y un detalle que conviene recordar: llegar a Kubernetes exige dos síes seguidos, conocimiento y presupuesto, y la peor opción de todas es un clúster autogestionado que nadie sabe reparar. Sabes que Compose en producción es una decisión perfectamente profesional cuando el host está bien montado —digests, límites, rotación de logs, copias probadas, TLS automático—, con sus dos huecos declarados: punto único de fallo y ausencia de autoescalado. Conoces los escalones intermedios: Swarm, honesto pero congelado; los servicios que ejecutan tu imagen sin que exista clúster alguno; y kompose, que te ahorra el 60 % del tecleo y nada del pensamiento.
Y te llevas la pregunta que ordena todo lo demás: si el clúster se rompe un sábado a las tres de la mañana, ¿quién lo arregla, en cuánto tiempo y cobrando qué?. Con las tres versiones de Aurora Libros —la tienda pequeña con Compose, la que crece con Kubernetes gestionado, la de campaña con autoescalado sin clúster— tienes tres respuestas de referencia y el hábito de escribir la decisión, con su fecha y su disparador de revisión, dentro del repositorio.
En la lección siguiente bajamos de la infraestructura al escritorio: Docker Desktop. Qué es exactamente esa VM Linux que llevas usando sin verla, por qué explica el rendimiento de los bind mounts, qué funciones aporta, qué condiciones de licencia tiene —y por qué conviene consultarlas antes de instalarlo en la empresa— y qué alternativas existen en cada sistema operativo.
Docker: De Principiante a Avanzado
Módulo 1: Introducción a Docker
- ¿Qué es Docker?
- Instalando Docker
- Arquitectura de Docker
- Comandos Básicos de Docker
- Entendiendo las Imágenes de Docker
- Creando tu Primer Contenedor Docker
- El Proyecto del Curso: la Plataforma Aurora Libros
Módulo 2: Trabajando con Imágenes Docker
- Docker Hub y Repositorios
- Construyendo Imágenes Docker
- Conceptos Básicos de Dockerfile
- Instrucciones Avanzadas del Dockerfile
- Gestionando Imágenes Docker
- Etiquetado y Publicación de Imágenes
Módulo 3: Contenedores Docker
- Ejecutando Contenedores
- Ciclo de Vida del Contenedor
- Gestionando Contenedores
- Inspección y Depuración de Contenedores
- Redes en Docker
- Persistencia de Datos con Volúmenes
- Límites de Recursos y Políticas de Reinicio
Módulo 4: Docker Compose
- Introducción a Docker Compose
- Definiendo Servicios en Docker Compose
- Comandos de Docker Compose
- Aplicaciones Multi-Contenedor
- Variables de Entorno en Docker Compose
- Perfiles, Overrides y Múltiples Entornos
- Desarrollo Local con Docker Compose
Módulo 5: Conceptos Avanzados de Docker
- Profundización en Redes Docker
- Opciones de Almacenamiento Docker
- Mejores Prácticas de Seguridad en Docker
- Optimizando Imágenes Docker
- Builds Avanzadas con BuildKit y Buildx
- Registro y Monitoreo en Docker
- El Runtime por Dentro: Namespaces, Cgroups y Capas
Módulo 6: Docker en Producción
- Preparar una Imagen para Producción
- CI/CD con Docker
- Orquestando Contenedores con Docker Swarm
- Introducción a Kubernetes
- Desplegando Contenedores Docker en Kubernetes
- Escalado y Balanceo de Carga
- Estrategias de Despliegue y Rollback
