Módulo 2: Pods Deployments And Services
4. Manos a la obra: tu primer Pod
Descripción
Esta es la primera lección de este módulo donde ejecutas algo de verdad contra andes-cargo-cluster. Vas a crear un Pod de dos formas distintas —primero imperativa, después declarativa—, inspeccionarlo a fondo con kubectl describe, y confirmar con tus propias manos el punto central de la lección 2: cuando borras un Pod suelto, nadie lo repone. Todo lo que ves aquí corrió de verdad para escribir esta lección — los nombres, los estados, los eventos son literales, con la única variación esperada en la IP del Pod y el nodo exacto donde el scheduler lo asigne.
Conexión con el módulo
Esta lección es la mitad práctica de la lección 2 (el concepto de Pod) y, al mismo tiempo, la preparación exacta para la lección 5, donde vas a repetir el mismo "borrar un Pod" — pero esta vez con andes-cargo-status-api corriendo dentro de un Deployment. El contraste entre lo que ves aquí (nadie repone el Pod) y lo que vas a ver ahí (el ReplicaSet lo repone solo) es, a propósito, el corazón pedagógico de todo este módulo.
Antes de empezar: confirma que el clúster sigue arriba
kind get clusters
kubectl get nodes
Qué esperar (si tu clúster sigue en el mismo estado que dejaste al cerrar el Módulo 1; AGE es tu valor variable):
andes-cargo-cluster
NAME STATUS ROLES AGE VERSION
andes-cargo-cluster-control-plane Ready control-plane 10m v1.36.1
andes-cargo-cluster-worker Ready <none> 10m v1.36.1
andes-cargo-cluster-worker2 Ready <none> 10m v1.36.1
Si kind get clusters no devuelve nada, tu clúster no sobrevivió (por ejemplo, si eliminaste explícitamente los contenedores de Docker, no solo los detuviste) — vuelve al Módulo 1, lección 5, y créalo de nuevo antes de seguir. Si los nodos aparecen, pero en NotReady, espera unos segundos y confirma con Docker (docker ps --filter "name=andes-cargo-cluster") que los tres contenedores siguen Up.
Paso 1 — La forma rápida: kubectl run (imperativo)
kubectl run crea un Pod con una sola línea, sin necesidad de escribir ningún YAML — la forma más rápida de tener algo corriendo cuando estás explorando o depurando. Vas a usarla exactamente una vez en esta lección, con una imagen mínima que no tiene ninguna relación con Andes Cargo — nginx:alpine, un servidor web liviano (25 MB), disponible públicamente en Docker Hub, que andes-cargo-cluster puede descargar sin ningún registro adicional porque sus nodos tienen salida a internet, igual que cualquier docker pull normal desde tu host:
kubectl run hello-pod --image=nginx:alpine
Qué esperar (literal, ejecutado):
pod/hello-pod created
kubectl get pods
Qué esperar (justo después de crearlo — todavía descargando la imagen; AGE es tu valor variable):
NAME READY STATUS RESTARTS AGE
hello-pod 0/1 ContainerCreating 0 0s
Este es el comando que vas a usar constantemente el resto de esta guía para verificar cualquier cosa que crees — recuérdalo, porque no lo vas a volver a explicar cada vez que aparezca.
Antes de seguir, limpia este primer Pod — la razón para no dejarlo es la que ves a continuación:
kubectl delete pod hello-pod
Qué esperar:
pod "hello-pod" deleted from default namespace
Por qué esta guía casi nunca usa kubectl run de aquí en adelante. kubectl run es cómodo, pero es imperativo — no queda ningún archivo que describa lo que creaste, así que si necesitas recrear exactamente lo mismo mañana, tendrías que recordar (o volver a escribir) el comando completo. El resto de esta guía usa manifiestos YAML aplicados con kubectl apply -f, siguiendo la misma disciplina declarativa de la lección 3 — un archivo versionable, que puedes revisar, comparar y, a partir del Módulo 5, entregarle a Git como fuente de verdad.
Paso 2 — La forma real: pod.yaml (declarativo)
Crea el manifiesto que vas a usar el resto de esta lección:
mkdir -p andes-cargo-k8s && cd andes-cargo-k8s
# pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: hello-pod
labels:
app: hello-pod
spec:
containers:
- name: hello-pod
image: nginx:alpine
ports:
- containerPort: 80
Cada campo de este archivo ya lo explicó la lección 2 a fondo — nada nuevo que aprender aquí, solo confirmarlo funcionando de verdad:
kubectl apply -f pod.yaml
Qué esperar:
pod/hello-pod created
Espera unos segundos, y confirma que llegó a Running:
kubectl get pods
Qué esperar (AGE es tu valor variable; el resto, literal para esta imagen):
NAME READY STATUS RESTARTS AGE
hello-pod 1/1 Running 0 5s
1/1 en la columna READY significa: de los contenedores declarados dentro de este Pod (uno, en este caso), uno está listo. Si este Pod tuviera dos contenedores (el patrón sidecar de la lección 2), verías 2/2 cuando ambos estén listos.
Paso 3 — Inspecciona el Pod a fondo: kubectl describe
kubectl get pods te da un resumen de una línea; kubectl describe pod te da todo lo que Kubernetes sabe sobre ese objeto específico — el comando que vas a usar constantemente para diagnosticar cualquier problema en el resto de esta guía:
kubectl describe pod hello-pod
Qué esperar (Node, IP, Start Time, Container ID e Image ID son tu valor variable — dependen de a qué nodo asignó el scheduler este Pod y del hash exacto de la imagen; el resto es literal para este manifiesto):
Name: hello-pod
Namespace: default
Priority: 0
Service Account: default
Node: andes-cargo-cluster-worker/172.19.0.4
Start Time: Fri, 14 Aug 2026 13:32:55 -0600
Labels: app=hello-pod
Annotations: <none>
Status: Running
IP: 10.244.2.3
IPs:
IP: 10.244.2.3
Containers:
hello-pod:
Container ID: containerd://2b26d030a64387ef61b50dae0542740a2c351610334b1eb58ea3e05ddf790596
Image: nginx:alpine
Image ID: docker.io/library/nginx@sha256:4a73073bd557c65b759505da037898b61f1be6cbcc3c2c3aeac22d2a470c1752
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Fri, 14 Aug 2026 13:32:55 -0600
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-rqd87 (ro)
Conditions:
Type Status
PodReadyToStartContainers True
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
kube-api-access-rqd87:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
Optional: false
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 5s default-scheduler Successfully assigned default/hello-pod to andes-cargo-cluster-worker
Normal Pulled 5s kubelet spec.containers{hello-pod}: Container image "nginx:alpine" already present on machine and can be accessed by the pod
Normal Created 5s kubelet spec.containers{hello-pod}: Container created
Normal Started 5s kubelet spec.containers{hello-pod}: Container started
Tres secciones merecen una lectura detenida, porque las vas a usar para diagnosticar problemas reales en el resto de esta guía:
Node— te dice, sin ambigüedad, en cuál de los tres nodos deandes-cargo-clusterterminó este Pod. En este caso,andes-cargo-cluster-worker— elkube-scheduler(Módulo 1, lección 6) tomó esa decisión sin que nadie se la pidiera explícitamente; en la lección 5 vas a ver que, con más de un Pod, elschedulerlos reparte entre los nodos disponibles.Conditions— cinco chequeos independientes, todos enTruecuando un Pod está completamente sano.PodScheduled(elschedulerya lo asignó a un nodo),Initialized,PodReadyToStartContainers,ContainersReadyyReady(el resumen final). Cuando algo falla, esta tabla es el primer lugar donde ves cuál de los cinco pasos se atascó, no solo que "algo está mal".Events, al final — la cronología completa de lo que pasó con este Pod, en orden.Scheduled→Pulled→Created→Started, cuatro pasos, cada uno con su propio mensaje. Fíjate en el mensaje dePulled: "already present on machine" — porque ya habías descargadonginx:alpineen el Paso 1 conkubectl run, así que este segundo Pod no necesitó descargarla de nuevo. Esta sección deEventses, sin excepción, el primer lugar donde vas a mirar cuando un Pod no arranque como esperabas en el resto de esta guía — incluido elImagePullBackOffque ya se nombró en el Módulo 1.
Paso 4 — Borra el Pod, y observa que nadie lo recrea
Aquí está el momento central de esta lección — la confirmación con evidencia real de todo lo que la lección 2 explicó en teoría:
kubectl delete pod hello-pod
Qué esperar:
pod "hello-pod" deleted from default namespace
Confirma de inmediato:
kubectl get pods
Qué esperar:
No resources found in default namespace.
Espera unos segundos más, y corre el mismo comando otra vez — no por desconfianza del resultado anterior, sino porque es exactamente la disciplina que necesitas para la lección 5, donde esta misma espera sí va a mostrar un cambio:
kubectl get pods
Qué esperar (idéntico al anterior, sin importar cuánto esperes):
No resources found in default namespace.
Nada va a cambiar, nunca, por más que esperes. No hay ningún ReplicaSet, ningún Deployment, ningún controlador vigilando este Pod específico — lo declaraste, corrió, lo borraste, y con eso terminó su existencia completa. Guarda mentalmente este resultado exacto: es el que vas a contrastar directamente en la lección 5.
Analogía: la oficina sin departamento de administración
Retomando la analogía del Módulo: hello-pod fue, durante los minutos que corrió, una oficina individual completa —con todo lo que necesitaba adentro (el contenedor nginx:alpine, su propia dirección de red)—, pero sin ningún departamento de administración del edificio (Deployment) que llevara la cuenta de si esa oficina debía seguir ocupada o no. Cuando "cerraste" esa oficina (kubectl delete pod), nadie en el edificio lo notó, porque nadie tenía instrucciones de vigilarla. La lección 5 agrega exactamente esa pieza faltante: un departamento de administración real, con instrucciones explícitas de mantener un número fijo de oficinas ocupadas, pase lo que pase con cualquiera de ellas individualmente.
Errores comunes
Esperar que kubectl get pods muestre algo relacionado con kubectl run después de borrarlo con kubectl delete pod de otro nombre (de flujo, un descuido fácil de cometer en esta lección específica). Qué pasa: alguien corre kubectl run hello-pod en el Paso 1, lo borra, y después en el Paso 2 aplica pod.yaml — que declara el mismo nombre, hello-pod — y se confunde pensando que es "el mismo Pod que sigue vivo" en vez de un objeto completamente nuevo. Por qué pasa: el nombre coincide a propósito en esta lección, para que el Paso 2 se sienta como una continuación natural del Paso 1, no un objeto sin relación. Cómo detectarlo: si comparas el Start Time del kubectl describe pod del Paso 3 contra cuándo corriste el Paso 1 —van a ser momentos distintos, porque son objetos distintos, aunque compartan nombre—. Cómo corregirlo: no es un error funcional (Kubernetes no tiene problema con reusar un nombre después de borrar el objeto anterior), solo un malentendido conceptual — cada kubectl apply/kubectl run que ves en esta lección crea un objeto nuevo, sin memoria del anterior con el mismo nombre.
Confundir READY: 0/1 con un error, en vez de un estado transitorio normal (conceptual, ya viste una variante de esto en el Módulo 1 con NotReady en los nodos). Qué pasa: alguien corre kubectl get pods inmediatamente después del Paso 1 o el Paso 2, ve 0/1 en la columna READY y ContainerCreating en STATUS, y asume que algo falló. Por qué pasa: es fácil leer "0 de 1" como una fracción incompleta de forma negativa. Cómo detectarlo: si el mismo comando, corrido unos segundos después, muestra 1/1 y Running — eso confirma que era transitorio, no un error. Cómo corregirlo: espera siempre unos instantes después de crear un Pod, antes de diagnosticar un problema; ContainerCreating significa, literalmente, que el proceso de arranque (descargar la imagen si hace falta, crear el contenedor) sigue en curso — es el mismo patrón que ya viste con NotReady en los nodos del Módulo 1.
No revisar la sección Events de kubectl describe pod antes de asumir que algo está roto (de flujo, el hábito más valioso de esta lección para el resto de la guía). Qué pasa: alguien ve un Pod que no llega a Running y empieza a adivinar la causa —revisando la imagen, el YAML, cualquier cosa— antes de mirar la sección Events, que casi siempre ya tiene la respuesta explícita. Por qué pasa: Events aparece al final del describe, después de secciones más técnicas (Volumes, Tolerations) que pueden sentirse más "importantes" a primera vista. Cómo detectarlo: si pasaste más de un minuto revisando el YAML de un Pod problemático antes de correr kubectl describe pod y leer su sección Events completa. Cómo corregirlo: instala el hábito desde esta lección — kubectl describe pod <nombre>, y lee Events primero, de arriba hacia abajo. Vas a repetir este mismo reflejo, sin excepción, cada vez que algo no arranque como esperabas en el resto de esta guía.
Ejercicios
Ejercicio 1 — Reconstruye el flujo completo de memoria. Sin volver a la lección, enumera los cuatro pasos, en orden, que seguiste en esta lección, y qué demostró cada uno.
Ver solución
kubectl run hello-pod --image=nginx:alpine— la forma imperativa, rápida, de crear un Pod sin YAML.kubectl apply -f pod.yaml— la forma declarativa, con un manifiesto versionable, que esta guía usa de aquí en adelante.kubectl describe pod hello-pod— inspección completa: nodo asignado, condiciones, y la cronología deEvents.kubectl delete pod hello-pod, seguido dekubectl get podsrepetido — demostró que un Pod suelto, sin ningún controlador detrás, no se repone nunca por sí mismo.
Ejercicio 2 — Interpreta un describe pod ajeno. Un compañero te pasa la salida de un kubectl describe pod cuya sección Conditions muestra PodScheduled: True pero Initialized: False, con el resto en blanco. ¿Qué te dice esto sobre en qué punto exacto se atascó ese Pod, y qué sección revisarías primero para saber por qué?
Ver solución
Le dice que el kube-scheduler sí asignó el Pod a un nodo (PodScheduled: True), pero que el proceso de inicialización de ese Pod en ese nodo no ha terminado (Initialized: False) — el problema está en algún paso posterior a la asignación, probablemente relacionado con descargar la imagen o preparar el contenedor, no con encontrar un nodo disponible. La sección a revisar primero es siempre Events, al final del describe — ahí debería aparecer el mensaje explícito de qué está bloqueando ese paso (por ejemplo, un error de Pulling/Pulled si la imagen no se puede descargar).
Ejercicio 3 — Predice un caso con dos contenedores. Si pod.yaml declarara dos contenedores dentro de spec.containers en vez de uno, ¿qué esperarías ver en la columna READY de kubectl get pods mientras solo uno de los dos ha terminado de arrancar? ¿Y cuando ambos estén listos?
Ver solución
Mientras solo uno de los dos contenedores esté listo: READY mostraría 1/2 — el primer número cuenta cuántos contenedores del Pod están listos, el segundo cuántos hay declarados en total. Cuando ambos terminen de arrancar: READY mostraría 2/2, y recién ahí el Pod completo se consideraría listo (Ready: True en Conditions) — un Pod con múltiples contenedores no se considera "listo" hasta que todos sus contenedores lo están, no basta con que uno funcione.
Resumen y siguiente paso
En esta lección creaste tu primer Pod real de dos formas —imperativa con kubectl run, declarativa con kubectl apply -f pod.yaml—, lo inspeccionaste a fondo con kubectl describe pod (nodo asignado, condiciones, cronología de eventos), y confirmaste con evidencia real el punto central de la lección 2: un Pod suelto, sin ningún controlador detrás, desaparece para siempre en cuanto lo borras — nadie lo repone, sin importar cuánto esperes.
Antes de avanzar deberías poder: crear un Pod con ambos métodos y explicar cuándo usar cada uno; leer la sección Events de un kubectl describe pod para diagnosticar en qué paso se atascó un Pod problemático; y describir, con evidencia propia, por qué un Pod suelto no se auto-repara.
Siguiente lección: manos a la obra, el Deployment de andes-cargo-status-api. Ahí repites el mismo experimento de borrar un Pod — pero esta vez con el servicio real de Andes Cargo, corriendo dentro de un Deployment. El resultado va a ser el opuesto exacto del que acabas de confirmar aquí.
Recursos
- Kubernetes — kubectl run — referencia oficial del comando imperativo usado en el Paso 1.
- Kubernetes — Debug Running Pods — la guía oficial de diagnóstico con
kubectl describe, la herramienta central de esta lección. - Kubernetes — Pod Lifecycle: Container states — referencia oficial de los estados que viste en
Conditionsy en la columnaSTATUS.