Módulo 2: Pods Deployments And Services
3. Deployments: ReplicaSets y el bucle de control declarativo
Descripción
La lección 2 dejó una pregunta abierta: si un Pod suelto no tiene ningún mecanismo de auto-reparación, ¿qué objeto sí lo tiene, y cómo funciona por dentro? Esta lección responde eso con precisión: un Deployment no "vigila" Pods de forma activa como si fuera un proceso separado corriendo un while true. Hace algo distinto, y más simple de lo que parece — declara un estado deseado, y un componente del plano de control (el controller-manager, que ya conociste por nombre en el Módulo 1, lección 6) lo compara contra la realidad, en un bucle que nunca se detiene, actuando solo cuando encuentra una diferencia.
Conexión con el módulo
Esta lección es la contraparte conceptual de la lección 5 (manos a la obra): aquí entiendes el mecanismo exacto detrás de "borro un Pod y alguien lo repone solo" — algo que vas a ver ejecutarse de verdad, con andes-cargo-status-api, dos lecciones más adelante. También es la primera vez en esta guía que ves con detalle el patrón que reaparece, con otro nombre, en el Módulo 5 completo: GitOps con ArgoCD es exactamente este mismo bucle de reconciliación, aplicado a un repositorio Git en vez de a un número de réplicas.
Declarativo frente a imperativo: la distinción que sostiene todo lo demás
Hasta ahora, en esta guía, usaste dos estilos distintos de decirle algo a una herramienta, aunque no se hayan nombrado así explícitamente:
- Imperativo: le dices al sistema qué hacer, paso a paso.
docker run -d --name andes-cargo-status-api ...es imperativo — es una orden única, ejecutada una vez, sin memoria de que la diste. Si el contenedor se cae, nadie vuelve a leer ese comando para recrearlo; tendrías que correrlo de nuevo, tú, a mano. - Declarativo: le dices al sistema qué estado final quieres, sin especificar los pasos para llegar ahí.
kind-config.yaml(Módulo 1) fue tu primer ejemplo — declaraste "quiero uncontrol-planey dosworker", ykinddecidió cómo llegar a eso. UnDeploymentes exactamente el mismo estilo, pero aplicado a Pods: declaras "quiero N réplicas de este Pod, corriendo esta imagen", y Kubernetes decide cómo mantener esa realidad, indefinidamente, sin que se lo vuelvas a pedir.
La diferencia no es solo de estilo de escritura — es de quién actúa cuando algo cambia. Con un comando imperativo, si la realidad se desvía de lo que pediste (el contenedor se cae), nadie hace nada hasta que tú das otra orden. Con un objeto declarativo, hay un proceso corriendo permanentemente dentro del clúster cuyo único trabajo es notar esa desviación y corregirla — sin que nadie la pida de nuevo.
El bucle de control: cómo el controller-manager reconcilia solo
El componente responsable de este comportamiento es el kube-controller-manager, uno de los procesos del plano de control que ya viste correr como contenedor Docker en el Módulo 1, lección 6. Dentro de él corren decenas de controladores independientes, cada uno responsable de un tipo de objeto distinto — el que te importa en esta lección es el controlador de Deployment (que, a su vez, delega buena parte del trabajo a un controlador de ReplicaSet, ver la sección siguiente).
Cada controlador implementa el mismo patrón, llamado bucle de reconciliación (reconciliation loop), y vale la pena verlo explícito, porque es el patrón de diseño más importante de todo Kubernetes:
EL BUCLE DE RECONCILIACIÓN (nunca se detiene)
┌──────────────────────────────────────────────────────┐
│ │
▼ │
1. Leer el estado DESEADO │
(el manifiesto que declaraste: "quiero 3 réplicas") │
│ │
▼ │
2. Leer el estado ACTUAL │
(cuántos Pods con esa etiqueta existen de verdad, ahora mismo) │
│ │
▼ │
3. Comparar ambos │
│ │
├── ¿Coinciden? ──▶ No hacer nada, esperar, volver al paso 1
│
└── ¿No coinciden? ──▶ 4. Actuar para acercar el estado
actual al deseado (crear Pods
faltantes, o eliminar Pods de más)
│
└──────────────▶ volver al paso 1
No hay ningún paso de "avisar a alguien" ni "pedir permiso" — el bucle actúa solo, todo el tiempo, con una frecuencia de segundos. Esto explica un comportamiento que vas a confirmar con tus propias manos en la lección 5: cuando borras un Pod administrado por un Deployment, no hay ningún retraso perceptible antes de ver el reemplazo — el controlador ya estaba corriendo el bucle, y detecta la diferencia en la siguiente vuelta, casi de inmediato.
Deployment y ReplicaSet: dos objetos, una sola decisión que tú tomas
La lección 2 ya mostró la jerarquía completa (Deployment → ReplicaSet → Pod); esta sección explica por qué existen dos objetos separados en vez de uno solo, porque no es una complicación gratuita:
ReplicaSetes el objeto que, literalmente, mantiene un número de réplicas de un Pod con una plantilla fija. Su bucle de reconciliación es simple: cuenta los Pods con la etiqueta que le indicaste, y crea o borra hasta que el número coincida conreplicas.Deploymentes una capa encima deReplicaSet, y agrega algo queReplicaSetno sabe hacer por sí solo: administrar cambios en la plantilla del Pod a lo largo del tiempo — por ejemplo, actualizar la imagen a una versión nueva sin tumbar el servicio de golpe. Cuando cambias algo en la plantilla de unDeployment, este no modifica elReplicaSetexistente — crea uno nuevo, con la plantilla actualizada, y mueve Pods delReplicaSetviejo al nuevo de forma gradual (RollingUpdate, el comportamiento por defecto — vas a profundizar en estrategias de despliegue en el Módulo 5, lección 7, cuando entre ArgoCD en escena).
En la práctica, casi nunca vas a crear un ReplicaSet a mano — declaras un Deployment, y Kubernetes crea el ReplicaSet automáticamente, con un nombre derivado (el prefijo del Deployment, más un sufijo con hash — vas a ver este patrón exacto en la lección 5). Esta guía nunca declara un ReplicaSet directamente por esta misma razón: es un objeto real, que existe y puedes inspeccionar, pero no es la interfaz que usas para trabajar con él.
QUÉ CAMBIA SI ACTUALIZAS LA IMAGEN DE UN Deployment
Antes Después de kubectl apply (imagen nueva)
Deployment Deployment
│ │
▼ ▼
ReplicaSet-abc123 (3/3) ReplicaSet-abc123 (0/3) ── vaciado gradual
│ │
├── Pod-abc123-x1 ReplicaSet-def456 (3/3) ── NUEVO, llenado gradual
├── Pod-abc123-x2 │
└── Pod-abc123-x3 ├── Pod-def456-y1
├── Pod-def456-y2
└── Pod-def456-y3
Este mecanismo —dos ReplicaSet, uno vaciándose mientras el otro se llena— es exactamente lo que hace posible un RollingUpdate sin caída de servicio: en todo momento hay al menos algunas réplicas respondiendo, nunca las tres apagadas a la vez.
Anatomía mínima de un Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: <nombre-del-deployment>
namespace: <namespace>
spec:
replicas: <N>
selector:
matchLabels:
app: <etiqueta>
template:
metadata:
labels:
app: <etiqueta>
spec:
containers:
- name: <nombre-del-contenedor>
image: <imagen:tag>
ports:
- containerPort: <puerto>
Fíjate en dos cosas que no aparecían en el manifiesto de Pod de la lección 2:
apiVersion: apps/v1, nov1.Deploymentvive en el grupo de APIapps, junto conReplicaSet,StatefulSety otros controladores de carga de trabajo — un grupo distinto del grupo "core" (v1) donde viven Pod, Service, Namespace.spec.templatecontiene, dentro de sí, la misma estructura que un manifiesto de Pod completo (metadata.labels+spec.containers) — no es casualidad. ElDeploymentno inventa una forma nueva de describir un Pod, reutiliza exactamente la misma plantilla que ya conoces, y la usa como "molde" para cada réplica que crea.spec.selector.matchLabelsdebe coincidir, exactamente, conspec.template.metadata.labels. Si no coinciden,kubectl applyrechaza el manifiesto — es la forma en que Kubernetes garantiza que elDeploymentsiempre sepa reconocer cuáles Pods son "suyos".
Analogía: el termostato, no el técnico que ajusta el aire manualmente
Retomando la analogía del edificio de oficinas: un Deployment es el termostato, no la persona que sube y baja el aire acondicionado a mano cada vez que alguien se queja de frío. Configuras el termostato una sola vez —"mantén esta temperatura"— y a partir de ahí, el sistema reacciona solo cada vez que la temperatura real se desvía, sin que nadie tenga que notarlo ni pedirlo de nuevo. Un ReplicaSet, en esta misma analogía, es el compresor físico que el termostato enciende y apaga — la pieza que ejecuta la corrección, mientras el termostato es quien decide cuándo corregir y por cuánto. Tú nunca le hablas directamente al compresor; le hablas al termostato, y el resto pasa solo — exactamente la misma relación que tienes con un Deployment frente a su ReplicaSet.
Profundización: por qué esto es el mismo patrón que vas a ver en GitOps (Módulo 5)
Vale la pena adelantar esta conexión, porque cambia cómo vas a leer el Módulo 5 completo: ArgoCD hace, sobre un repositorio Git entero, exactamente lo mismo que el controller-manager hace sobre un Deployment. Lee el estado deseado (los manifiestos en Git), lee el estado actual (los objetos que existen de verdad en el clúster), compara, y actúa solo si hay diferencia — el mismo bucle de tres pasos de esta lección, con "Git" en el lugar de "el manifiesto de Deployment" y "el clúster completo" en el lugar de "los Pods de un ReplicaSet". Cuando llegues al Módulo 5 y veas ArgoCD "convergiendo el clúster hacia lo que Git declara", no vas a estar aprendiendo un concepto nuevo — vas a estar viendo el mismo bucle de reconciliación de esta lección, aplicado a una escala mucho mayor.
Errores comunes
Creer que el Deployment "revisa" activamente cada cierto número de segundos, como si corriera un temporizador (conceptual, una simplificación que casi siempre es inofensiva, pero rompe la intuición en casos límite). Qué pasa: alguien imagina el bucle de reconciliación como un cron que corre cada N segundos, y se sorprende de la rapidez con la que un Deployment reacciona a un cambio —o, al revés, espera una reacción instantánea siempre—. Por qué pasa: "bucle que se repite" suena a temporizador, pero el mecanismo real es más parecido a un sistema orientado a eventos: los controladores reaccionan tanto a cambios notificados por el kube-apiserver (casi inmediato) como a una resincronización periódica de respaldo (con un intervalo más largo, del orden de minutos, para casos donde una notificación se perdió). Cómo detectarlo: si observas una reacción casi instantánea a un cambio y asumes que "siempre es así de rápido", sin importar la causa del desvío. Cómo corregirlo: para lo que necesitas en esta guía, basta con saber que el bucle reacciona rápido a la mayoría de los cambios (incluido borrar un Pod, que vas a confirmar en la lección 5) — el mecanismo exacto de notificación por eventos está documentado a fondo en la referencia oficial de controladores (ver Recursos), si quieres profundizar más allá de lo que esta guía cubre.
Editar un ReplicaSet directamente, en vez de editar el Deployment que lo creó (de flujo, específico de quien ya tiene algo de experiencia con kubectl). Qué pasa: alguien encuentra el ReplicaSet con kubectl get replicasets, y edita su número de réplicas ahí directamente, en vez de en el Deployment. El cambio parece funcionar por un momento, pero el Deployment lo revierte poco después, sin ningún mensaje de error visible. Por qué pasa: el ReplicaSet sí es un objeto real y editable — pero el Deployment es su fuente de verdad, y en cuanto el bucle de reconciliación del Deployment corre de nuevo, sobrescribe cualquier cambio hecho directamente sobre el ReplicaSet, porque desde su perspectiva el ReplicaSet "debería" seguir viendo la plantilla que el Deployment declaró. Cómo detectarlo: un cambio en el ReplicaSet que se revierte solo, sin que nadie lo haya tocado de nuevo. Cómo corregirlo: cualquier cambio de estado deseado —número de réplicas, imagen, variables de entorno— se hace siempre sobre el Deployment, nunca sobre el ReplicaSet que crea. Esta guía nunca edita un ReplicaSet directamente, ni una sola vez, por esta razón exacta.
Asumir que "declarativo" significa que Kubernetes valida que el estado deseado tenga sentido de negocio (conceptual). Qué pasa: alguien declara replicas: 3 con una imagen que no existe, o con un puerto equivocado, y espera que Kubernetes "rechace" el manifiesto por ser incorrecto de alguna forma más profunda que un error de sintaxis YAML. Por qué pasa: "declarativo" suena a "el sistema entiende lo que quiero", pero en realidad el bucle de reconciliación intenta cumplir el estado deseado tal como se declaró, sin juzgar si tiene sentido — si la imagen no existe, el resultado no es un rechazo, es un Pod atascado en ImagePullBackOff (el error exacto que ya viste nombrado en el Módulo 1, lección 7), reintentando indefinidamente. Cómo detectarlo: un Deployment que muestra 0/3 en READY durante mucho más tiempo del esperado. Cómo corregirlo: kubectl describe pod <nombre> (ya lo usaste en la lección 4) siempre muestra, en su sección Events, la razón exacta por la que un Pod no está listo — el bucle de reconciliación intenta cumplir el estado deseado, pero no puede adivinar que ese estado tiene un error, solo reportarlo.
Ejercicios
Ejercicio 1 — Explica el bucle de reconciliación sin el diagrama. Sin volver a mirar la sección correspondiente, describe en tus propias palabras los tres pasos del bucle de reconciliación, y qué pasa en cada uno de los dos caminos posibles del paso de comparación.
Ver solución
- Leer el estado deseado (el manifiesto declarado, por ejemplo
replicas: 3). - Leer el estado actual (cuántos Pods con la etiqueta correspondiente existen de verdad, ahora mismo).
- Comparar: si coinciden, no se hace nada y el bucle vuelve a empezar; si no coinciden, el controlador actúa (crea Pods faltantes o elimina Pods de más) para acercar el estado actual al deseado, y el bucle vuelve a empezar de inmediato.
Este bucle nunca se detiene mientras el Deployment exista — es la razón técnica exacta detrás de "Kubernetes se auto-repara".
Ejercicio 2 — Predice el resultado de una edición directa. Con lo que aprendiste en "Errores comunes", predice qué pasaría si editaras directamente, con kubectl edit replicaset <nombre>, el número de réplicas de un ReplicaSet creado por un Deployment con replicas: 3 declarado. ¿El cambio se mantiene? Justifica tu respuesta.
Ver solución
No se mantiene, o se mantiene solo momentáneamente. El Deployment sigue siendo la fuente de verdad para ese ReplicaSet — en cuanto el bucle de reconciliación del Deployment corre de nuevo (casi inmediato), nota que el ReplicaSet que administra no coincide con lo que el Deployment declara, y lo corrige de vuelta a replicas: 3. Cualquier cambio de estado deseado tiene que hacerse sobre el Deployment, no sobre el ReplicaSet que este crea.
Ejercicio 3 — Conecta el patrón con GitOps. Sin volver a la sección de profundización, explica en dos o tres frases por qué "ArgoCD converge el clúster hacia lo que Git declara" (Módulo 5) es, en esencia, el mismo mecanismo que esta lección explica para un Deployment.
Ver solución
Ambos son bucles de reconciliación: leen un estado deseado (el manifiesto de Deployment, o el contenido de un repositorio Git), leen el estado actual (los Pods vivos, o los objetos reales del clúster), y actúan solo cuando hay una diferencia entre ambos — sin que nadie lo pida explícitamente cada vez. La diferencia es de escala y de qué se declara: un Deployment reconcilia el número de réplicas de un solo tipo de Pod; ArgoCD reconcilia el clúster completo contra todo lo que un repositorio Git declara. El principio de fondo —comparar deseado contra actual, actuar sin intervención— es idéntico.
Resumen y siguiente paso
En esta lección entendiste el mecanismo exacto detrás de la auto-reparación de Kubernetes: la distinción entre imperativo y declarativo, el bucle de reconciliación de tres pasos que corre permanentemente dentro del controller-manager, y por qué Deployment y ReplicaSet son dos objetos separados —uno que administra cambios de plantilla a lo largo del tiempo, otro que mantiene un número fijo de réplicas de una plantilla dada. También viste, por adelantado, que este mismo patrón reaparece en el Módulo 5 con ArgoCD, a una escala mayor.
Antes de avanzar deberías poder: explicar la diferencia entre un comando imperativo y un objeto declarativo con un ejemplo propio; describir los tres pasos del bucle de reconciliación de memoria; y explicar por qué editar un ReplicaSet directamente no produce un cambio duradero mientras el Deployment que lo creó siga declarando algo distinto.
Siguiente lección: manos a la obra, tu primer Pod. Ahí confirmas, con evidencia real, la mitad de este contraste — un Pod suelto, sin ningún Deployment detrás, que nadie repone cuando lo borras. La lección 5 confirma la otra mitad: el mismo comportamiento, pero con un Deployment de por medio, corriendo andes-cargo-status-api.
Recursos
- Kubernetes — Deployments — la referencia oficial completa, incluida la mecánica de
RollingUpdatecon dosReplicaSet. - Kubernetes — ReplicaSet — referencia oficial, incluida la advertencia explícita de la documentación sobre por qué casi nunca se declara uno directamente.
- Kubernetes — Controllers — la explicación oficial del patrón de bucle de control (control loop), la base conceptual de esta lección completa.