Esta es la última lección del curso y funciona de forma distinta a todas las anteriores: no se lee, se hace. Es un examen de prácticas completo, con quince tareas cronometradas al estilo de las certificaciones, sobre la plataforma Rutas Norte que has construido a lo largo de los once módulos anteriores. Tiene su puntuación, su solucionario y una tabla de autoevaluación que traduce tu resultado en un veredicto claro: listo para reservar fecha, repasar dos dominios concretos, o volver a determinados módulos.
La regla más importante: haz el simulacro antes de leer las soluciones. El valor de esta lección no está en el solucionario —está en descubrir, con un cronómetro corriendo, qué sabes hacer sin ayuda y qué no. Leerlo primero convierte un diagnóstico en una lectura agradable y sin utilidad.
Prepara el clúster, pon el cronómetro a 120 minutos y empieza.
Contenido
- Instrucciones del simulacro
- Preparación del entorno
- Las quince tareas
- Reparto del tiempo y estrategia
- Instrucciones del simulacro
1.1 Reglas
Estas reglas replican las condiciones reales del examen. Respetarlas es lo que hace que el resultado signifique algo.
| Regla | Detalle |
|---|---|
| Tiempo total | 120 minutos, cronometrados desde la primera tarea |
| Sin pausas | Nada de parar el reloj. Ve al baño antes. |
| Documentación | Solo kubernetes.io/docs. Una pestaña. Nada más. |
| Prohibido | Buscadores, foros, GitHub, notas propias, esta lección, herramientas de IA |
| Editor | vim o nano en el terminal. No un IDE gráfico. |
| Móvil | En otra habitación |
| Puntuación | 100 puntos repartidos entre las 15 tareas |
| Corrección | Al final, con el solucionario. Nunca durante. |
1.2 Cómo puntuar
Cada tarea indica su criterio de aceptación. Puntúa así:
- Puntuación completa si se cumple todo el criterio.
- Puntuación parcial (la mitad) si se cumple una parte sustancial pero falta algún requisito.
- Cero si el objeto no existe, está en el namespace equivocado o no funciona.
Sé honesto contigo mismo. Inflar la nota solo te perjudica.
1.3 Qué anotar mientras trabajas
Ten un fichero de notas abierto (o papel, aquí sí vale) y registra para cada tarea:
T1 5p ✔ 3 min
T2 7p ½ 9 min - no me salió el Ingress a la primera
T3 6p ✔ 4 min
T4 8p ⏸ saltada - PVC en Pending y no supe por qué
...Esta información es tan valiosa como la nota final: te dice dónde se te va el tiempo.
- Preparación del entorno
2.1 Opción recomendada: kind con tres nodos
kind levanta un clúster multinodo en un minuto y soporta casi todo lo que necesita el simulacro. Necesitas Docker o Podman instalado.
# kind-simulacro.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30080
hostPort: 30080
- role: worker
- role: worker
networking:
disableDefaultCNI: true # instalaremos Calico para NetworkPolicieskind create cluster --name simulacro --config kind-simulacro.yaml
# CNI con soporte de NetworkPolicies (imprescindible para la tarea 9)
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/calico.yaml
# Esperar a que todos los nodos estén Ready
kubectl wait --for=condition=Ready nodes --all --timeout=300s
kubectl get nodesNAME STATUS ROLES AGE VERSION
simulacro-control-plane Ready control-plane 2m v1.30.0
simulacro-worker Ready <none> 2m v1.30.0
simulacro-worker2 Ready <none> 2m v1.30.02.2 Alternativa: minikube
minikube start --nodes=3 --cni=calico --kubernetes-version=v1.30.0
minikube addons enable ingress
minikube addons enable metrics-serverMinikube tiene la ventaja de traer el controlador de Ingress y el metrics-server como addons, que en kind hay que instalar aparte.
2.3 Componentes adicionales
Para que todas las tareas sean resolubles, instala esto antes de arrancar el cronómetro:
# Controlador de Ingress (necesario para la tarea 2)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.2/deploy/static/provider/kind/deploy.yaml
kubectl wait --namespace ingress-nginx --for=condition=ready pod \
--selector=app.kubernetes.io/component=controller --timeout=180s
# metrics-server (necesario para la tarea 11)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
kubectl patch deployment metrics-server -n kube-system --type='json' \
-p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'2.4 Script de preparación (ejecútalo antes del cronómetro)
Este script crea los namespaces, las cargas de partida y las tres averías de las tareas de resolución de problemas. Guárdalo como preparar-simulacro.sh y ejecútalo. No lo leas con detalle: las averías deben ser una sorpresa.
#!/bin/bash
set -e
for ns in rutas-norte-dev rutas-norte-pre rutas-norte-pro; do
kubectl create namespace $ns --dry-run=client -o yaml | kubectl apply -f -
done
# Etiquetas de nodo para la tarea 5
kubectl label node simulacro-worker2 tipo=batch --overwrite
kubectl taint node simulacro-worker2 dedicado=batch:NoSchedule --overwrite
# Carga de partida para las tareas 7, 11 y 12
kubectl create deployment api-reservas --image=nginx:1.27-alpine \
--replicas=2 -n rutas-norte-pre
kubectl create deployment tienda-web --image=nginx:1.27-alpine \
--replicas=3 -n rutas-norte-pro
# --- AVERÍA 1 (tarea 13) ---
kubectl create deployment worker-notificaciones --image=busybox:1.36 \
-n rutas-norte-dev
kubectl set env deployment/worker-notificaciones -n rutas-norte-dev \
--from=secret/credenciales-cola --prefix=COLA_ 2>/dev/null || \
kubectl patch deployment worker-notificaciones -n rutas-norte-dev --type='json' \
-p='[{"op":"add","path":"/spec/template/spec/containers/0/envFrom",
"value":[{"secretRef":{"name":"credenciales-cola"}}]}]'
# --- AVERÍA 2 (tarea 14) ---
kubectl create deployment redis-cache --image=redis:7-alpine -n rutas-norte-pre
kubectl expose deployment redis-cache --name=redis-cache-svc --port=6379 \
-n rutas-norte-pre
kubectl patch svc redis-cache-svc -n rutas-norte-pre \
-p '{"spec":{"selector":{"app":"redis"}}}'
# --- AVERÍA 3 (tarea 15) ---
kubectl run informes-ocupacion --image=busybox:1.36 -n rutas-norte-pro \
--overrides='{"spec":{"nodeSelector":{"disco":"nvme"}}}' \
--command -- sleep 3600
echo "Entorno preparado. Arranca el cronómetro."2.5 Ahora sí: arranca el cronómetro
Antes de leer la tarea 1, teclea tu bloque de arranque (lección 12-04):
alias k=kubectl
export do='--dry-run=client -o yaml'
export now='--force --grace-period=0'
source <(kubectl completion bash)
complete -o default -F __start_kubectl k
cat <<'EOF' > ~/.vimrc
set number
set expandtab
set tabstop=2
set shiftwidth=2
set softtabstop=2
set autoindent
set paste
EOF120 minutos. Empieza.
- Las quince tareas
Tarea 1 — Despliegue básico y escalado
Namespace: rutas-norte-dev · Peso: 5 puntos · Objetivo: 4 min
Crea un Deployment llamado tienda-web con la imagen nginx:1.27-alpine y 4 réplicas. Todos sus pods deben llevar la etiqueta entorno=dev además de las que ponga el controlador. Después, escálalo a 6 réplicas.
Criterio de aceptación: kubectl get deploy tienda-web -n rutas-norte-dev muestra 6/6 y todos los pods tienen la etiqueta entorno=dev.
Tarea 2 — Exposición: Service e Ingress
Namespace: rutas-norte-dev · Peso: 7 puntos · Objetivo: 8 min
Expón el Deployment tienda-web de la tarea anterior con un Service ClusterIP llamado tienda-web-svc en el puerto 80. Crea además un Ingress tienda-ingress con la IngressClass nginx que enrute el host www.rutasnorte.es, ruta /, hacia ese Service en el puerto 80.
Criterio de aceptación: el Service tiene endpoints poblados y kubectl describe ingress tienda-ingress muestra la regla con el backend correcto.
Tarea 3 — Configuración y secretos
Namespace: rutas-norte-dev · Peso: 6 puntos · Objetivo: 6 min
Crea:
- Un ConfigMap
config-apicon las clavesnivel_log=infoymax_conexiones=100. - Un Secret
credenciales-postgresconusuario=reservasypassword=N0rt3-2026.
Crea un Pod api-reservas con la imagen nginx:1.27-alpine que reciba todas las claves del ConfigMap como variables de entorno y monte el Secret como ficheros en /etc/secretos en modo solo lectura.
Criterio de aceptación: kubectl exec api-reservas -- env | grep nivel_log devuelve nivel_log=info y kubectl exec api-reservas -- cat /etc/secretos/usuario devuelve reservas.
Tarea 4 — Almacenamiento persistente
Namespace: rutas-norte-pre · Peso: 8 puntos · Objetivo: 9 min
Crea un PersistentVolume pv-reservas de 3Gi, modo de acceso ReadWriteOnce, storageClassName: manual, política de reclamación Retain, con hostPath en /mnt/datos-reservas.
Crea un PersistentVolumeClaim pvc-reservas de 2Gi que se enlace a ese PV, y un Pod postgres-reservas con imagen busybox:1.36 que ejecute sleep 3600 y monte el volumen en /var/lib/datos.
Criterio de aceptación: el PVC aparece como Bound al PV pv-reservas y el pod está Running con el volumen montado.
Tarea 5 — Planificación con taints y etiquetas de nodo
Namespace: rutas-norte-pro · Peso: 6 puntos · Objetivo: 6 min
El nodo simulacro-worker2 tiene el taint dedicado=batch:NoSchedule y la etiqueta tipo=batch.
Crea un Deployment worker-notificaciones con imagen busybox:1.36, comando sleep 3600, 2 réplicas, que se ejecute exclusivamente en ese nodo.
Criterio de aceptación: los 2 pods están Running en simulacro-worker2 (compruébalo con -o wide).
Tarea 6 — Trabajo programado
Namespace: rutas-norte-pro · Peso: 5 puntos · Objetivo: 5 min
Crea un CronJob informes-nocturnos que se ejecute cada día a las 2:30, con imagen busybox:1.36 y comando echo informe de ocupacion generado. Requisitos: no debe permitir ejecuciones solapadas, cada Job puede reintentar como máximo 2 veces, y solo se conservan 3 Jobs exitosos en el historial.
Criterio de aceptación: el CronJob existe con schedule: "30 2 * * *", concurrencyPolicy: Forbid, backoffLimit: 2 y successfulJobsHistoryLimit: 3. Una ejecución manual produce la salida esperada.
Tarea 7 — Sondas y calidad de servicio
Namespace: rutas-norte-pre · Peso: 7 puntos · Objetivo: 7 min
El Deployment api-reservas ya existe. Modifícalo para que:
- No reciba tráfico hasta que responda
GET /en el puerto 80 (readinessProbe). - Se reinicie si
GET /falla 3 veces seguidas (livenessProbe). - Tenga clase de QoS
Guaranteedcon 200m de CPU y 256Mi de memoria.
Criterio de aceptación: kubectl describe pod -l app=api-reservas -n rutas-norte-pre muestra QoS Class: Guaranteed y ambas sondas configuradas.
Tarea 8 — RBAC y ServiceAccount
Namespace: rutas-norte-pro · Peso: 7 puntos · Objetivo: 7 min
Crea la ServiceAccount soporte-n1 en rutas-norte-pro. Concédele permiso para listar y ver pods y leer sus logs, únicamente en ese namespace. No debe poder borrar nada ni acceder a otros namespaces.
Asigna esa ServiceAccount al Deployment tienda-web de rutas-norte-pro.
Criterio de aceptación: kubectl auth can-i list pods --as=system:serviceaccount:rutas-norte-pro:soporte-n1 -n rutas-norte-pro devuelve yes; la misma consulta con delete o en otro namespace devuelve no.
Tarea 9 — Política de red
Namespace: rutas-norte-pro · Peso: 7 puntos · Objetivo: 8 min
Crea en rutas-norte-pro una NetworkPolicy llamada denegar-todo-entrada que deniegue todo el tráfico de entrada a todos los pods del namespace.
Añade una segunda política permitir-tienda-desde-ingress que permita el tráfico de entrada al puerto 80 de los pods con etiqueta app=tienda-web únicamente desde pods del namespace ingress-nginx.
Criterio de aceptación: ambas políticas existen; un pod de prueba dentro de rutas-norte-pro no puede alcanzar a tienda-web en el puerto 80.
Tarea 10 — Endurecimiento y admisión
Namespace: rutas-norte-pre · Peso: 7 puntos · Objetivo: 7 min
Etiqueta el namespace rutas-norte-pre para que aplique el estándar restricted de Pod Security Admission en modo enforce, con la versión v1.30.
Después, crea un Pod auditor-seguro (imagen busybox:1.36, comando sleep 3600) que cumpla ese estándar: usuario no root, sin escalada de privilegios, todas las capacidades descartadas y perfil seccomp por defecto.
Criterio de aceptación: el namespace tiene la etiqueta de enforce; el pod auditor-seguro está Running; un kubectl run inseguro --image=nginx -n rutas-norte-pre es rechazado.
Tarea 11 — Autoescalado
Namespace: rutas-norte-pre · Peso: 5 puntos · Objetivo: 4 min
Crea un HorizontalPodAutoscaler para el Deployment api-reservas que mantenga entre 2 y 8 réplicas, con un objetivo de utilización de CPU del 65 %.
Criterio de aceptación: kubectl get hpa -n rutas-norte-pre muestra el HPA con MINPODS 2, MAXPODS 8 y el objetivo del 65 %, y con métricas leídas (no <unknown>).
Tarea 12 — Actualización y reversión
Namespace: rutas-norte-pro · Peso: 6 puntos · Objetivo: 6 min
Sobre el Deployment tienda-web de rutas-norte-pro:
- Configura la estrategia para que ningún pod deje de estar disponible durante la actualización (
maxUnavailable: 0,maxSurge: 1). - Actualiza la imagen a
nginx:1.27.2-alpiney anota la causa del cambio comosubida a 1.27.2. - Espera a que termine el despliegue y después revierte a la revisión anterior.
Criterio de aceptación: kubectl rollout history muestra al menos dos revisiones con la causa anotada, y la imagen final vuelve a ser nginx:1.27-alpine.
Tarea 13 — Resolución de problemas: el worker que no arranca
Namespace: rutas-norte-dev · Peso: 8 puntos · Objetivo: 8 min
El Deployment worker-notificaciones de rutas-norte-dev no consigue arrancar ningún pod. Encuentra la causa, corrígela sin cambiar la imagen ni la configuración del contenedor, y deja el Deployment con al menos un pod en Running.
Escribe en /opt/diagnostico-13.txt una línea explicando la causa raíz.
Criterio de aceptación: el Deployment tiene 1/1 pods listos y el fichero de diagnóstico existe y no está vacío.
Tarea 14 — Resolución de problemas: el servicio que no responde
Namespace: rutas-norte-pre · Peso: 8 puntos · Objetivo: 8 min
El Service redis-cache-svc de rutas-norte-pre no está sirviendo tráfico: las aplicaciones que lo llaman reciben un rechazo de conexión, aunque los pods de redis-cache están Running. Encuentra la causa y arréglala sin modificar los pods.
Escribe en /opt/diagnostico-14.txt una línea explicando la causa raíz.
Criterio de aceptación: kubectl get endpoints redis-cache-svc -n rutas-norte-pre muestra al menos una dirección IP.
Tarea 15 — Resolución de problemas: el pod que nunca se planifica
Namespace: rutas-norte-pro · Peso: 8 puntos · Objetivo: 8 min
El Pod informes-ocupacion de rutas-norte-pro lleva minutos en estado Pending. Averigua por qué y consíguelo poner en Running. Puedes elegir entre corregir el pod o adaptar el clúster; justifica tu decisión.
Escribe en /opt/diagnostico-15.txt una línea explicando la causa raíz y la solución elegida.
Criterio de aceptación: el pod informes-ocupacion está en Running.
- Reparto del tiempo y estrategia
4.1 Tiempos objetivo
| Tarea | Tema | Puntos | Objetivo | Acumulado |
|---|---|---|---|---|
| 1 | Deployment y escalado | 5 | 4 min | 4 |
| 2 | Service e Ingress | 7 | 8 min | 12 |
| 3 | ConfigMap y Secret | 6 | 6 min | 18 |
| 4 | PV, PVC y Pod | 8 | 9 min | 27 |
| 5 | Taints y nodeSelector | 6 | 6 min | 33 |
| 6 | CronJob | 5 | 5 min | 38 |
| 7 | Sondas y QoS | 7 | 7 min | 45 |
| 8 | RBAC | 7 | 7 min | 52 |
| 9 | NetworkPolicy | 7 | 8 min | 60 |
| 10 | Pod Security | 7 | 7 min | 67 |
| 11 | HPA | 5 | 4 min | 71 |
| 12 | Rollout y rollback | 6 | 6 min | 77 |
| 13 | TS: worker caído | 8 | 8 min | 85 |
| 14 | TS: Service sin endpoints | 8 | 8 min | 93 |
| 15 | TS: pod Pending | 8 | 8 min | 101 |
| — | Revisión final | — | ~19 min | 120 |
Total: 100 puntos en 101 minutos de trabajo, con 19 de margen. Ese margen es deliberado: en el examen real se consume en dudas, tareas que se alargan y la revisión.
4.2 Estrategia sugerida
Aplicando lo aprendido en la lección 12-04:
Min 0-4 Bloque de arranque + lectura de las 15 tareas
Min 4-30 Fase 1 (baratas y rápidas): T1, T11, T6, T3, T12 → 27 puntos
Min 30-85 Fase 2 (bloque central por peso): T4, T13, T14, T15, T9, T8, T10, T7, T2, T5
Min 85-105 Fase 3: pendientes y medio hechas
Min 105-120 Revisión: contexto, namespace y funcionamiento de cada objetoSi te ciñes al orden numérico también funciona, pero fíjate en que las tareas 11 y 6 valen 10 puntos entre las dos y se resuelven en 9 minutos: son las de mejor rendimiento del simulacro.
4.3 Recordatorio antes de empezar
- Fija el namespace al empezar cada tarea, o usa
-nen todos los comandos. - Verifica cada tarea antes de pasar a la siguiente.
- Si una tarea supera su objetivo en más del 50 %, márcala y salta.
- Nunca dejes una tarea completamente vacía: hay puntuación parcial.
Cuando termines, no leas todavía el solucionario. Primero haz los ejercicios integradores de la sección siguiente si te quedan fuerzas, o descansa y vuelve más tarde.
Errores Comunes y Consejos
Estos son los fallos que más se repiten en este simulacro concreto.
| Error | Tarea afectada | Cómo evitarlo |
|---|---|---|
| Crear el Deployment sin la etiqueta pedida en los pods (solo en el Deployment) | 1 | La etiqueta va en spec.template.metadata.labels |
Ingress con pathType: Exact en vez de Prefix |
2 | Usar --rule="host/*=svc:80" con asterisco |
Confundir envFrom con env |
3 | "Todas las claves" → envFrom; "una clave" → valueFrom |
storageClassName distinto entre PV y PVC |
4 | Debe coincidir exactamente, o el PVC queda Pending |
Poner solo la tolerancia y olvidar el nodeSelector |
5 | La tolerancia permite, no obliga: hacen falta las dos cosas |
backoffLimit en el nivel equivocado del CronJob |
6 | Va en jobTemplate.spec, no en CronJob.spec |
QoS Burstable en vez de Guaranteed |
7 | requests y limits idénticos en CPU y memoria |
Olvidar el subrecurso pods/log |
8 | Sin él, kubectl logs falla aunque puedas listar pods |
NetworkPolicy sin policyTypes |
9 | Sin policyTypes: [Ingress] no hay denegación efectiva |
Aplicar restricted y olvidar seccompProfile |
10 | El estándar restricted lo exige explícitamente |
HPA con <unknown> en las métricas |
11 | Falta el metrics-server o el Deployment no tiene requests de CPU |
rollout undo sin comprobar la imagen resultante |
12 | Verificar con jsonpath la imagen final |
| Diagnosticar sin mirar los eventos | 13, 14, 15 | kubectl describe y kubectl get events --sort-by=.lastTimestamp |
| Borrar pods de un Deployment para "arreglarlos" | 13 | Se recrean idénticos: hay que corregir la causa |
Tres consejos finales para el simulacro:
- Trata las tres tareas de resolución de problemas como el examen real las trata: valen 24 de los 100 puntos, casi una cuarta parte. Si vas justo de tiempo, prioriza estas antes que las de creación.
- Si un objeto queda
PendingoCrashLoopBackOff, la tarea no está hecha. No basta con que el YAML se haya aplicado. - Anota el tiempo real de cada tarea. El análisis posterior de dónde se te fue el tiempo vale tanto como la nota.
Ejercicios
Estos tres retos integradores son de mayor calado que las tareas del simulacro: cada uno junta varios módulos del curso en un único objetivo. Hazlos después del simulacro, sin límite estricto de tiempo pero midiéndolo.
Ejercicio 1 — Componente completo desde cero
Partiendo de un namespace vacío rutas-norte-qa, deja operativo el componente api-reservas completo, listo para producción:
- Un ConfigMap
config-apiy un Secretcredenciales-postgres. - Un Deployment
api-reservascon 3 réplicas, imagennginx:1.27-alpine, que consuma el ConfigMap por variables y el Secret por fichero. - Sondas de readiness y liveness, y QoS
Guaranteed. - Una ServiceAccount propia
sa-apicon permiso para leer únicamente el ConfigMapconfig-api, y sin montaje automático del token. - Un Service ClusterIP y un Ingress con host
qa.rutasnorte.es. - Un HPA entre 3 y 9 réplicas al 70 % de CPU.
- Un PodDisruptionBudget que garantice al menos 2 pods disponibles.
- NetworkPolicy de denegación de entrada por defecto, con excepción para el namespace
ingress-nginx.
Objetivo: 30 minutos. Criterio: todo Running, endpoints poblados, auth can-i correcto y el pod sin token montado.
Ejercicio 2 — Recuperar un servicio caído
Provoca deliberadamente esta situación en rutas-norte-pro y después recupérala como si fuera una incidencia real (usa el runbook mental del módulo 11):
El síntoma que te dan: "los usuarios reciben 503 al entrar en la tienda; el Ingress responde pero no llega nada detrás".
Preparación de la avería (que la haga otra persona, o hazla tú y espera un día para olvidar los detalles):
kubectl scale deployment tienda-web --replicas=0 -n rutas-norte-pro
kubectl patch svc tienda-web-svc -n rutas-norte-pro \
-p '{"spec":{"ports":[{"port":80,"targetPort":8080}]}}'
kubectl set image deployment/tienda-web tienda-web=nginx:9.9-noexiste -n rutas-norte-proLo que se te pide: diagnosticar en orden (Ingress → Service → endpoints → pods → contenedor), documentar cada hallazgo, arreglar las tres causas y dejar el servicio sirviendo con 3 réplicas. Escribe un breve informe de incidencia con línea temporal, causa raíz y acción preventiva.
Ejercicio 3 — Migración con cero cortes
En rutas-norte-pro, el Deployment tienda-web sirve tráfico. Migra su almacenamiento de configuración de un ConfigMap montado como fichero a un ConfigMap distinto con contenido nuevo, sin que ningún pod deje de estar disponible en ningún momento y sin que se sirva contenido inconsistente.
Pasos que debes resolver: crear el nuevo ConfigMap, ajustar la estrategia del Deployment, pausar el rollout para agrupar cambios, aplicar la modificación, reanudar, verificar en cada fase que hay al menos 3 pods listos, y tener preparado el rollout undo si algo falla.
Demuestra con un bucle de peticiones que no ha habido ni un solo fallo durante la migración.
Soluciones
Solución al Ejercicio 1
k create namespace rutas-norte-qa
k config set-context --current --namespace=rutas-norte-qa
# 1. Configuración
k create configmap config-api --from-literal=nivel_log=info --from-literal=max_conexiones=100
k create secret generic credenciales-postgres \
--from-literal=usuario=reservas --from-literal=password='N0rt3-2026'
# 4a. ServiceAccount y RBAC mínimo
k create serviceaccount sa-api
k patch serviceaccount sa-api -p '{"automountServiceAccountToken": false}'
k create role lector-config --verb=get --resource=configmaps --resource-name=config-api
k create rolebinding lector-config-b --role=lector-config \
--serviceaccount=rutas-norte-qa:sa-apiEl Deployment con todo integrado:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-reservas
namespace: rutas-norte-qa
spec:
replicas: 3
selector:
matchLabels: { app: api-reservas }
template:
metadata:
labels: { app: api-reservas }
spec:
serviceAccountName: sa-api
automountServiceAccountToken: false
containers:
- name: api
image: nginx:1.27-alpine
ports:
- containerPort: 80
envFrom:
- configMapRef: { name: config-api }
volumeMounts:
- name: secretos
mountPath: /etc/secretos
readOnly: true
resources:
requests: { cpu: 200m, memory: 256Mi }
limits: { cpu: 200m, memory: 256Mi }
readinessProbe:
httpGet: { path: /, port: 80 }
initialDelaySeconds: 3
periodSeconds: 10
livenessProbe:
httpGet: { path: /, port: 80 }
periodSeconds: 15
failureThreshold: 3
volumes:
- name: secretos
secret: { secretName: credenciales-postgres }k apply -f api-qa.yaml
# 5. Exposición
k expose deployment api-reservas --name=api-reservas-svc --port=80 --target-port=80
k create ingress api-ingress --class=nginx --rule="qa.rutasnorte.es/*=api-reservas-svc:80"
# 6. HPA
k autoscale deployment api-reservas --min=3 --max=9 --cpu-percent=70
# 7. PDB
k create poddisruptionbudget api-pdb --selector=app=api-reservas --min-available=2# 8. NetworkPolicies
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: denegar-entrada, namespace: rutas-norte-qa }
spec:
podSelector: {}
policyTypes: [Ingress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: permitir-desde-ingress, namespace: rutas-norte-qa }
spec:
podSelector:
matchLabels: { app: api-reservas }
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector:
matchLabels: { kubernetes.io/metadata.name: ingress-nginx }
ports:
- protocol: TCP
port: 80Verificación completa:
k get deploy,svc,ing,hpa,pdb,netpol -n rutas-norte-qa
k describe pod -l app=api-reservas -n rutas-norte-qa | grep "QoS Class" # Guaranteed
k exec deploy/api-reservas -n rutas-norte-qa -- ls /var/run/secrets/kubernetes.io/serviceaccount
# No such file or directory ← el token NO está montado
k auth can-i get configmap/config-api --as=system:serviceaccount:rutas-norte-qa:sa-api -n rutas-norte-qa # yes
k auth can-i list configmaps --as=system:serviceaccount:rutas-norte-qa:sa-api -n rutas-norte-qa # noLas dos trampas del ejercicio: automountServiceAccountToken: false hay que ponerlo en el pod (o en la SA), y el --resource-name=config-api en el Role es lo que convierte "leer un ConfigMap concreto" en un permiso realmente mínimo.
Solución al Ejercicio 2
Diagnóstico en el orden correcto, de fuera hacia dentro:
# 1. El Ingress: ¿tiene backend?
k describe ingress tienda-ingress -n rutas-norte-pro | grep -A5 RulesHost Path Backends
www.rutasnorte.es / tienda-web-svc:80 (<error: endpoints "tienda-web-svc" not found>)# 3. ¿Hay pods?
k get pods -l app=tienda-web -n rutas-norte-pro
k get deploy tienda-web -n rutas-norte-proHallazgo 1: el Deployment está escalado a 0.
k scale deployment tienda-web --replicas=3 -n rutas-norte-pro
k get pods -l app=tienda-web -n rutas-norte-proHallazgo 2: imagen inexistente.
k describe pod -l app=tienda-web -n rutas-norte-pro | grep -A3 Events
# Failed to pull image "nginx:9.9-noexiste": manifest unknown
k set image deployment/tienda-web tienda-web=nginx:1.27-alpine -n rutas-norte-pro
k rollout status deployment/tienda-web -n rutas-norte-pro
k get endpoints tienda-web-svc -n rutas-norte-proLos pods están Running pero el Service sigue sin endpoints: hay una tercera causa.
Hallazgo 3: targetPort equivocado.
k patch svc tienda-web-svc -n rutas-norte-pro \
-p '{"spec":{"ports":[{"port":80,"targetPort":80,"protocol":"TCP"}]}}'
k get endpoints tienda-web-svc -n rutas-norte-proInforme de incidencia:
INCIDENCIA: 503 en www.rutasnorte.es
Impacto: tienda-web no disponible, 42 minutos.
Línea temporal:
T+0 Alerta de 503 desde el Ingress
T+2 Ingress OK, Service sin endpoints
T+4 Deployment escalado a 0 -> escalado a 3
T+6 Pods en ImagePullBackOff (nginx:9.9-noexiste) -> imagen corregida
T+9 Pods Running pero Service aún sin endpoints
T+11 targetPort 8080 frente a puerto real 80 -> corregido
T+12 Servicio restablecido
Causa raíz: tres cambios manuales no revisados sobre produccion.
Prevención: gestionar rutas-norte-pro por GitOps (modulo 10), impedir cambios
manuales con RBAC, y alerta sobre "Service sin endpoints" en Prometheus.La lección de este ejercicio: un síntoma puede esconder varias causas encadenadas. Arreglar la primera y dar por cerrada la incidencia es el error clásico. La verificación de extremo a extremo (endpoints poblados) es lo único que demuestra que está resuelto.
Solución al Ejercicio 3
# 0. Bucle de comprobación en otro terminal
while true; do
k run probe-$RANDOM --rm -q --image=busybox:1.36 --restart=Never -n rutas-norte-pro \
-- wget -qO- --timeout=2 tienda-web-svc >/dev/null 2>&1 \
&& echo -n "." || echo -n "X"
sleep 1
done# 1. Nuevo ConfigMap
k create configmap config-web-v2 \
--from-literal=index.html='<h1>Rutas Norte v2</h1>' -n rutas-norte-pro
# 2. Estrategia sin cortes
k patch deployment tienda-web -n rutas-norte-pro -p '
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0'
# 3. Pausar para agrupar cambios
k rollout pause deployment/tienda-web -n rutas-norte-pro
# 4. Aplicar los cambios (ninguno se despliega todavía)
k patch deployment tienda-web -n rutas-norte-pro --type='json' -p='[
{"op":"replace","path":"/spec/template/spec/volumes/0/configMap/name","value":"config-web-v2"}
]'
k annotate deployment tienda-web -n rutas-norte-pro \
kubernetes.io/change-cause="migracion a config-web-v2" --overwrite
# 5. Reanudar: ahora sí, un único rollout
k rollout resume deployment/tienda-web -n rutas-norte-pro
k rollout status deployment/tienda-web -n rutas-norte-pro --timeout=120sVerificación durante el proceso (en un tercer terminal):
watch -n1 'kubectl get deploy tienda-web -n rutas-norte-pro; \
kubectl get endpoints tienda-web-svc -n rutas-norte-pro'El bucle de peticiones debe mostrar solo puntos, sin ninguna X.
Plan de reversión preparado antes de empezar:
k rollout undo deployment/tienda-web -n rutas-norte-pro
k rollout status deployment/tienda-web -n rutas-norte-proLas tres claves del ejercicio:
maxUnavailable: 0es lo que garantiza que nunca falten pods;maxSurge: 1es lo que permite que el rollout avance (con ambos a 0 se bloquearía).rollout pause/resumeevita dos rollouts consecutivos cuando hay varios cambios: sin pausar, elpatchdel volumen y la anotación provocarían dos oleadas de reemplazo.- La
readinessProbees imprescindible: sin ella, Kubernetes considera "disponible" a un pod que todavía no sirve, y sí habría cortes aunquemaxUnavailablesea 0.
Solucionario del Simulacro
Ahora sí. Corrige tarea por tarea y anota tu puntuación.
Tarea 1 (5 puntos)
k create deployment tienda-web --image=nginx:1.27-alpine --replicas=4 \
-n rutas-norte-dev $do > t1.yamlAñadir la etiqueta en la plantilla del pod:
Comprobación:
NAME READY UP-TO-DATE AVAILABLE
tienda-web 6/6 6 6
tienda-web-6c9d... 1/1 Running app=tienda-web,entorno=dev,pod-template-hash=...La trampa: poner entorno=dev solo en metadata.labels del Deployment. Los pods no la heredan: va en spec.template.metadata.labels. Alternativa válida y más rápida: crear el Deployment y después k label pods -l app=tienda-web entorno=dev, pero entonces los pods nuevos del escalado no la llevarían.
Tarea 2 (7 puntos)
k expose deployment tienda-web --name=tienda-web-svc \
--port=80 --target-port=80 -n rutas-norte-dev
k create ingress tienda-ingress -n rutas-norte-dev --class=nginx \
--rule="www.rutasnorte.es/*=tienda-web-svc:80"Comprobación:
k get endpoints tienda-web-svc -n rutas-norte-dev
k describe ingress tienda-ingress -n rutas-norte-dev | grep -A4 RulesNAME ENDPOINTS
tienda-web-svc 10.244.1.5:80,10.244.1.6:80,... (6 direcciones)
Rules:
Host Path Backends
www.rutasnorte.es / tienda-web-svc:80 (10.244.1.5:80,...)La trampa: el /* genera pathType: Prefix. Sin el asterisco obtendrías Exact, que solo casa la raíz literal. Y si el describe muestra <error: endpoints not found>, el Service está mal.
Tarea 3 (6 puntos)
k create configmap config-api --from-literal=nivel_log=info \
--from-literal=max_conexiones=100 -n rutas-norte-dev
k create secret generic credenciales-postgres \
--from-literal=usuario=reservas --from-literal=password='N0rt3-2026' -n rutas-norte-dev
k run api-reservas --image=nginx:1.27-alpine -n rutas-norte-dev $do > t3.yamlspec:
containers:
- name: api-reservas
image: nginx:1.27-alpine
envFrom:
- configMapRef:
name: config-api
volumeMounts:
- name: secretos
mountPath: /etc/secretos
readOnly: true
volumes:
- name: secretos
secret:
secretName: credenciales-postgresComprobación:
k exec api-reservas -n rutas-norte-dev -- env | grep -E 'nivel_log|max_conexiones'
k exec api-reservas -n rutas-norte-dev -- cat /etc/secretos/usuarioLa trampa: envFrom es hermano de env, no hijo. Y "montar el Secret como ficheros" significa volumen, no secretKeyRef.
Tarea 4 (8 puntos)
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-reservas
spec:
capacity: { storage: 3Gi }
accessModes: [ReadWriteOnce]
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath: { path: /mnt/datos-reservas }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-reservas
namespace: rutas-norte-pre
spec:
accessModes: [ReadWriteOnce]
storageClassName: manual
resources:
requests: { storage: 2Gi }
---
apiVersion: v1
kind: Pod
metadata:
name: postgres-reservas
namespace: rutas-norte-pre
spec:
containers:
- name: postgres
image: busybox:1.36
command: ["sleep", "3600"]
volumeMounts:
- name: datos
mountPath: /var/lib/datos
volumes:
- name: datos
persistentVolumeClaim:
claimName: pvc-reservasComprobación:
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM
pv-reservas 3Gi RWO Retain Bound rutas-norte-pre/pvc-reservas
NAME STATUS VOLUME CAPACITY
persistentvolumeclaim/pvc-reservas Bound pv-reservas 3GiLa trampa doble: el PV es de ámbito de clúster (sin namespace), y el storageClassName: manual debe estar en ambos. Si lo omites en el PVC, el aprovisionador dinámico por defecto intentaría crear otro volumen y el PVC no se enlazaría al tuyo.
Tarea 5 (6 puntos)
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker-notificaciones
namespace: rutas-norte-pro
spec:
replicas: 2
selector:
matchLabels: { app: worker-notificaciones }
template:
metadata:
labels: { app: worker-notificaciones }
spec:
nodeSelector:
tipo: batch
tolerations:
- key: dedicado
operator: Equal
value: batch
effect: NoSchedule
containers:
- name: worker
image: busybox:1.36
command: ["sleep", "3600"]Comprobación:
NAME READY STATUS NODE
worker-notificaciones-x 1/1 Running simulacro-worker2
worker-notificaciones-y 1/1 Running simulacro-worker2La trampa: hacen falta las dos piezas. La toleration solo permite que el pod entre en ese nodo; el nodeSelector es lo que lo obliga a ir ahí. Con solo la tolerancia, los pods irían a cualquier nodo libre.
Tarea 6 (5 puntos)
apiVersion: batch/v1
kind: CronJob
metadata:
name: informes-nocturnos
namespace: rutas-norte-pro
spec:
schedule: "30 2 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
containers:
- name: informes
image: busybox:1.36
command: ["/bin/sh", "-c", "echo informe de ocupacion generado"]Comprobación:
k get cronjob informes-nocturnos -n rutas-norte-pro
k create job prueba-t6 --from=cronjob/informes-nocturnos -n rutas-norte-pro
k logs job/prueba-t6 -n rutas-norte-proLa trampa: backoffLimit va en jobTemplate.spec; concurrencyPolicy y successfulJobsHistoryLimit en CronJob.spec. Y restartPolicy debe ser OnFailure o Never: Always hace que la API rechace el manifiesto.
Tarea 7 (7 puntos)
containers:
- name: nginx
image: nginx:1.27-alpine
resources:
requests: { cpu: 200m, memory: 256Mi }
limits: { cpu: 200m, memory: 256Mi }
readinessProbe:
httpGet: { path: /, port: 80 }
initialDelaySeconds: 3
periodSeconds: 10
livenessProbe:
httpGet: { path: /, port: 80 }
periodSeconds: 10
failureThreshold: 3Comprobación:
k rollout status deployment/api-reservas -n rutas-norte-pre
k describe pod -l app=api-reservas -n rutas-norte-pre | grep "QoS Class"La trampa: Guaranteed exige requests idénticos a limits en CPU y memoria y en todos los contenedores. Poner solo limits también funciona (Kubernetes copia los requests), pero poner requests distintos de limits da Burstable y la tarea puntúa a la mitad.
Tarea 8 (7 puntos)
k create serviceaccount soporte-n1 -n rutas-norte-pro
k create role soporte-lectura --verb=get,list,watch \
--resource=pods,pods/log -n rutas-norte-pro
k create rolebinding soporte-lectura-b --role=soporte-lectura \
--serviceaccount=rutas-norte-pro:soporte-n1 -n rutas-norte-pro
k set serviceaccount deployment/tienda-web soporte-n1 -n rutas-norte-proComprobación:
SA=system:serviceaccount:rutas-norte-pro:soporte-n1
k auth can-i list pods --as=$SA -n rutas-norte-pro # yes
k auth can-i get pods/log --as=$SA -n rutas-norte-pro # yes
k auth can-i delete pods --as=$SA -n rutas-norte-pro # no
k auth can-i list pods --as=$SA -n rutas-norte-dev # no
k get deploy tienda-web -n rutas-norte-pro \
-o jsonpath='{.spec.template.spec.serviceAccountName}' # soporte-n1La trampa: pods/log es un recurso aparte. Sin él, kubectl logs devuelve Forbidden aunque puedas listar pods. Y usar ClusterRole+ClusterRoleBinding daría acceso a todos los namespaces: violaría el criterio.
Tarea 9 (7 puntos)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: denegar-todo-entrada
namespace: rutas-norte-pro
spec:
podSelector: {}
policyTypes: [Ingress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: permitir-tienda-desde-ingress
namespace: rutas-norte-pro
spec:
podSelector:
matchLabels:
app: tienda-web
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- protocol: TCP
port: 80Comprobación:
k get netpol -n rutas-norte-pro
# Desde dentro del namespace: debe FALLAR
k run test --rm -it --image=busybox:1.36 --restart=Never -n rutas-norte-pro \
-- wget -qO- --timeout=3 tienda-web-svcLa trampa: sin policyTypes: [Ingress] en la primera política, el objeto existe pero no deniega nada. Y kubernetes.io/metadata.name es una etiqueta que Kubernetes pone automáticamente en todos los namespaces desde la 1.21: es la forma fiable de seleccionar uno por nombre.
Tarea 10 (7 puntos)
k label namespace rutas-norte-pre \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=v1.30 --overwriteapiVersion: v1
kind: Pod
metadata:
name: auditor-seguro
namespace: rutas-norte-pre
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: auditor
image: busybox:1.36
command: ["sleep", "3600"]
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]Comprobación:
k get ns rutas-norte-pre --show-labels
k get pod auditor-seguro -n rutas-norte-pre
k run inseguro --image=nginx -n rutas-norte-preError from server (Forbidden): pods "inseguro" is forbidden:
violates PodSecurity "restricted:v1.30": allowPrivilegeEscalation != false,
unrestricted capabilities, runAsNonRoot != true, seccompProfileLa trampa: el estándar restricted exige cuatro cosas a la vez y la gente olvida seccompProfile: RuntimeDefault. El mensaje de error las enumera todas: úsalo como lista de comprobación.
Efecto lateral a tener en cuenta: al aplicar enforce en rutas-norte-pre, el Deployment api-reservas de la tarea 7 seguirá corriendo (PSA no expulsa pods existentes), pero no podrá crear pods nuevos. Si haces la tarea 10 antes que la 7 o la 11, el rollout se bloqueará. Es exactamente el tipo de interacción entre tareas que aparece en el examen real.
Tarea 11 (5 puntos)
Comprobación:
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
api-reservas Deployment/api-reservas cpu: 0%/65% 2 8 2La trampa: si TARGETS muestra <unknown>/65%, hay dos causas posibles: el metrics-server no está instalado o no responde, o el Deployment no tiene requests de CPU (el HPA calcula el porcentaje sobre el request). Si hiciste la tarea 7 antes, los requests ya están y esto funciona; si no, hay que añadirlos.
Tarea 12 (6 puntos)
k patch deployment tienda-web -n rutas-norte-pro -p '
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0'
k set image deployment/tienda-web nginx=nginx:1.27.2-alpine -n rutas-norte-pro
k annotate deployment tienda-web -n rutas-norte-pro \
kubernetes.io/change-cause="subida a 1.27.2" --overwrite
k rollout status deployment/tienda-web -n rutas-norte-pro
k rollout history deployment/tienda-web -n rutas-norte-pro
k rollout undo deployment/tienda-web -n rutas-norte-proComprobación:
k rollout status deployment/tienda-web -n rutas-norte-pro
k get deploy tienda-web -n rutas-norte-pro \
-o jsonpath='{.spec.template.spec.containers[0].image}'La trampa: el nombre del contenedor en set image no es libre. Compruébalo primero:
Si set image no encuentra el contenedor, el comando dice error: unable to find container named ... y no hace nada.
Tarea 13 (8 puntos) — Resolución de problemas
Causa raíz: el Deployment referencia con envFrom un Secret que no existe en el namespace.
k create secret generic credenciales-cola \
--from-literal=host=cola.rutasnorte.es --from-literal=token=abc123 \
-n rutas-norte-dev
k get pods -n rutas-norte-dev -w
echo "El Deployment referenciaba con envFrom el Secret credenciales-cola, inexistente en rutas-norte-dev" \
> /opt/diagnostico-13.txtComprobación:
La trampa: CreateContainerConfigError siempre apunta a un ConfigMap o Secret ausente, o a una clave inexistente dentro de uno que sí existe. No lo confundas con CrashLoopBackOff (el proceso arranca y muere) ni con ImagePullBackOff (problema de imagen). Y el enunciado prohíbe cambiar la configuración del contenedor: la solución es crear el Secret, no quitar el envFrom.
Tarea 14 (8 puntos) — Resolución de problemas
pod/redis-cache-6f8b...-xyz 1/1 Running
service/redis-cache-svc ClusterIP 10.96.87.4 6379/TCP
endpoints/redis-cache-svc <none> ← el síntomaLos pods están bien pero el Service no los ve. Eso solo tiene una explicación: el selector no casa las etiquetas.
k get svc redis-cache-svc -n rutas-norte-pre -o jsonpath='{.spec.selector}'
k get pods -l app=redis-cache -n rutas-norte-pre --show-labelsCausa raíz: el Service selecciona app=redis, pero los pods llevan app=redis-cache.
k patch svc redis-cache-svc -n rutas-norte-pre \
-p '{"spec":{"selector":{"app":"redis-cache"}}}'
echo "El selector del Service era app=redis y los pods llevan app=redis-cache" \
> /opt/diagnostico-14.txtComprobación:
La trampa: el enunciado prohíbe tocar los pods, así que cambiar sus etiquetas no vale (y además rompería el ReplicaSet, que los recrearía). Hay que corregir el Service. Recuerda la regla: Service sin endpoints = selector que no casa en el 90 % de los casos; el 10 % restante son pods no Ready por una readinessProbe fallando.
Tarea 15 (8 puntos) — Resolución de problemas
k get pod informes-ocupacion -n rutas-norte-pro
k describe pod informes-ocupacion -n rutas-norte-pro | tail -6NAME READY STATUS RESTARTS AGE
informes-ocupacion 0/1 Pending 0 14m
Events:
Warning FailedScheduling 2m (x8 over 14m) default-scheduler
0/3 nodes are available: 1 node(s) had untolerated taint
{node-role.kubernetes.io/control-plane: }, 2 node(s) didn't match
Pod's node affinity/selector.Causa raíz: el pod tiene nodeSelector: disco=nvme y ningún nodo lleva esa etiqueta.
k get pod informes-ocupacion -n rutas-norte-pro -o jsonpath='{.spec.nodeSelector}'
k get nodes --show-labels | grep disco || echo "ningun nodo tiene la etiqueta disco"Dos soluciones válidas. La mejor es etiquetar el nodo, porque el nodeSelector expresa un requisito real (un disco rápido para generar informes) y el pod es inmutable en ese campo:
La alternativa —recrear el pod sin nodeSelector— también deja el pod Running, pero pierde la intención del diseño:
k get pod informes-ocupacion -n rutas-norte-pro -o yaml > t15.yaml
# eliminar la sección nodeSelector
k delete pod informes-ocupacion -n rutas-norte-pro
k apply -f t15.yamlecho "nodeSelector disco=nvme sin ningun nodo etiquetado; solucion: etiquetar simulacro-worker con disco=nvme para respetar el requisito de planificacion" \
> /opt/diagnostico-15.txtLa trampa: el mensaje de FailedScheduling es la respuesta completa y hay que saber leerlo. didn't match Pod's node affinity/selector es nodeSelector o afinidad; untolerated taint es un taint sin tolerancia; Insufficient cpu/memory son recursos; pod has unbound immediate PersistentVolumeClaims es almacenamiento. Cuatro mensajes, cuatro causas distintas.
Autoevaluación
Suma los puntos obtenidos y busca tu franja.
| Puntuación | Veredicto | Qué hacer |
|---|---|---|
| 90-100 | Listo para presentarte. Tu nivel está claramente por encima del umbral de aprobado, con margen para un mal día. | Reserva la fecha ya. Dedica los días previos a mantener velocidad, no a estudiar temas nuevos. |
| 75-89 | Prácticamente listo. Aprobarías, pero sin colchón. | Identifica las 2-3 tareas que fallaste, repasa sus lecciones y repite el simulacro en una semana. Reserva la fecha para dentro de 2-3 semanas. |
| 60-74 | En el límite. Es la franja del "puede caer de cualquier lado". | Repasa los dominios de tus fallos con las tablas de mapeo de las lecciones 12-01, 12-02 o 12-03. Practica 2 semanas más y repite el simulacro. |
| 40-59 | Repasar dominios concretos. El conocimiento está, pero hay lagunas y falta velocidad. | Vuelve a las lecciones de los módulos donde fallaste (ver tabla siguiente). Trabaja 3-4 semanas antes de repetir. |
| 0-39 | Volver a los módulos. Faltan fundamentos, no solo práctica. | Rehaz el curso desde el módulo correspondiente, con el clúster delante y haciendo todos los ejercicios. |
Diagnóstico por tarea fallada
Cada tarea apunta a un módulo concreto. Si fallaste una, sabes exactamente adónde volver.
| Tarea fallada | Dominio | Vuelve a |
|---|---|---|
| 1 | Cargas de trabajo | 02-03-deployments |
| 2 | Servicios y redes | 04-02-tipos-de-servicios, 04-04-controladores-de-ingress |
| 3 | Configuración | 03-01-configmaps, 03-02-secrets, 03-03-variables-de-entorno |
| 4 | Almacenamiento | 05-02-volumenes-persistentes, 05-03-reclamaciones-de-volumenes-persistentes |
| 5 | Planificación | 06-05-planificacion-afinidad-taints-y-tolerations |
| 6 | Cargas por lotes | 06-03-trabajos-y-cronjobs |
| 7 | Observabilidad y recursos | 07-01-verificaciones-de-salud-y-sondas, 03-05-limitranges-y-clases-de-qos |
| 8 | Seguridad y acceso | 08-01-control-de-acceso-basado-en-roles, 03-06-serviceaccounts-y-acceso-a-la-api |
| 9 | Seguridad de red | 04-06-politicas-de-red, 08-04-seguridad-de-red |
| 10 | Endurecimiento | 08-02-contextos-de-seguridad-y-endurecimiento, 08-03-politicas-de-seguridad-de-pods |
| 11 | Escalado | 09-01-autoescalado-horizontal-de-pods |
| 12 | Despliegues | 02-04-actualizaciones-rollbacks-y-estrategias |
| 13, 14, 15 | Resolución de problemas | 07-06-depuracion-y-eventos-del-cluster, 11-06-operacion-en-produccion |
Análisis del tiempo
Además de la nota, revisa tus tiempos anotados:
| Situación | Qué significa | Remedio |
|---|---|---|
| Nota alta pero sin acabar a tiempo | Sabes hacerlo, pero despacio | Alias, $do, copiar de la documentación (lección 12-04) |
| Acabaste pronto con nota baja | Vas rápido pero con lagunas | Repasar los módulos de la tabla anterior |
| Te atascaste más de 15 min en una tarea | Falta disciplina de tiempo | Entrenar el límite duro y el salto sin remordimientos |
| Fallaste por namespace o contexto | Problema de proceso, no de conocimiento | Entrenar el ciclo contexto → resolver → verificar |
| Fallaste las tres de troubleshooting | El dominio de mayor peso del CKA | Rompe tu clúster a propósito y arréglalo, una y otra vez |
Conclusión
Aquí termina el curso.
Empezaste, en el módulo 1, lanzando un Pod suelto contra un clúster recién montado y preguntándote por qué demonios hacía falta tanta maquinaria para ejecutar un contenedor. Terminas resolviendo un examen de quince tareas contrarreloj sobre una plataforma completa en producción.
Entre esos dos puntos está el recorrido de Rutas Norte. Le diste forma con Deployments y Services, la configuraste sin meter contraseñas en el código, la publicaste con Ingress y TLS, le diste memoria con volúmenes persistentes y copias de seguridad, la enseñaste a arrancar en el orden correcto con initContainers y a sobrevivir al mantenimiento de un nodo con taints y PodDisruptionBudgets. Le pusiste ojos con Prometheus y Grafana, la cerraste con RBAC, NetworkPolicies, Pod Security y firma de imágenes, la hiciste crecer sola con HPA y KEDA, la empaquetaste con Helm y Kustomize, la desplegaste sola desde Git con Argo CD, y la operaste de verdad: con incidencias reales, runbooks, despliegues canario y una factura de costes que había que justificar.
Qué sabes hacer ahora, dicho sin adornos:
- Diseñar y desplegar una aplicación completa en Kubernetes, desde el manifiesto hasta la URL pública con certificado.
- Elegir la primitiva correcta para cada problema: Deployment o StatefulSet, Job o CronJob, initContainer o sidecar, readiness o liveness.
- Gestionar configuración y secretos sin exponerlos, y controlar el consumo de recursos de todo lo que corre.
- Montar, actualizar y reparar un clúster: nodos, plano de control, etcd, certificados.
- Diagnosticar. Y esto es lo que más va a distinguirte: ante un síntoma —un 503, un pod
Pending, un Service sin endpoints— sabes por dónde empezar, qué comando ejecutar y cómo llegar a la causa raíz en minutos. - Asegurar lo que despliegas, en las tres fases: construcción, despliegue y ejecución.
- Escalar, medir y ajustar, con datos en vez de intuiciones.
- Operar en producción como un profesional: con runbooks, con procedimientos de reversión probados y con conciencia del coste.
Y si te presentas a una certificación, sabes también cómo funciona el examen, qué dominios entran, dónde estudió cada objetivo y cómo se gestionan dos horas de terminal contrarreloj.
Kubernetes va a seguir cambiando. Aparecerán APIs nuevas, se retirarán otras, la Gateway API acabará desplazando a Ingress, y herramientas que hoy son estándar serán reemplazadas. Lo que no cambia es lo que de verdad has aprendido: el modelo declarativo, el bucle de reconciliación, la separación entre deseo y realidad, y el método para averiguar por qué la realidad no coincide con el deseo. Eso se traslada a cualquier versión y a cualquier herramienta que venga después.
Monta un clúster propio y no lo apagues. Rompe cosas a propósito. Despliega tus proyectos ahí aunque no haga falta. La única forma de que esto no se oxide es usarlo.
Buen viaje.
Curso de Kubernetes
Módulo 1: Introducción a Kubernetes
- ¿Qué es Kubernetes?
- Arquitectura de Kubernetes
- Conceptos y Terminología Clave
- Configuración de un Clúster de Kubernetes
- La CLI de Kubernetes: kubectl
- Objetos, Manifiestos YAML y el Modelo Declarativo
- El Proyecto del Curso: la Plataforma Rutas Norte
Módulo 2: Componentes Principales de Kubernetes
- Pods
- ReplicaSets
- Deployments
- Actualizaciones, Rollbacks y Estrategias de Despliegue
- Servicios
- Namespaces
- Etiquetas, Selectores y Anotaciones
Módulo 3: Gestión de Configuración y Secretos
- ConfigMaps
- Secrets
- Variables de Entorno
- Cuotas y Límites de Recursos
- LimitRanges y Clases de Calidad de Servicio (QoS)
- ServiceAccounts y Acceso a la API desde los Pods
Módulo 4: Redes en Kubernetes
- Redes de Clúster
- Tipos de Servicios
- DNS Interno y Descubrimiento de Servicios
- Controladores de Ingress
- TLS y Gestión de Certificados con cert-manager
- Políticas de Red
Módulo 5: Almacenamiento en Kubernetes
- Volúmenes
- Volúmenes Persistentes
- Reclamaciones de Volúmenes Persistentes
- Clases de Almacenamiento
- Aprovisionamiento Dinámico, Expansión y Snapshots
- Copias de Seguridad y Restauración de Datos
Módulo 6: Conceptos Avanzados de Kubernetes
- StatefulSets
- DaemonSets
- Trabajos y CronJobs
- Init Containers, Sidecars y Patrones Multi-Contenedor
- Planificación: Afinidad, Taints y Tolerations
- Definiciones de Recursos Personalizados (CRDs)
- Operadores y el Patrón Controlador
Módulo 7: Monitoreo y Registro
- Verificaciones de Salud y Sondas
- Servidor de Métricas y kubectl top
- Monitoreo con Prometheus
- Visualización y Alertas con Grafana y Alertmanager
- Registro Centralizado con Elasticsearch, Fluentd y Kibana (EFK)
- Depuración de Aplicaciones y Eventos del Clúster
Módulo 8: Seguridad en Kubernetes
- Control de Acceso Basado en Roles (RBAC)
- Contextos de Seguridad y Endurecimiento del Contenedor
- Políticas de Seguridad de Pods y Pod Security Standards
- Seguridad de Red
- Seguridad de Imágenes
- Auditoría, Escaneo y Gestión de Vulnerabilidades
Módulo 9: Escalado y Rendimiento
- Autoescalado Horizontal de Pods
- Autoescalado Vertical de Pods
- Autoescalado de Clúster
- Escalado por Eventos y Métricas Personalizadas con KEDA
- Alta Disponibilidad: PodDisruptionBudgets y Topología
- Ajuste de Rendimiento
Módulo 10: Ecosistema y Herramientas de Kubernetes
- Minikube y Entornos Locales con kind
- Kubeadm
- Helm
- Kustomize
- GitOps con Argo CD y Flux
- Kubernetes Gestionado: EKS, AKS y GKE
Módulo 11: Estudios de Caso y Aplicaciones del Mundo Real
- Despliegue de una Aplicación Web
- Ejecución de Aplicaciones con Estado
- CI/CD con Kubernetes
- Estrategias de Despliegue: Blue-Green y Canary
- Gestión Multi-Clúster
- Operación en Producción: Incidencias, Runbooks y Costes
