Cerrábamos la lección anterior diciendo que ya tenías clúster, CLI y lenguaje de manifiestos, y que solo faltaba el paciente. Aquí está. Esta lección presenta a fondo Rutas Norte S.L., la empresa ficticia cuya plataforma de venta de billetes de autobús vamos a desplegar y operar sobre Kubernetes durante los once módulos restantes. No es un ejemplo decorativo: cada concepto que aprendas a partir de ahora entrará en escena porque Rutas Norte lo necesita, y al final del curso tendrás una plataforma completa, segura, observable y autoescalada, construida pieza a pieza. Verás la situación de partida de la empresa, cada uno de sus seis componentes, la arquitectura objetivo, el mapa de qué módulo aporta qué, y las convenciones que seguiremos siempre. Y terminarás con el primer despliegue real: tienda-web corriendo en tu clúster y respondiendo en tu navegador.

Contenido

  1. La empresa y su problema actual
  2. Los seis componentes de la plataforma
  3. Arquitectura objetivo sobre Kubernetes
  4. Hoja de ruta: qué aporta cada módulo
  5. Convenciones del proyecto
  6. Primer despliegue: tienda-web en rutas-norte-dev
  7. Por qué este pod es frágil

  1. La empresa y su problema actual

Rutas Norte S.L. es un operador de autobuses interurbanos que vende sus billetes por internet a través de la plataforma Rutas Norte. Trece personas en el departamento técnico, unas 4.000 reservas diarias de media y 25.000 en los días punta.

Su infraestructura actual:

  • Dos máquinas alquiladas con Docker Compose. Una sirve la web y la API; la otra, la base de datos.
  • Despliegues manuales: alguien se conecta por SSH, ejecuta docker compose pull && docker compose up -d y cruza los dedos. Hay un corte de servicio de entre uno y dos minutos, así que solo se despliega los martes de madrugada.
  • Sin autorreparación: en marzo, el contenedor de la API murió a las 02:40 por una fuga de memoria y nadie se dio cuenta hasta las 07:15. Cuatro horas y media sin vender billetes.
  • Sin elasticidad: en los picos de puentes y vacaciones el tráfico se multiplica por seis. La respuesta ha sido contratar una máquina dimensionada para agosto y pagarla los doce meses.
  • Secretos por todas partes: la contraseña de PostgreSQL vive en un fichero .env que se ha compartido por correo. Esa base de datos contiene nombre, DNI, teléfono y correo de cada cliente.
  • Sin observabilidad: los logs están en ficheros dentro de cada contenedor. Diagnosticar un problema implica entrar por SSH y buscar con grep.

La dirección técnica ha aprobado la migración a Kubernetes con cuatro objetivos medibles: cero cortes en los despliegues, recuperación automática de caídas, capacidad elástica en los picos y trazabilidad y control de acceso sobre los datos personales. Esos cuatro objetivos son, uno a uno, el temario que tienes por delante.

  1. Los seis componentes de la plataforma

Estos nombres te acompañarán durante todo el curso. Consérvalos exactamente así.

2.1. tienda-web

  • Qué es: el frontal público. Una SPA servida por nginx con los resultados de búsqueda, el selector de asientos y el proceso de pago.
  • Estado: sin estado. Cualquier réplica sirve cualquier petición.
  • Imagen: registry.rutasnorte.example/tienda-web:1.8.0
  • Dominio: www.rutasnorte.example
  • Qué necesita del clúster: varias réplicas, exposición HTTP/HTTPS al exterior, configuración inyectada (la URL de la API varía por entorno) y actualización sin corte.

2.2. api-reservas

  • Qué es: la API REST en Node.js. Consulta disponibilidad de plazas, calcula precios, crea reservas y emite billetes. Es el corazón del negocio.
  • Estado: sin estado. Toda la persistencia está en PostgreSQL y en Redis.
  • Imagen: registry.rutasnorte.example/api-reservas:2.4.0
  • Dominio: api.rutasnorte.example
  • Qué necesita del clúster: réplicas, autoescalado por carga, credenciales de base de datos como secreto, sondas de salud, límites de recursos y acceso restringido a la base de datos.

2.3. postgres-reservas

  • Qué es: PostgreSQL 16 con las tablas de reservas, clientes, rutas y expediciones.
  • Estado: con estado. Es el componente crítico del sistema.
  • Imagen: postgres:16
  • Qué necesita del clúster: almacenamiento persistente que sobreviva a la recreación del pod, identidad de red estable, credenciales gestionadas como secreto, copias de seguridad y una política de red que impida que cualquiera se conecte a él.

2.4. redis-cache

  • Qué es: caché en memoria de la disponibilidad de plazas por expedición y fecha. Absorbe la mayoría de las consultas de búsqueda y evita saturar PostgreSQL.
  • Estado: con estado, pero prescindible. Si se pierde, se repuebla desde la base de datos; solo se degrada el rendimiento durante unos minutos.
  • Imagen: redis:7.2-alpine
  • Qué necesita del clúster: un nombre estable, límites de memoria estrictos y configuración de política de expulsión.

2.5. worker-notificaciones

  • Qué es: proceso en segundo plano que consume una cola y envía los correos de confirmación de reserva y los recordatorios de viaje.
  • Estado: sin estado, pero no recibe tráfico entrante: no necesita ser accesible desde fuera.
  • Imagen: registry.rutasnorte.example/worker-notificaciones:1.2.0
  • Qué necesita del clúster: réplicas escaladas por longitud de cola y no por CPU, credenciales SMTP como secreto, y apagado ordenado para no perder un envío a medias.

2.6. informes-ocupacion

  • Qué es: tarea programada que cada noche a las 02:30 calcula la ocupación por línea y expedición del día anterior y deja un informe para el departamento comercial.
  • Estado: sin estado, de ejecución finita: arranca, trabaja unos minutos y termina.
  • Imagen: registry.rutasnorte.example/informes-ocupacion:1.0.3
  • Qué necesita del clúster: ejecución programada, control de reintentos, límite de ejecuciones simultáneas e historial de resultados.

Resumen comparativo

Componente Estado Tráfico entrante Objeto de Kubernetes que lo gobernará Módulo
tienda-web Sin estado Público (HTTP) Deployment + Service + Ingress 2, 4
api-reservas Sin estado Público (HTTP) e interno Deployment + Service + Ingress + HPA 2, 4, 9
postgres-reservas Con estado Solo interno, restringido StatefulSet + PVC + Service headless 5, 6
redis-cache Con estado prescindible Solo interno Deployment o StatefulSet + Service 2, 5
worker-notificaciones Sin estado Ninguno Deployment (sin Service) + KEDA 2, 9
informes-ocupacion Sin estado, finito Ninguno CronJob 6

Esa tabla contiene ya una lección de diseño: el tipo de objeto de Kubernetes que necesitas se deduce de dos preguntas —¿tiene estado? y ¿recibe tráfico?— antes de escribir una sola línea de YAML.

  1. Arquitectura objetivo sobre Kubernetes

Este es el destino al que llegaremos al final del módulo 11.

flowchart TB
    U["Clientes<br/>navegador y móvil"]
    DNS["DNS<br/>www / api .rutasnorte.example"]
    U --> DNS

    subgraph CL["Clúster de Kubernetes"]
        ING["Ingress Controller<br/>TLS con cert-manager"]

        subgraph NS["namespace rutas-norte-pro"]
            SVCW["Service tienda-web"]
            SVCA["Service api-reservas"]
            TW1["Pod tienda-web"]
            TW2["Pod tienda-web"]
            API1["Pod api-reservas"]
            API2["Pod api-reservas"]
            API3["Pod api-reservas"]
            SVCR["Service redis-cache"]
            RED["Pod redis-cache"]
            SVCP["Service postgres-reservas"]
            PG["StatefulSet<br/>postgres-reservas-0"]
            PVC[("PVC 20 GiB")]
            WK1["Pod worker-notificaciones"]
            WK2["Pod worker-notificaciones"]
            CJ["CronJob<br/>informes-ocupacion"]
        end
    end

    DNS --> ING
    ING --> SVCW
    ING --> SVCA
    SVCW --> TW1
    SVCW --> TW2
    SVCA --> API1
    SVCA --> API2
    SVCA --> API3
    API1 --> SVCR
    API2 --> SVCR
    API3 --> SVCP
    SVCR --> RED
    SVCP --> PG
    PG --- PVC
    WK1 --> SVCP
    WK2 --> SVCP
    CJ --> SVCP

Puntos que conviene leer en el diagrama:

  • Una sola puerta de entrada: el Ingress Controller termina el TLS y enruta por dominio hacia el Service correspondiente.
  • Ningún componente conoce IPs: todo se comunica por el nombre del Service, resuelto por el DNS interno del clúster.
  • postgres-reservas es el único con disco persistente, y solo debe ser alcanzable desde api-reservas, el worker y el CronJob. Eso se garantizará con una NetworkPolicy en el módulo 4.
  • worker-notificaciones no tiene Service: nadie le habla, él consume trabajo y sale a hablar con otros.

  1. Hoja de ruta: qué aporta cada módulo

Esta tabla es el contrato del curso: dice qué pieza de Rutas Norte añadimos o mejoramos en cada módulo.

Módulo Qué se añade o se mejora en Rutas Norte
1. Introducción Clúster de prácticas, convenciones, namespace rutas-norte-dev y primer pod de tienda-web
2. Componentes principales tienda-web y api-reservas como Deployments con réplicas; Services internos; actualización de versión sin corte y rollback; separación en namespaces por entorno
3. Configuración y secretos Configuración de la tienda y la API en ConfigMaps; contraseña de PostgreSQL y credenciales SMTP en Secrets; requests y limits en todos los componentes; cuotas por entorno; ServiceAccounts propias
4. Redes Publicación de www.rutasnorte.example y api.rutasnorte.example con Ingress y TLS; DNS interno entre componentes; NetworkPolicy que aísla postgres-reservas
5. Almacenamiento Volumen persistente para la base de datos vía PVC y StorageClass; expansión del volumen; copias de seguridad y restauración de las reservas
6. Conceptos avanzados postgres-reservas migrado a StatefulSet; informes-ocupacion como CronJob; init container que espera a la base de datos; sidecar de métricas; agente de logs como DaemonSet
7. Monitoreo y registro Sondas de vida y disponibilidad en todos los componentes; kubectl top; Prometheus y Grafana con panel de reservas por minuto; logs centralizados; depuración de incidencias
8. Seguridad RBAC por equipo y entorno; contenedores sin privilegios y con sistema de ficheros de solo lectura; Pod Security Standards; endurecimiento de red; firma y escaneo de imágenes
9. Escalado y rendimiento HPA sobre api-reservas para los picos de puentes; VPA para dimensionar; autoescalado de nodos; escalado de worker-notificaciones por longitud de cola con KEDA; PodDisruptionBudgets
10. Ecosistema Empaquetado de la plataforma con Helm; variantes por entorno con Kustomize; despliegue continuo con GitOps; traslado a un clúster gestionado
11. Casos reales Puesta en producción completa; pipeline de CI/CD; despliegue canary de una versión de la API; operación diaria, runbooks y control de costes
12. Certificación Repaso transversal orientado a CKA, CKAD y CKS usando la plataforma como banco de pruebas

  1. Convenciones del proyecto

Estas reglas se aplican en todas las lecciones. Adóptalas desde ahora; son también buenas prácticas del mundo real.

5.1. Namespaces por entorno

Namespace Uso Quién despliega
rutas-norte-dev Desarrollo. Datos ficticios, recursos reducidos Cualquier persona del equipo
rutas-norte-pre Preproducción. Réplica fiel de producción para validar Pipeline automático
rutas-norte-pro Producción. Datos reales de clientes Solo el pipeline, con aprobación

Durante el módulo 1 y buena parte del 2 trabajaremos solo en rutas-norte-dev.

5.2. Esquema de etiquetas

Todos los objetos llevan estas tres etiquetas como mínimo:

metadata:
  labels:
    app: tienda-web                          # el componente
    app.kubernetes.io/part-of: rutas-norte   # la plataforma completa
    entorno: dev                             # el entorno

Esto permite consultas como estas, que valen oro cuando el clúster crece:

kubectl get all -l app.kubernetes.io/part-of=rutas-norte -n rutas-norte-dev
kubectl get pods -l entorno=pro -A
kubectl logs -l app=api-reservas -n rutas-norte-dev --tail=50

5.3. Registro e imágenes: etiquetas inmutables

El registro de imágenes de la empresa es registry.rutasnorte.example. La regla más importante del proyecto en cuanto a imágenes:

Cada versión se publica con una etiqueta única e inmutable, que nunca se reutiliza. Prohibido latest en cualquier entorno.

Referencia ¿Válida? Por qué
registry.rutasnorte.example/api-reservas:2.4.0 Versión semántica, inmutable
registry.rutasnorte.example/api-reservas:2.4.0-a3f9c1b Sí, mejor Incluye el commit: trazabilidad total
registry.rutasnorte.example/api-reservas@sha256:9f3a1c... Sí, la más estricta Digest: imposible de suplantar
registry.rutasnorte.example/api-reservas:latest No Dos réplicas del mismo Deployment pueden acabar ejecutando código distinto; el rollback deja de ser fiable y no sabes qué hay en producción

5.4. Estructura del repositorio

La que definimos en la lección Objetos, Manifiestos YAML y el Modelo Declarativo:

rutas-norte/
└── k8s/
    ├── base/                # definición común
    ├── entornos/
    │   ├── dev/
    │   ├── pre/
    │   └── pro/
    ├── entorno-local/
    └── README.md

Y la regla de oro que la acompaña: si un cambio no está en Git, no existe. Nada de kubectl edit como método de despliegue.

  1. Primer despliegue: tienda-web en rutas-norte-dev

Suficiente teoría. Vamos a desplegar el primer trozo real de Rutas Norte.

Paso 1: comprobar el clúster

minikube status --profile=rutas-norte
kubectl config current-context
rutas-norte
type: Control Plane
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

rutas-norte

Paso 2: crear el namespace de forma declarativa

# k8s/base/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: rutas-norte-dev
  labels:
    app.kubernetes.io/part-of: rutas-norte
    entorno: dev
kubectl apply -f k8s/base/namespace.yaml
kubectl get namespace rutas-norte-dev
namespace/rutas-norte-dev created

NAME              STATUS   AGE
rutas-norte-dev   Active   3s

Fíjate en que lo hemos creado con un manifiesto, no con kubectl create namespace. Es una decisión deliberada: el namespace forma parte de la definición del proyecto y debe estar en Git como todo lo demás.

Y ahora, el consejo de productividad de la lección 01-05, que te ahorrará escribir -n rutas-norte-dev cientos de veces:

kubectl config set-context --current --namespace=rutas-norte-dev

Paso 3: escribir el manifiesto del pod

# k8s/base/tienda-web-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: tienda-web
  namespace: rutas-norte-dev
  labels:
    app: tienda-web
    app.kubernetes.io/part-of: rutas-norte
    entorno: dev
  annotations:
    rutasnorte.example/responsable: [email protected]
spec:
  containers:
    - name: nginx
      image: nginx:1.27-alpine
      ports:
        - name: http
          containerPort: 80
      resources:
        requests:
          cpu: "50m"
          memory: "64Mi"
        limits:
          cpu: "200m"
          memory: "128Mi"

Repaso campo a campo, apoyándonos en lo aprendido:

  • apiVersion: v1 y kind: Pod: el Pod pertenece al grupo core, por eso no lleva prefijo de grupo.
  • metadata.name: nombre único dentro del namespace.
  • metadata.labels: las tres etiquetas del proyecto. En el módulo 2 serán las que use el Service para encontrar sus pods.
  • metadata.annotations: metadato informativo, no consultable.
  • spec.containers: una lista. Aquí un solo elemento, como será habitual.
  • image: nginx:1.27-alpine: usamos la imagen pública nginx porque registry.rutasnorte.example es un registro ficticio y no existe. En todo el curso, cuando aparezca una imagen de registry.rutasnorte.example, sustitúyela mentalmente —y en tu clúster— por su equivalente público. Etiqueta explícita, nunca latest, según la convención del proyecto.
  • ports: es documentación informativa; no abre nada por sí sola. La exposición real vendrá con el Service (módulo 2).
  • resources: requests es lo que el scheduler reserva para decidir en qué nodo cabe; limits es el techo que el kubelet impone. Los estudiaremos a fondo en el módulo 3, pero conviene ponerlos desde el primer día.

Paso 4: validar y aplicar

kubectl apply -f k8s/base/tienda-web-pod.yaml --dry-run=client
kubectl apply -f k8s/base/tienda-web-pod.yaml
pod/tienda-web created (dry run)
pod/tienda-web created

Paso 5: observar el arranque

kubectl get pods -w
NAME         READY   STATUS              RESTARTS   AGE
tienda-web   0/1     Pending             0          0s
tienda-web   0/1     ContainerCreating   0          1s
tienda-web   1/1     Running             0          4s

Estás viendo, en directo, el recorrido de la lección Arquitectura de Kubernetes: Pending mientras el scheduler elige nodo, ContainerCreating mientras el kubelet descarga la imagen y prepara la red, Running cuando el contenedor está vivo. Corta con Ctrl+C.

Paso 6: inspeccionar

kubectl get pod tienda-web -o wide
NAME         READY   STATUS    RESTARTS   AGE   IP           NODE          NOMINATED NODE
tienda-web   1/1     Running   0          45s   10.244.0.21  rutas-norte   <none>
kubectl describe pod tienda-web
Name:         tienda-web
Namespace:    rutas-norte-dev
Node:         rutas-norte/192.168.49.2
Labels:       app=tienda-web
              app.kubernetes.io/part-of=rutas-norte
              entorno=dev
Status:       Running
IP:           10.244.0.21
Containers:
  nginx:
    Image:          nginx:1.27-alpine
    Port:           80/TCP
    State:          Running
    Ready:          True
    Restart Count:  0
    Limits:         cpu: 200m, memory: 128Mi
    Requests:       cpu: 50m, memory: 64Mi
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  50s   default-scheduler  Successfully assigned rutas-norte-dev/tienda-web to rutas-norte
  Normal  Pulled     49s   kubelet            Container image "nginx:1.27-alpine" already present on machine
  Normal  Created    49s   kubelet            Created container nginx
  Normal  Started    49s   kubelet            Started container nginx

Los cuatro eventos cuentan la historia completa: el scheduler asignó nodo, y después el kubelet obtuvo la imagen, creó el contenedor y lo arrancó. Exactamente el reparto de responsabilidades que estudiamos.

kubectl logs tienda-web --tail=5
/docker-entrypoint.sh: Configuration complete; ready for start up
2026/08/05 10:22:14 [notice] 1#1: nginx/1.27.0
2026/08/05 10:22:14 [notice] 1#1: start worker processes

Paso 7: verlo en el navegador

El pod tiene IP 10.244.0.21, pero esa IP solo existe dentro del clúster. Para alcanzarla desde tu máquina usamos port-forward:

kubectl port-forward pod/tienda-web 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

Abre http://localhost:8080 en el navegador: verás la página de bienvenida de nginx. En Rutas Norte, esa sería la portada de la tienda de billetes. Deja el comando corriendo mientras pruebas y córtalo con Ctrl+C.

Desde otra terminal también puedes comprobarlo sin navegador:

curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080
200

Enhorabuena: acabas de desplegar el primer componente de Rutas Norte en Kubernetes.

  1. Por qué este pod es frágil

Antes de celebrarlo demasiado, hagamos el experimento que da sentido al módulo 2.

kubectl delete pod tienda-web
kubectl get pods
pod "tienda-web" deleted

No resources found in rutas-norte-dev namespace.

Ha desaparecido y no vuelve. Compáralo con lo que dijimos en la lección de conceptos: un pod suelto no tiene ningún controlador que lo vigile. Nadie ejecuta un bucle de reconciliación sobre él, porque el estado deseado que declaraste era literalmente "que exista este pod", y al borrarlo también borraste esa declaración.

Los cuatro problemas de un pod suelto:

Problema Consecuencia para Rutas Norte
No se recrea si muere Reproduce exactamente la caída nocturna de cuatro horas y media que sufrió la empresa en marzo
No se puede escalar Para tener tres copias habría que escribir tres manifiestos con tres nombres distintos
No se puede actualizar sin corte Cambiar de imagen exige borrar y recrear: hay un intervalo sin servicio
No tiene dirección estable Cada recreación cambia la IP, y nadie sabe a dónde conectarse

La solución es la jerarquía que ya conoces del mapa conceptual: Deployment → ReplicaSet → Pod. Un Deployment declara "quiero 3 réplicas de esta imagen", el ReplicaSet las mantiene contra viento y marea, y un Service les da un nombre estable.

Vuelve a crear el pod para dejar el clúster como estaba y cerrar el módulo con la plataforma en marcha:

kubectl apply -f k8s/base/tienda-web-pod.yaml
kubectl get pods
pod/tienda-web created

NAME         READY   STATUS    RESTARTS   AGE
tienda-web   1/1     Running   0          5s

Errores Comunes y Consejos

  • Aplicar en el namespace equivocado. Si kubectl get pods no muestra nada, comprueba el namespace con kubectl config view --minify | grep namespace o usa -A. Declarar metadata.namespace explícitamente en cada manifiesto, como hacemos aquí, elimina el problema de raíz.
  • Usar registry.rutasnorte.example literalmente. Es un registro ficticio: no existe. El pod se quedaría en ImagePullBackOff. En los ejercicios prácticos usa siempre la imagen pública equivalente (nginx, postgres, redis, node).
  • Creer que ports.containerPort expone el pod. No hace nada por sí solo: es documentación. El acceso real llega con port-forward (para pruebas), Service (módulo 2) o Ingress (módulo 4).
  • Dejar port-forward como solución. Es una herramienta de depuración de un solo usuario, atada a tu terminal. Nunca es una forma de exponer un servicio.
  • Crear pods sueltos y acostumbrarse. Este ha sido un ejercicio deliberado para entender la fragilidad. A partir del módulo 2, todo va en Deployments.
  • Olvidar resources. Sin requests, el scheduler no puede planificar bien; sin limits, un contenedor descontrolado puede tumbar a sus vecinos. Ponlos desde el principio aunque los ajustes vengan después.
  • Consejo: crea ya el repositorio con la estructura k8s/ y haz un commit con namespace.yaml y tienda-web-pod.yaml. Trabajar así desde la primera lección es la diferencia entre terminar el curso con una plataforma reproducible y terminar con un clúster lleno de cosas que nadie sabe cómo se crearon.

Ejercicios

Ejercicio 1: Diseñar la plataforma sobre el papel

Sin escribir ningún manifiesto, completa una tabla con los seis componentes de Rutas Norte y, para cada uno, responde: (a) ¿tiene estado?, (b) ¿recibe tráfico entrante y de dónde?, (c) ¿qué objeto de Kubernetes lo gobernará?, y (d) ¿qué pasaría hoy, con Docker Compose, si la máquina que lo aloja se apagara? Después, justifica en tres líneas por qué redis-cache y postgres-reservas, siendo ambos "con estado", reciben tratamientos muy distintos.

Ejercicio 2: Desplegar y explorar redis-cache

Siguiendo todas las convenciones del proyecto, crea el manifiesto k8s/base/redis-cache-pod.yaml para un pod redis-cache en rutas-norte-dev con la imagen pública redis:7.2-alpine, el puerto 6379, las tres etiquetas del proyecto y requests de 50m de CPU y 64Mi de memoria. Después:

  1. Valídalo sin tocar el clúster y aplícalo.
  2. Comprueba que está Running y averigua su IP y su nodo.
  3. Entra en el contenedor y comprueba que Redis responde ejecutando redis-cli ping.
  4. Muestra únicamente los pods de la plataforma Rutas Norte usando la etiqueta común.

Ejercicio 3: Demostrar la fragilidad y anticipar la solución

  1. Simula una caída de la aplicación matando el proceso principal del contenedor de tienda-web con kubectl exec, y observa con --watch qué ocurre. ¿Vuelve el contenedor? ¿Cambia el contador RESTARTS? ¿Cambia la IP del pod?
  2. Ahora borra el pod entero con kubectl delete pod y observa de nuevo. ¿Vuelve?
  3. Explica la diferencia entre los dos casos indicando qué componente actúa en cada uno.
  4. Escribe en dos frases qué objeto necesitarías para que el segundo caso también se recuperase solo.

Soluciones

Solución 1

Componente (a) Estado (b) Tráfico entrante (c) Objeto (d) Si cae la máquina hoy
tienda-web No Público, desde internet Deployment + Service + Ingress La web deja de responder por completo
api-reservas No Público e interno Deployment + Service + Ingress + HPA No se pueden consultar ni crear reservas
postgres-reservas Interno, restringido StatefulSet + PVC Parada total y riesgo de pérdida de datos si el disco no está replicado
redis-cache Sí, prescindible Interno Deployment o StatefulSet + Service Degradación de rendimiento; el servicio sigue con consultas directas a la base de datos
worker-notificaciones No Ninguno Deployment (sin Service) Los correos de confirmación se acumulan sin enviarse
informes-ocupacion No, finito Ninguno CronJob El informe nocturno no se genera

redis-cache y postgres-reservas reciben tratamientos distintos porque el estado de Redis es reconstruible: si se pierde, se repuebla desde la base de datos y solo se degrada el rendimiento. El estado de PostgreSQL es la fuente de verdad del negocio: perderlo significa perder reservas y datos personales de clientes. Por eso PostgreSQL exige almacenamiento persistente, identidad estable, copias de seguridad y aislamiento de red, mientras que Redis solo necesita un límite de memoria y una política de expulsión.

Solución 2

# k8s/base/redis-cache-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: redis-cache
  namespace: rutas-norte-dev
  labels:
    app: redis-cache
    app.kubernetes.io/part-of: rutas-norte
    entorno: dev
spec:
  containers:
    - name: redis
      image: redis:7.2-alpine
      ports:
        - name: redis
          containerPort: 6379
      resources:
        requests:
          cpu: "50m"
          memory: "64Mi"
        limits:
          cpu: "200m"
          memory: "256Mi"
kubectl apply -f k8s/base/redis-cache-pod.yaml --dry-run=client
kubectl apply -f k8s/base/redis-cache-pod.yaml
kubectl get pod redis-cache -o wide
kubectl exec -it redis-cache -- redis-cli ping
kubectl get pods -l app.kubernetes.io/part-of=rutas-norte
PONG

NAME          READY   STATUS    RESTARTS   AGE
redis-cache   1/1     Running   0          32s
tienda-web    1/1     Running   0          14m

Solución 3

# 1. Matar el proceso dentro del contenedor
kubectl get pods -w &
kubectl exec tienda-web -- kill 1
tienda-web   1/1   Running   0          15m
tienda-web   0/1   Error     0          15m
tienda-web   1/1   Running   1 (2s ago) 15m

El contenedor sí vuelve: RESTARTS pasa de 0 a 1 y la IP del pod no cambia, porque es el mismo pod. Quien lo recupera es el kubelet, aplicando la restartPolicy: Always que un pod tiene por defecto. El kubelet reinicia contenedores dentro de un pod existente.

# 2. Borrar el pod entero
kubectl delete pod tienda-web
kubectl get pods
No resources found in rutas-norte-dev namespace.

El pod no vuelve. No hay ningún controlador que vigile su existencia.

  1. La diferencia está en quién actúa y sobre qué: el kubelet vigila los contenedores dentro de los pods que tiene asignados y los reinicia si mueren, pero no puede recrear un pod que ya no existe como objeto en la API. Recrear pods es trabajo de un controlador del kube-controller-manager, y un pod suelto no tiene ninguno asociado: al borrarlo, borraste el propio estado deseado.

  2. Necesitas un Deployment, que crea un ReplicaSet cuyo controlador mantiene permanentemente el número de réplicas declarado. Con él, el estado deseado deja de ser "que exista este pod concreto" y pasa a ser "que existan N pods con estas características", de modo que borrar uno provoca inmediatamente la creación de otro.

Conclusión

Ya conoces el proyecto que da sentido a todo el curso. Rutas Norte S.L. es una empresa con problemas absolutamente reales —caídas nocturnas sin respuesta, picos de demanda en puentes y vacaciones, despliegues con corte de servicio y datos personales de clientes mal protegidos— y su plataforma se compone de seis piezas con necesidades bien diferenciadas: dos frontales sin estado, una base de datos crítica con estado, una caché prescindible, un worker sin tráfico entrante y una tarea nocturna programada. Tienes la arquitectura objetivo, el mapa de qué módulo aporta qué, y las convenciones —namespaces por entorno, esquema de etiquetas, etiquetas de imagen inmutables y estructura k8s/ versionada en Git— que aplicaremos sin excepción.

Y, sobre todo, ya has desplegado tu primer componente: tienda-web corriendo en rutas-norte-dev, inspeccionado con get, describe y logs, y visible en tu navegador mediante port-forward. También has comprobado en primera persona su punto débil: un pod suelto que, al borrarse, no vuelve nunca.

Con esto cierras el módulo 1. Sabes qué es Kubernetes, cómo está construido, qué significa cada término, tienes un clúster funcionando, dominas kubectl y entiendes el modelo declarativo. El módulo 2, Componentes Principales de Kubernetes, arranca justo donde termina esta lección: convertiremos ese pod frágil en un Deployment con varias réplicas que se recupera solo, aprenderemos primero qué es exactamente un Pod por dentro y cómo un ReplicaSet lo mantiene con vida, y le daremos a tienda-web y a api-reservas una dirección estable con Servicios. La plataforma Rutas Norte empieza a tomar forma de verdad.

Curso de Kubernetes

Módulo 1: Introducción a Kubernetes

Módulo 2: Componentes Principales de Kubernetes

Módulo 3: Gestión de Configuración y Secretos

Módulo 4: Redes en Kubernetes

Módulo 5: Almacenamiento en Kubernetes

Módulo 6: Conceptos Avanzados de Kubernetes

Módulo 7: Monitoreo y Registro

Módulo 8: Seguridad en Kubernetes

Módulo 9: Escalado y Rendimiento

Módulo 10: Ecosistema y Herramientas de Kubernetes

Módulo 11: Estudios de Caso y Aplicaciones del Mundo Real

Módulo 12: Preparación para la Certificación de Kubernetes

© Copyright 2026. Todos los derechos reservados