Módulo 1: Why Kubernetes And The Continuity Challenge
6. Arquitectura de un clúster: control plane, nodos, y `etcd`
Descripción
kubectl get nodes en la lección 5 te mostró tres líneas: un control-plane y dos worker. Detrás de esa salida de tres líneas hay, en realidad, cinco procesos distintos trabajando juntos, cada uno con un trabajo específico y limitado, ninguno con conocimiento completo del sistema por sí solo. Esta lección abre la caja: qué hace cada componente, cómo se comunican, y —el detalle que hace que la siguiente lección tenga sentido— por qué kind corre todo esto como contenedores Docker dentro de otros contenedores Docker, sin ninguna magia adicional.
Conexión con el módulo
Esta lección no ejecuta ningún manifiesto nuevo — usa el clúster que ya creaste en la lección 5 como evidencia viva. La lección 7 retoma directamente algo que vas a confirmar aquí: la imagen de andes-cargo-status-api, que ya existe en tu Docker del host, no está disponible todavía dentro del clúster — y vas a entender exactamente por qué antes de resolverlo.
Los cinco componentes del plano de control
El plano de control (control plane) es el conjunto de procesos que deciden qué debería estar pasando en el clúster — nunca ejecutan directamente tu carga de trabajo, solo la planifican y la supervisan. En tu clúster andes-cargo-cluster, los cinco corren juntos en el nodo andes-cargo-cluster-control-plane:
kube-apiserver— la única puerta de entrada al clúster. Todo —kubectl, cualquier operador como ArgoCD (Módulo 5), cualquier admission controller (Módulo 6)— habla con Kubernetes exclusivamente a través de este componente, nunca directo conetcdni con ningún otro proceso. Valida cada solicitud, la autentica, la autoriza, y —lo más importante para el Módulo 6— es el punto exacto donde corre el admission control antes de que un objeto se guarde.etcd— la base de datos del clúster, y la única fuente de verdad sobre el estado actual de todo. Cuando creas unDeployment, ese documento YAML termina, después de pasar porkube-apiserver, guardado como un registro enetcd. Nada en Kubernetes "recuerda" nada por sí mismo fuera deetcd— sietcdse pierde sin respaldo, el clúster pierde la memoria completa de qué existía.kube-scheduler— decide en qué nodo debería correr un Pod nuevo, basándose en los recursos disponibles de cada nodo y en las restricciones que el propio Pod declare (por ejemplo, cuánta CPU/memoria necesita — algo que instalas explícitamente en el Módulo 3). Elschedulerno ejecuta nada — solo asigna, y le avisa alkube-apiservercuál fue su decisión.kube-controller-manager— el proceso que corre, en el fondo, decenas de bucles de control distintos, cada uno comparando continuamente "qué existe ahora mismo" contra "qué debería existir" (lo declarado enetcd), y actuando para cerrar cualquier diferencia sin que nadie se lo pida. Este es el mecanismo exacto que hace que unReplicaSetreemplace un Pod eliminado sin intervención humana — lo vas a ver en acción de primera mano en el Módulo 2.kube-proxy— corre en cada nodo (no solo en el plano de control), y mantiene las reglas de red que hacen posible que unServicede Kubernetes (Módulo 2) reparta tráfico entre varios Pods, sin importar en qué nodo esté corriendo cada uno.
Y, en cada nodo —control plane y workers por igual— corre un sexto proceso, distinto en naturaleza a los cinco anteriores porque no vive "en el clúster" como objeto sino directamente en el sistema operativo del nodo:
kubelet— el agente que de verdad ejecuta algo. Recibe instrucciones dekube-apiserver("este Pod debería correr aquí"), y usa el motor de contenedores del nodo (enkind,containerd) para arrancarlo, monitorear su salud, y reportar el estado real de vuelta. Sikube-scheduleres quien decide,kubeletes quien ejecuta la decisión.
El diagrama completo
andes-cargo-cluster — ARQUITECTURA REAL
┌──────────────────────────────────────────────────────────────────┐
│ NODO: andes-cargo-cluster-control-plane │
│ │
│ kubectl ───────▶ ┌─────────────────┐ │
│ (tu terminal) │ kube-apiserver │ ◀── única puerta de entrada │
│ └────────┬────────┘ │
│ │ │
│ ┌────────────────────┼────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────────┐ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ etcd │ │ kube-scheduler │ │ kube-controller- │ │
│ │ (estado guardado) │ │ (decide EN QUÉ NODO)│ │ manager (reconcilia) │ │
│ └────────────────┘ └──────────────────┘ └──────────────────────┘ │
│ │
│ kubelet + kube-proxy (también corren aquí, como en todo nodo) │
└──────────────────────────────────────────────────────────────────┘
│
kube-apiserver le indica a cada kubelet
qué Pods deberían correr en su nodo
│
┌───────────────────────────┼───────────────────────────┐
▼ ▼
┌──────────────────────────┐ ┌──────────────────────────┐
│ NODO: ...-worker │ │ NODO: ...-worker2 │
│ │ │ │
│ kubelet ──▶ containerd │ │ kubelet ──▶ containerd │
│ kube-proxy │ │ kube-proxy │
│ (aquí van a correr tus │ │ (aquí también pueden correr │
│ Pods desde el Módulo 2) │ │ tus Pods) │
└──────────────────────────┘ └──────────────────────────┘
kube-apiserver es, literalmente, el único componente con el que cualquier otra pieza —incluido kubectl, incluido cada kubelet— tiene permitido hablar directamente. Ni kube-scheduler ni kube-controller-manager tocan etcd directo: ambos consultan y escriben a través de kube-apiserver. Esta centralización, aparentemente rígida, es lo que hace posible el modelo de seguridad completo que el Módulo 6 construye — si solo hay una puerta, solo hace falta vigilar una puerta.
Evidencia real: los cinco componentes, corriendo como Pods
Aquí está el detalle más importante de esta lección, y la razón por la que kind es una herramienta de aprendizaje honesta y no un simulador: en Kubernetes, hasta el propio plano de control corre como Pods, con el mismo mecanismo que vas a usar para tu propia carga de trabajo desde el Módulo 2. Confírmalo tú mismo, contra el clúster que ya tienes arriba:
kubectl get pods -A -o wide
Qué esperar (los nombres de Pod con sufijo de hash —coredns-589f44dc88-2mqbh, por ejemplo— son variables por diseño; los prefijos, el NAMESPACE, y qué corre en qué nodo son literales para esta arquitectura):
NAMESPACE NAME READY STATUS RESTARTS AGE NODE
kube-system coredns-589f44dc88-2mqbh 1/1 Running 0 100s andes-cargo-cluster-control-plane
kube-system coredns-589f44dc88-q6qs4 1/1 Running 0 100s andes-cargo-cluster-control-plane
kube-system etcd-andes-cargo-cluster-control-plane 1/1 Running 0 108s andes-cargo-cluster-control-plane
kube-system kindnet-4z9xs 1/1 Running 0 95s andes-cargo-cluster-worker2
kube-system kindnet-8njwk 1/1 Running 0 101s andes-cargo-cluster-control-plane
kube-system kindnet-zffz9 1/1 Running 0 95s andes-cargo-cluster-worker
kube-system kube-apiserver-andes-cargo-cluster-control-plane 1/1 Running 0 108s andes-cargo-cluster-control-plane
kube-system kube-controller-manager-andes-cargo-cluster-control-plane 1/1 Running 0 109s andes-cargo-cluster-control-plane
kube-system kube-proxy-2xk6h 1/1 Running 0 95s andes-cargo-cluster-worker
kube-system kube-proxy-nqqvw 1/1 Running 0 95s andes-cargo-cluster-worker2
kube-system kube-proxy-w2scb 1/1 Running 0 101s andes-cargo-cluster-control-plane
kube-system kube-scheduler-andes-cargo-cluster-control-plane 1/1 Running 0 108s andes-cargo-cluster-control-plane
local-path-storage local-path-provisioner-855c7b7774-h2km4 1/1 Running 0 100s andes-cargo-cluster-control-plane
Fíjate en el patrón de nombres: etcd-andes-cargo-cluster-control-plane, kube-apiserver-andes-cargo-cluster-control-plane, kube-scheduler-..., kube-controller-manager-... — los cuatro componentes centrales del plano de control, corriendo como Pods, todos en el nodo control-plane, exactamente como el diagrama de esta lección los ubicó. kube-proxy y kindnet (el CNI que kind usa por defecto) aparecen tres veces cada uno — uno por cada nodo del clúster, confirmando que esos dos sí corren en todos lados, no solo en el plano de control. Y coredns —el servidor de nombres interno que confirmaste en la lección 5— corre como dos Pods separados, por redundancia.
Ningún componente de esta lista lo "inventó" kind — son, literalmente, las mismas imágenes de contenedor (registry.k8s.io/kube-apiserver, registry.k8s.io/etcd, etc.) que corren dentro de un nodo real de EKS. Confírmalo con crictl, la herramienta de línea de comandos del motor de contenedores interno del nodo (no de Docker):
docker exec andes-cargo-cluster-control-plane crictl images
Qué esperar (los tamaños son literales para esta versión exacta; pueden variar levemente entre arquitecturas de procesador):
IMAGE TAG IMAGE ID SIZE
docker.io/kindest/kindnetd v20260528-9350166c f2ede2b789a61 35.6MB
docker.io/kindest/local-path-helper v20260131-7181c60a 8f375e9f93513 2.74MB
docker.io/kindest/local-path-provisioner v20260521-9fb22683 3501a03785a84 14.2MB
registry.k8s.io/coredns/coredns v1.14.2 fe81a497e85f1 20.8MB
registry.k8s.io/etcd 3.6.8-0 6da6ea097b384 21.1MB
registry.k8s.io/kube-apiserver v1.36.1 4923943f21256 89.9MB
registry.k8s.io/kube-controller-manager v1.36.1 39d983367f38c 80.2MB
registry.k8s.io/kube-proxy v1.36.1 01ad784c02283 80.9MB
registry.k8s.io/kube-scheduler v1.36.1 76e62361b06b5 57.3MB
registry.k8s.io/pause 3.10 afb61768ce381 268kB
etcd, marcado con su propia versión (3.6.8-0) independiente de la versión general de Kubernetes (v1.36.1) — un detalle que confirma que etcd es un proyecto separado, con su propio ciclo de versiones, que Kubernetes adopta como dependencia.
Por qué kind corre todo esto sobre contenedores Docker anidados
Este es el detalle más importante de la lección, y la razón exacta de un comportamiento que vas a resolver en la lección 7. Cada "nodo" de tu clúster —andes-cargo-cluster-control-plane, andes-cargo-cluster-worker, andes-cargo-cluster-worker2— es, del lado de tu Docker del host, un único contenedor (confirmaste esto con docker ps en la lección 5). Pero dentro de ese contenedor corre su propio motor de contenedores independiente —containerd—, que es el que de verdad usa kubelet para arrancar cada Pod. Es, literalmente, un motor de contenedores corriendo dentro de otro contenedor: el Docker de tu host ve un solo proceso llamado andes-cargo-cluster-control-plane; ese proceso, por dentro, administra su propia colección de "contenedores" (los Pods del plano de control que acabas de listar) con un containerd que tu Docker del host no puede ver directamente.
La consecuencia práctica, que confirmas tú mismo ahora mismo:
docker images andes-cargo-status-api --format "table {{.Repository}}\t{{.Tag}}\t{{.ID}}"
Qué esperar (si ya construiste la imagen en aws-serverless-and-containers-guide o en la lección 7 de este módulo; tu IMAGE ID será distinto — literal en estructura, variable en el ID exacto):
REPOSITORY TAG IMAGE ID
andes-cargo-status-api latest d07c06907665
Y ahora, la misma pregunta, pero adentro del containerd del nodo:
docker exec andes-cargo-cluster-control-plane crictl images | grep andes-cargo-status-api
Qué esperar (sin salida — la imagen no aparece, aunque acabas de confirmar que sí existe en tu Docker del host):
(sin salida)
Ahí está el problema exacto que la lección 7 resuelve: tu Docker del host y el containerd interno de cada nodo son dos cachés de imágenes completamente separadas, aunque ambos corran, en última instancia, sobre la misma máquina física. Construir una imagen con docker build la deja disponible para tu Docker del host — nunca, de forma automática, para el containerd interno de un nodo de kind. Esa es, exactamente, la razón por la que existe el comando kind load docker-image, el protagonista de la próxima lección.
Analogía: la sala de control del estadio
Retomando la maqueta a escala del estadio de la lección 5: si el estadio completo es tu clúster andes-cargo-cluster, el plano de control es la sala de control del estadio — el lugar, físicamente separado de las gradas y del campo de juego, donde un equipo pequeño decide qué debería estar pasando (kube-scheduler decidiendo en qué sector sienta a cada grupo de espectadores nuevos), lleva el registro oficial de cada asiento asignado (etcd, el libro maestro), y vigila constantemente que la realidad coincida con lo planificado, corrigiendo sin que nadie se lo pida si algo se desvía (kube-controller-manager). kube-apiserver es la única ventanilla de esa sala de control que atiende al público — nadie entra directo a hablar con el libro maestro ni con el equipo de asignación, todo pasa por esa ventanilla única. Y kubelet, en cada sector de las gradas, es el personal de planta que de verdad ejecuta las instrucciones que bajan de la sala de control — abre la puerta correcta, acomoda a la gente, reporta si un asiento quedó vacío.
Errores comunes
Asumir que etcd es "solo una base de datos más" y que se puede reemplazar por cualquier otra sin consecuencia (conceptual). Qué pasa: alguien, familiarizado con bases de datos relacionales o documentales de propósito general, subestima el rol de etcd y piensa en él como un detalle de implementación intercambiable. Por qué pasa: "base de datos" suena genérico, y es fácil no notar que Kubernetes depende de garantías muy específicas de consistencia que etcd (un almacén clave-valor distribuido, con consenso vía el algoritmo Raft) ofrece por diseño. Cómo detectarlo: si crees que perder etcd sin respaldo sería "recuperable revisando los Pods que siguen corriendo". Cómo corregirlo: etcd es la única fuente de verdad del estado deseado del clúster — si se pierde sin respaldo, el clúster pierde la memoria completa de qué debería existir, aunque los Pods que ya estaban corriendo puedan seguir vivos por un tiempo. En un EKS real (Módulo 7), AWS administra y respalda etcd por ti — una de las razones concretas por las que el plano de control gestionado tiene un costo propio.
Confundir kube-scheduler con el proceso que "corre" los Pods (conceptual, el error más común de esta lección). Qué pasa: alguien lee "el scheduler decide dónde corre un Pod" y asume que el scheduler también lo arranca. Por qué pasa: en el lenguaje cotidiano, "decidir" y "ejecutar" se mezclan fácilmente. Cómo detectarlo: si no puedes explicar, sin dudar, la diferencia de trabajo entre kube-scheduler y kubelet. Cómo corregirlo: kube-scheduler solo asigna —decide en qué nodo debería correr un Pod nuevo, y se lo comunica a kube-apiserver—; quien de verdad lo arranca, lo monitorea, y reporta su estado real es kubelet, corriendo en el nodo específico donde el Pod fue asignado. Son dos responsabilidades separadas, en dos componentes distintos, a propósito.
Intentar depurar un problema de imagen con docker images cuando el problema está dentro del clúster (de flujo, directamente relacionado con la sección de contenedores anidados de esta lección). Qué pasa: alguien, en un módulo posterior, tiene un Pod con el error ImagePullBackOff, corre docker images en su host, ve que la imagen sí existe ahí, y concluye —equivocadamente— que el problema no puede ser de imagen. Por qué pasa: sin haber internalizado la distinción de esta lección entre el Docker del host y el containerd interno de cada nodo, es natural asumir que "la imagen existe en mi máquina" es suficiente. Cómo detectarlo: si tu evidencia de que "la imagen está disponible" viene solo de docker images, sin haber confirmado también con crictl images dentro del nodo específico. Cómo corregirlo: usa siempre docker exec <nombre-del-nodo> crictl images para confirmar qué imágenes están realmente disponibles dentro del clúster — la lección 7 de este módulo instala el hábito completo.
Ejercicios
Ejercicio 1 — Asigna cada componente a su trabajo. Sin mirar la lección, empareja cada uno de estos cinco componentes con su responsabilidad: kube-apiserver, etcd, kube-scheduler, kube-controller-manager, kubelet. Responsabilidades: (a) ejecuta el Pod en el nodo asignado; (b) guarda el estado deseado del clúster; (c) decide en qué nodo debería correr un Pod nuevo; (d) es la única puerta de entrada al clúster; (e) reconcilia continuamente la realidad contra lo declarado.
Ver solución
kube-apiserver → (d); etcd → (b); kube-scheduler → (c); kube-controller-manager → (e); kubelet → (a).
Ejercicio 2 — Explica por qué kube-apiserver es un punto de centralización, no un cuello de botella conceptual. En dos o tres frases, explica por qué el diseño de "una sola puerta de entrada" (kube-apiserver) es una decisión de seguridad, no solo una limitación técnica.
Ver solución
Una respuesta completa suena, más o menos, así: "Si cualquier componente pudiera escribir directo en etcd o hablar directo con otros componentes internos, no habría un solo punto donde aplicar autenticación, autorización y validación de forma consistente. Al forzar que absolutamente todo —kubectl, un operador como ArgoCD, cualquier admission controller— pase por kube-apiserver, Kubernetes garantiza que cada solicitud pasa por el mismo conjunto de controles, sin excepciones. Esto es exactamente lo que hace posible el admission control del Módulo 6: si hubiera múltiples puertas, un admission webhook solo podría vigilar una de ellas."
Ejercicio 3 — Predice el resultado de un comando nuevo. Sin ejecutarlo todavía, predice qué esperarías ver si corrieras docker exec andes-cargo-cluster-worker crictl images (nota: worker, no control-plane) contra tu clúster actual. ¿Esperarías ver las mismas imágenes que en el control plane?
Ver solución
No exactamente las mismas — esperarías ver kindnetd, local-path-helper (si ese nodo llegó a necesitarlo) y pause, porque kube-proxy y kindnet corren en todos los nodos, pero no esperarías ver etcd, kube-apiserver, kube-scheduler ni kube-controller-manager, porque esos cuatro componentes solo corren como Pods en el nodo control-plane, nunca en los nodos worker. Este es exactamente el patrón que confirmaste con kubectl get pods -A -o wide en esta lección: la columna NODE mostraba esos cuatro Pods únicamente en andes-cargo-cluster-control-plane.
Resumen y siguiente paso
En esta lección abriste la caja del clúster que creaste en la lección 5: cinco componentes del plano de control (kube-apiserver como única puerta de entrada, etcd como la única fuente de verdad, kube-scheduler decidiendo dónde, kube-controller-manager reconciliando sin que nadie lo pida) más kubelet y kube-proxy corriendo en cada nodo, confirmados con evidencia real de tu propio clúster (kubectl get pods -A, crictl images). Y descubriste, con evidencia literal, el detalle que hace posible la siguiente lección: el Docker de tu host y el containerd interno de cada nodo de kind son cachés de imágenes completamente separadas — construir una imagen no la hace automáticamente disponible dentro del clúster.
Antes de avanzar deberías poder: nombrar los cinco componentes del plano de control y la responsabilidad exacta de cada uno; explicar por qué kube-apiserver es la única puerta de entrada; y predecir, sin ejecutarlo, qué Pods de kube-system esperarías ver en un nodo worker frente a uno control-plane.
La lección 7 resuelve, de forma explícita y ejecutada, el vacío que acabas de confirmar: cómo llevar la imagen andes-cargo-status-api del Docker de tu host hacia adentro del clúster, con kind load docker-image.
Recursos
- Kubernetes — Kubernetes Components — referencia oficial completa de los cinco componentes del plano de control y de nodo cubiertos en esta lección.
- Kubernetes — Kubernetes Architecture — la visión general oficial de cómo se relacionan estos componentes entre sí.
- etcd — What is etcd? — documentación oficial del proyecto
etcd, independiente de Kubernetes pero adoptado como su almacén de estado. - kind — Design Principles — la explicación oficial del proyecto sobre por qué cada nodo corre su propio motor de contenedores interno.
- Kubernetes — Debugging Kubernetes Nodes With crictl — referencia oficial de
crictl, usado en esta lección para inspeccionar elcontainerdinterno de un nodo.