Introducción
Los Services proporcionan a los clientes una dirección estable para un conjunto de Pods. Esto resulta especialmente útil cuando cambia la demanda: un Deployment puede añadir o eliminar réplicas mientras la identidad del Service permanece intacta.
En este laboratorio, cada backend devuelve el nombre de host de su propio Pod. Escalarás de dos réplicas a cuatro, enviarás solicitudes independientes a través de un único Service y verás respuestas procedentes de varios Pods. Después volverás a reducir la escala a dos y observarás cómo tanto el Deployment como el EndpointSlice convergen hacia el nuevo estado deseado.
Este es un escalado horizontal manual. El escalado automático mediante HPA depende de las solicitudes de recursos, las métricas y una política de control, por lo que conviene abordarlo después de comprender por completo el comportamiento manual de las réplicas.
Crear una aplicación replicada observable
Inicio del entorno: Este laboratorio inicia un clúster de Kubernetes completo. La configuración del plano de control, el nodo y los componentes de red suele tardar 2–3 minutos. Espera pacientemente a que el entorno termine de cargarse antes de comenzar.
En este paso, crearás dos backends que muestran qué Pod gestionó cada solicitud. Así, la distribución del tráfico del Service será visible en lugar de quedar oculta tras una abstracción.
El primer comando utiliza cd para entrar en el espacio de trabajo preparado. El siguiente emplea un documento aquí (here-document): cat <<'EOF' > hostname-web.yaml escribe en el archivo YAML todas las líneas posteriores hasta el EOF de cierre, reemplazando cualquier contenido anterior.
cd /home/labex/project/scale-lab
cat <<'EOF' > hostname-web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hostname-web
spec:
replicas: 2
selector:
matchLabels:
app: hostname-web
template:
metadata:
labels:
app: hostname-web
spec:
containers:
- name: web
image: busybox:1.36
imagePullPolicy: IfNotPresent
command: ["sh", "-c"]
args:
- mkdir -p /www; hostname > /www/index.html; exec httpd -f -p 8080 -h /www
ports:
- name: http
containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: hostname-web
spec:
selector:
app: hostname-web
ports:
- name: http
port: 80
targetPort: http
EOF
kubectl apply -f hostname-web.yaml
kubectl rollout status deployment/hostname-web --timeout=60s
kubectl get pods -l app=hostname-web
Después del EOF de cierre, kubectl apply -f envía cada objeto del archivo al servidor de API. rollout status espera hasta 60 segundos a que estén listos los dos Pods deseados, y -l app=hostname-web muestra únicamente los Pods que tienen esa etiqueta.
Dentro del comando del contenedor, los puntos y coma ejecutan las acciones en secuencia: crear /www, redirigir el nombre de host a index.html y usar exec para convertir el servidor web en el proceso principal del contenedor. Los nombres de los dos Pods son distintos, por lo que cada backend devuelve un valor de página diferente.
Establecer los backends de referencia
En este paso, relacionarás el número de réplicas del Deployment con el número de backends listos del Service.
Consulta tres vistas relacionadas. get deployment muestra las réplicas deseadas y listas; -l selecciona los Pods de la aplicación y -o wide añade las columnas de IP y nodo; el selector de etiquetas final encuentra el EndpointSlice creado para este Service:
kubectl get deployment hostname-web
kubectl get pods -l app=hostname-web -o wide
kubectl get endpointslices -l kubernetes.io/service-name=hostname-web
El Deployment mostrará 2/2, y el EndpointSlice mostrará dos direcciones. Solicita varias veces el Service desde un único Pod cliente de larga duración.
kubectl run crea un Pod independiente porque se ha definido --restart=Never. El separador -- indica el final de las opciones de kubectl; sleep 3600 es el comando del contenedor que lo mantiene activo. El bucle for utiliza seq 1 6 para generar seis iteraciones, y kubectl exec POD -- COMMAND ejecuta wget dentro del cliente cada vez:
kubectl run load-client --image=busybox:1.36 --image-pull-policy=IfNotPresent --restart=Never -- sleep 3600
kubectl wait --for=condition=Ready pod/load-client --timeout=30s
for i in $(seq 1 6); do kubectl exec load-client -- wget -qO- http://hostname-web; done
Cada respuesta es el nombre de un Pod. En una muestra breve puede que veas uno o ambos nombres; la distribución del Service no garantiza un orden estrictamente rotatorio.
Escalar declarativamente
En este paso, cambiarás el estado deseado guardado de dos réplicas a cuatro. Editar el manifiesto mantiene el archivo y el objeto activo sincronizados.
sed -i 's/old/new/' file reemplaza directamente en un archivo el texto coincidente. Después, grep -n busca replicas: y muestra su número de línea, lo que permite comprobar rápidamente el cambio antes de aplicarlo:
cd /home/labex/project/scale-lab
sed -i 's/replicas: 2/replicas: 4/' hostname-web.yaml
grep -n 'replicas:' hostname-web.yaml
kubectl apply -f hostname-web.yaml
kubectl rollout status deployment/hostname-web --timeout=60s
kubectl get deployment hostname-web
El Deployment debería mostrar 4/4. El controlador creó dos Pods adicionales porque el estado actual estaba por debajo del nuevo estado deseado.
Observar cómo se amplía el conjunto de backends del Service
En este paso, comprobarás que el Service, que no ha cambiado, descubre automáticamente los nuevos Pods.
El comando de EndpointSlice utiliza JSONPath porque la tabla normal puede abreviar algunos detalles. range repite la plantilla para cada endpoint; cada repetición imprime su primera dirección, la palabra literal ready, el valor de disponibilidad y un salto de línea. La barra invertida permite continuar un mismo comando de shell en varias líneas visuales:
kubectl get pods -l app=hostname-web -o wide
kubectl get endpointslices -l kubernetes.io/service-name=hostname-web \
-o jsonpath='{range .items[*].endpoints[*]}{.addresses[0]}{" ready="}{.conditions.ready}{"\n"}{end}'
Ahora hay cuatro direcciones listas. No has editado el Service: su selector sigue coincidiendo con todos los Pods listos que tienen app=hostname-web.
Compara la IP estable del Service con el conjunto ampliado de backends:
kubectl get service hostname-web -o wide
El ClusterIP permanece estable, mientras cambia la pertenencia al EndpointSlice.
Observar cómo las solicitudes llegan a varios Pods
En este paso, enviarás solicitudes HTTP independientes a través de un único Service y resumirás qué nombres de host de Pod responden.
Primero, rm -f elimina un archivo de resultados anterior si existe; -f también evita errores cuando el archivo no está presente. El bucle envía 20 solicitudes. >> añade cada nombre de host sin reemplazar los resultados anteriores. Por último, la tubería envía las líneas ordenadas a uniq -c, que agrupa los duplicados adyacentes y antepone a cada nombre de host su cantidad:
rm -f /tmp/hostname-responses.txt
for i in $(seq 1 20); do
kubectl exec load-client -- wget -qO- http://hostname-web >> /tmp/hostname-responses.txt
done
sort /tmp/hostname-responses.txt | uniq -c
Deberías ver más de un nombre de host. Las cantidades pueden ser desiguales, y una ejecución breve no tiene por qué alcanzar los cuatro backends. El enrutamiento de los Services de Kubernetes distribuye las conexiones, pero no garantiza una secuencia perfectamente uniforme ni ordenada.
Confirma cuántos backends únicos alcanzó tu muestra. sort -u conserva una sola copia de cada nombre de host, la tubería envía esas líneas a wc -l, y wc -l cuenta las líneas:
sort -u /tmp/hostname-responses.txt | wc -l
Un valor superior a uno demuestra directamente que la dirección estable del Service enrutó las solicitudes a varios Pods.
Reducir la escala de forma imperativa
En este paso, utilizarás kubectl scale para realizar un ajuste rápido del estado activo, pasando de cuatro réplicas a dos.
kubectl scale cambia inmediatamente el número deseado de réplicas del Deployment activo. --replicas=2 proporciona el nuevo número; no modifica tu archivo YAML:
kubectl scale deployment/hostname-web --replicas=2
kubectl rollout status deployment/hostname-web --timeout=60s
kubectl get pods -l app=hostname-web
Kubernetes terminará dos Pods y conservará otros dos. Espera hasta que el conjunto de backends del Service también converja.
Este bucle de sondeo limitado realiza como máximo 30 intentos. En cada pasada, guarda en count el número de endpoints listos; jq filtra el JSON del EndpointSlice para seleccionar los endpoints listos y devuelve la longitud del arreglo. [ "$count" -eq 2 ] es una prueba numérica del shell, && break sale del bucle cuando la condición se cumple y sleep 1 espera un segundo antes de volver a intentarlo:
for i in $(seq 1 30); do
count=$(kubectl get endpointslices -l kubernetes.io/service-name=hostname-web -o json | jq '[.items[].endpoints[] | select(.conditions.ready == true)] | length')
[ "$count" -eq 2 ] && break
sleep 1
done
echo "Ready backends: $count"
El Deployment activo ahora solicita dos réplicas, pero el archivo todavía indica cuatro. Esta diferencia es intencionada para el siguiente paso.
Reconciliar el manifiesto y consultar el historial del controlador
En este paso, harás que el manifiesto guardado coincida con el estado activo de dos réplicas y relacionarás las acciones de escalado con las evidencias del controlador.
Primero compara los dos estados deseados. grep -n muestra la línea guardada; JSONPath extrae únicamente el campo .spec.replicas del estado activo y añade un salto de línea:
grep -n 'replicas:' /home/labex/project/scale-lab/hostname-web.yaml
kubectl get deployment hostname-web -o jsonpath='Live replicas: {.spec.replicas}{"\n"}'
Cambia el archivo de cuatro réplicas a dos y aplícalo:
sed -i 's/replicas: 4/replicas: 2/' /home/labex/project/scale-lab/hostname-web.yaml
kubectl apply -f /home/labex/project/scale-lab/hostname-web.yaml
Como el estado activo ya tiene dos réplicas, esta aplicación no debería crear más Pods. Consulta los eventos del Deployment. La tubería pasa toda la salida de describe a sed -n; /Events:/,$p significa “imprimir desde la línea que contiene Events: hasta el final”:
kubectl describe deployment hostname-web | sed -n '/Events:/,$p'
Busca mensajes ScalingReplicaSet que muestren las decisiones de aumentar y reducir la escala. Por último, elimina el cliente temporal. --ignore-not-found permite que la limpieza finalice correctamente aunque el Pod ya haya desaparecido:
kubectl delete pod load-client --ignore-not-found
El escalado manual cambia el número deseado de réplicas; el controlador del Deployment crea o termina Pods, y el Service realiza un seguimiento automático de los miembros listos. Mantener el manifiesto sincronizado evita que un kubectl apply posterior restaure inesperadamente un número antiguo.
Resumen
Has escalado manualmente un Deployment de dos réplicas a cuatro y después de nuevo a dos. Has observado cómo el controlador reconcilia los Pods, cómo la pertenencia al EndpointSlice sigue a los backends listos sin modificar el Service y cómo una única dirección del Service enruta solicitudes independientes a varios Pods.
El modelo mental fundamental es el siguiente: el número de réplicas representa el estado deseado, el controlador del Deployment reconcilia los Pods reales y el Service sigue a los backends etiquetados que están listos. Los archivos declarativos deben reconciliarse con los cambios realizados deliberadamente en el estado activo para que las futuras aplicaciones sigan siendo predecibles.


