Si ya usas Docker, sabes empaquetar una aplicación en una imagen y arrancarla en un contenedor. Pero en producción surgen otras preguntas: ¿qué pasa si el contenedor se cae a las tres de la mañana?, ¿cómo pongo cinco copias si llega mucho tráfico?, ¿cómo actualizo sin dejar la web sin servicio? Kubernetes responde a todas: es un sistema que gobierna contenedores en uno o muchos equipos. Tú le describes cómo quieres que esté tu aplicación y él se encarga de que esté así, todo el tiempo. Esta guía lo explica desde cero, con esquemas y con un clúster de pruebas real.
Kubernetes en tres ideas
- Describes, no ordenas. Con Docker dices «arranca este contenedor». Con Kubernetes escribes en un archivo «quiero tres copias de esta imagen, escuchando en este puerto» y se lo entregas.
- Vigila sin parar. Kubernetes compara continuamente lo que pediste con lo que hay. Si falta una copia, la crea; si sobra, la quita. Por eso se dice que se «repara solo».
- Junta muchos equipos en uno. A un grupo de máquinas con Kubernetes se le llama clúster. Tú no eliges en qué máquina va cada contenedor: lo decide Kubernetes según el espacio libre.
El nombre viene del griego y significa «timonel». Lo creó Google a partir de su experiencia con Borg, el sistema con el que lleva años gobernando sus propios centros de datos, y lo publicó como código abierto en 2014. Hoy lo mantiene la Cloud Native Computing Foundation y lo ofrecen todas las grandes nubes. Como es una palabra larga, se abrevia K8s: la K, ocho letras y la s.
Docker y Kubernetes no compiten: trabajan juntos
Es la confusión más habitual. Docker y Kubernetes no hacen lo mismo:
- Docker construye imágenes (
docker build) y arranca contenedores en un equipo (docker run). - Kubernetes no construye nada: recibe imágenes ya hechas y decide cuántas copias arrancar, en qué equipos, cómo repartir el tráfico entre ellas y cómo sustituirlas cuando hay una versión nueva.
El flujo normal es: escribes tu Dockerfile, construyes la imagen con Docker, la subes a un registro (como Docker Hub o el de tu nube) y Kubernetes la descarga en cada nodo que la necesite. Si ya sabes Docker, tienes medio camino hecho: casi todo tiene su equivalente.
| Con Docker | Con Kubernetes | Para qué |
|---|---|---|
docker run saludo:1 | un Deployment | arrancar la app, aquí con el número de copias que quieras |
| el contenedor | el pod | lo que corre: uno o varios contenedores que viven juntos |
-p 8080:8000 | un Service | llegar a la app por un nombre y un puerto fijos |
--restart always | el propio Deployment | volver a arrancarla si se cae, aquí también si se cae el equipo |
-e o env_file | ConfigMap y Secret | pasar configuración y contraseñas |
-v datos:/var/lib/… | PersistentVolumeClaim | guardar datos que sobrevivan al contenedor |
compose.yaml | los manifiestos YAML | describir la aplicación entera en archivos |
docker ps · logs · exec | kubectl get pods · logs · exec | ver qué corre, leer su salida y entrar en él |
¿Kubernetes ya no usa Docker?
Quizá leíste en 2022 que «Kubernetes abandona Docker». Es verdad a medias. Para arrancar contenedores, cada nodo usa un motor de contenedores, y desde la versión 1.24 Kubernetes ya no habla directamente con Docker, sino con motores más ligeros como containerd, que por cierto es el mismo que Docker lleva por dentro. En nuestro clúster de pruebas el motor es containerd. Pero las imágenes que construyes con Docker funcionan igual, porque todas siguen el mismo estándar, el de la Open Container Initiative. Para ti no cambia nada: sigues usando Docker en tu equipo para construir y probar.
¿Lo necesitas?
No siempre. Kubernetes es potente, pero también complejo. Para una web pequeña o un proyecto personal, un servidor con Docker Compose suele bastar, como contamos en la guía de Docker. Kubernetes empieza a compensar cuando tienes varios servidores, varios servicios que se hablan entre sí, necesitas escalar según la demanda o no te puedes permitir cortes al actualizar. Y aunque no lo administres tú, es muy probable que tu empresa o tu nube lo usen por debajo: entender sus piezas te ayudará igual.
Las piezas que hay que conocer
- Clúster: el conjunto de equipos que gobierna Kubernetes.
- Nodo: cada uno de esos equipos. En cada nodo corre el kubelet, el agente que arranca los contenedores que le mandan.
- Plano de control: el cerebro. Recibe tus órdenes por una API, guarda el estado del clúster, decide en qué nodo va cada cosa y vigila que todo cuadre.
- Pod: la unidad mínima. Casi siempre es un contenedor; a veces, varios que tienen que ir juntos. Cada pod tiene su propia IP, pero es desechable: si muere, se crea otro con otro nombre y otra IP.
- Deployment: dice qué imagen usar y cuántas copias (réplicas) quieres, y sabe actualizarlas. Por debajo crea un ReplicaSet, que es el que mantiene el número exacto de pods.
- Service: un nombre y una IP fijos delante de un grupo de pods. Como los pods cambian, nadie les habla directamente: se le habla al Service y él reparte.
- ConfigMap y Secret: la configuración y las contraseñas, separadas de la imagen.
- kubectl: la herramienta de línea de órdenes con la que hablas con el clúster.
Cómo probarlo en tu equipo
No hace falta una nube ni varios servidores. Hay varias formas de tener un Kubernetes de un nodo en tu propio equipo, todas gratuitas:
| Herramienta | Cómo funciona |
|---|---|
| Docker Desktop | trae un Kubernetes que se activa en los ajustes; lo más fácil si ya lo usas en Windows o macOS |
| kind | crea clústeres cuyos nodos son contenedores de Docker; muy usado para pruebas |
| minikube | el clásico para aprender, en una máquina virtual o en un contenedor |
| k3s | un Kubernetes completo y ligero en un solo programa; el que usamos aquí |
Nosotros lo arrancamos así, como un contenedor de Docker más (necesita --privileged porque dentro va a crear sus propios contenedores) y le pasamos las dos imágenes de la app sin subirlas a ningún registro:
docker run -d --privileged --hostname nodo-1 --name k8s \ rancher/k3s:v1.37.1-k3s1 server --disable=traefik --disable=metrics-server docker save saludo:1 saludo:2 | docker exec -i k8s ctr images import - docker exec -it k8s sh
Dentro ya tenemos kubectl. Lo primero es ver el clúster:
kubectl get nodes kubectl version
salidaNAME STATUS ROLES AGE VERSION nodo-1 Ready control-plane 21s v1.37.1+k3s1 Client Version: v1.37.1+k3s1 Kustomize Version: v5.8.1 Server Version: v1.37.1+k3s1
Paso 1: la imagen, con Docker
La app de prueba es un servidor web de veinte líneas que responde con un mensaje, el nombre del contenedor en el que corre y su versión. Fíjate en las dos líneas de signal: cuando Kubernetes quiere parar un pod, primero le avisa con la señal SIGTERM y espera hasta 30 segundos. Un programa que la ignora tarda eso en morir cada vez que actualizas o escalas.
# Servidor mínimo para las pruebas de la guía de Kubernetes: responde con su versión, el nombre del
# contenedor (en Kubernetes, el del pod) y el mensaje que le llegue por la variable de entorno MENSAJE.
import os, signal, socket, sys
from http.server import BaseHTTPRequestHandler, HTTPServer
VERSION = os.environ.get("VERSION", "?")
class Saludo(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/salud":
cuerpo = "ok\n"
else:
cuerpo = f"{os.environ.get('MENSAJE', 'Hola')} desde {socket.gethostname()} (versión {VERSION})\n"
self.send_response(200)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(cuerpo.encode())
def log_message(self, formato, *args):
print(f"{self.address_string()} {formato % args}", flush=True)
# Kubernetes avisa con SIGTERM antes de parar un pod; sin esto, Python lo ignoraría y esperaría 30 s al SIGKILL.
signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))
print(f"saludo {VERSION} escuchando en el puerto 8000", flush=True)
HTTPServer(("0.0.0.0", 8000), Saludo).serve_forever()FROM python:3.13-slim
ARG VERSION=1
ENV VERSION=${VERSION}
WORKDIR /app
COPY app.py .
EXPOSE 8000
CMD ["python", "app.py"]Construimos dos versiones de la misma imagen, que solo cambian en la variable VERSION, para poder actualizar después:
docker build -t saludo:1 --build-arg VERSION=1 . docker build -t saludo:2 --build-arg VERSION=2 .
Con Docker, arrancar la app y hablarle es esto. Como nombre del equipo, la app ve el identificador del contenedor:
docker run -d --name saludo-docker saludo:1
docker exec saludo-docker python -c "import urllib.request; print(urllib.request.urlopen('http://localhost:8000').read().decode(), end='')"salidaHola desde aa4248725aca (versión 1)
Una copia, en un equipo, y si se cae, se queda caída. Vamos a darle lo mismo a Kubernetes.
Paso 2: el manifiesto, lo que quieres que haya
En Kubernetes todo se describe en archivos YAML llamados manifiestos. Pueden ir varios objetos en un archivo, separados por ---. Nuestro saludo.yaml tiene tres. El primero, la configuración:
apiVersion: v1 kind: ConfigMap metadata: name: saludo-config data: MENSAJE: "Hola"
El segundo, el Deployment. Es el más largo, pero se lee de arriba abajo: quiero 3 réplicas; los pods llevan la etiqueta app: saludo; cada uno tiene un contenedor con la imagen saludo:1 que escucha en el 8000 y toma sus variables del ConfigMap.
apiVersion: apps/v1
kind: Deployment
metadata:
name: saludo
spec:
replicas: 3
selector:
matchLabels:
app: saludo
template:
metadata:
labels:
app: saludo
spec:
containers:
- name: saludo
image: saludo:1
imagePullPolicy: Never
ports:
- containerPort: 8000
envFrom:
- configMapRef:
name: saludo-config
readinessProbe:
httpGet:
path: /salud
port: 8000
resources:
requests:
cpu: 50m
memory: 32Mi
limits:
memory: 64MiTres detalles que conviene entender:
imagePullPolicy: Neverle dice que no busque la imagen en internet, porque la importamos a mano. En un clúster de verdad se quita, y la imagen se descarga de un registro.- El
readinessProbees una prueba de disponibilidad: Kubernetes llama a/saludy no le manda tráfico al pod hasta que responde. Así nadie llega a una copia que todavía está arrancando. resourcesreserva un mínimo de CPU y memoria para que el planificador sepa en qué nodo cabe cada pod, y pone un tope de memoria: si lo supera, Kubernetes lo reinicia.
Y el tercero, el Service. Junta todos los pods con la etiqueta app: saludo bajo un solo nombre, saludo, en el puerto 80. Al ser de tipo NodePort, además abre el puerto 30080 en cada nodo para llegar desde fuera del clúster:
apiVersion: v1
kind: Service
metadata:
name: saludo
spec:
type: NodePort
selector:
app: saludo
ports:
- port: 80
targetPort: 8000
nodePort: 30080Paso 3: aplicarlo
kubectl apply le entrega el manifiesto al clúster. Y kubectl rollout status espera a que todo esté listo:
kubectl apply -f saludo.yaml kubectl rollout status deployment/saludo
salidaconfigmap/saludo-config created deployment.apps/saludo created service/saludo created Waiting for deployment "saludo" rollout to finish: 0 out of 3 new replicas have been updated... Waiting for deployment "saludo" rollout to finish: 0 of 3 updated replicas are available... Waiting for deployment "saludo" rollout to finish: 1 of 3 updated replicas are available... Waiting for deployment "saludo" rollout to finish: 2 of 3 updated replicas are available... deployment "saludo" successfully rolled out
Ya hay tres pods. Kubernetes les pone nombre a partir del Deployment, y cada uno tiene su IP dentro del clúster:
kubectl get pods -o wide
salidaNAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES saludo-7df8fd8d98-9ntnr 1/1 Running 0 1s 10.42.0.5 nodo-1 <none> <none> saludo-7df8fd8d98-ck5qw 1/1 Running 0 1s 10.42.0.6 nodo-1 <none> <none> saludo-7df8fd8d98-pql4h 1/1 Running 0 1s 10.42.0.7 nodo-1 <none> <none>
Y aquí se ve la cadena entera: el Deployment, el ReplicaSet que creó (el nombre de los pods empieza igual) y el Service, con su IP fija. El Service kubernetes viene de serie: es la API del propio clúster.
kubectl get deployment,replicaset,service
salidaNAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/saludo 3/3 3 3 2s NAME DESIRED CURRENT READY AGE replicaset.apps/saludo-7df8fd8d98 3 3 3 2s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 23s service/saludo NodePort 10.43.13.252 <none> 80:30080/TCP 2s
Paso 4: hablar con la app
Dentro del clúster, cualquier pod puede llamar al Service por su nombre, http://saludo: Kubernetes trae su propio DNS. Le hacemos seis peticiones desde uno de los pods:
kubectl exec deploy/saludo -- python -c "
import urllib.request
for _ in range(6): print(urllib.request.urlopen('http://saludo').read().decode(), end='')"salidaHola desde saludo-7df8fd8d98-9ntnr (versión 1) Hola desde saludo-7df8fd8d98-9ntnr (versión 1) Hola desde saludo-7df8fd8d98-9ntnr (versión 1) Hola desde saludo-7df8fd8d98-ck5qw (versión 1) Hola desde saludo-7df8fd8d98-pql4h (versión 1) Hola desde saludo-7df8fd8d98-9ntnr (versión 1)
Contestan los tres pods: el Service reparte las peticiones entre ellos. Para saber a quién reparte, mira sus endpoints, que son justo las IP de los pods que vimos antes:
kubectl get endpointslices -l kubernetes.io/service-name=saludo
salidaNAME ADDRESSTYPE PORTS ENDPOINTS AGE saludo-8nb9d IPv4 8000 10.42.0.5,10.42.0.7,10.42.0.6 2s
Desde fuera del clúster se entra por el puerto 30080 del nodo. Lo probamos desde otro contenedor:
python -c "import urllib.request; print(urllib.request.urlopen('http://IP-DEL-NODO:30080').read().decode(), end='')"salida, tres vecesHola desde saludo-7df8fd8d98-6mmkh (versión 1) Hola desde saludo-7df8fd8d98-xkhjh (versión 1) Hola desde saludo-7df8fd8d98-6mmkh (versión 1)
Para leer lo que escribe la app, kubectl logs, como docker logs. Si le pasas el Deployment, elige uno de sus pods:
kubectl logs deploy/saludo --tail=3
salidaFound 3 pods, using pod/saludo-7df8fd8d98-9ntnr 10.42.0.1 "GET / HTTP/1.1" 200 - 10.42.0.1 "GET / HTTP/1.1" 200 - 10.42.0.1 "GET / HTTP/1.1" 200 -
NodePort, sino con un Service de tipo LoadBalancer, que en las nubes crea un balanceador con IP pública, o con un Ingress o la nueva Gateway API, que reparten por dominio y ruta (/api a un servicio, / a otro) y se encargan del HTTPS.Paso 5: se repara solo
Aquí está la gran diferencia con Docker. Borramos uno de los pods, como si se hubiera caído:
kubectl delete pod saludo-7df8fd8d98-9ntnr --wait=false kubectl get pods
salida, un segundo despuéspod "saludo-7df8fd8d98-9ntnr" deleted from default namespace NAME READY STATUS RESTARTS AGE saludo-7df8fd8d98-9ntnr 0/1 Completed 0 4s saludo-7df8fd8d98-ck5qw 1/1 Running 0 4s saludo-7df8fd8d98-ph4sr 0/1 Running 0 1s saludo-7df8fd8d98-pql4h 1/1 Running 0 4s
El pod borrado está terminando (Completed) y ya hay uno nuevo, ph4sr, con un segundo de vida, arrancando: 0/1 significa que aún no ha pasado la prueba de disponibilidad. Al momento:
salidaNAME READY STATUS RESTARTS AGE saludo-7df8fd8d98-ck5qw 1/1 Running 0 4s saludo-7df8fd8d98-ph4sr 1/1 Running 0 1s saludo-7df8fd8d98-pql4h 1/1 Running 0 4s
Otra vez tres. Nadie se lo ha pedido: el ReplicaSet vio que había dos pods cuando el manifiesto dice tres, y creó otro. Es el bucle de control, la idea en la que se basa todo Kubernetes. Si en vez de un pod se cayera un nodo entero, haría lo mismo con todos sus pods, en los nodos que quedan.
Con Docker, en cambio, borrar el contenedor es definitivo:
docker rm -f saludo-docker docker ps -a --filter name=saludo-docker
salida: el segundo comando ya no muestra nadasaludo-docker
Por eso, en Kubernetes, para quitar algo de verdad no se borra el pod: se cambia el manifiesto o se borra el Deployment.
Paso 6: escalar
¿Llega más tráfico? Se cambia el número de réplicas. Con una orden, o editando replicas: en el YAML y volviendo a aplicarlo:
kubectl scale deployment saludo --replicas=5 kubectl get pods
salidadeployment.apps/saludo scaled NAME READY STATUS RESTARTS AGE saludo-7df8fd8d98-ck5qw 1/1 Running 0 5s saludo-7df8fd8d98-gsp7k 1/1 Running 0 1s saludo-7df8fd8d98-k67tf 1/1 Running 0 1s saludo-7df8fd8d98-ph4sr 1/1 Running 0 2s saludo-7df8fd8d98-pql4h 1/1 Running 0 5s
Dos pods nuevos en un segundo, y el Service ya reparte entre los cinco. Kubernetes también puede hacerlo solo según el uso de CPU, con un HorizontalPodAutoscaler, aunque eso necesita el servidor de métricas que en la prueba dejamos apagado. Después volvimos a tres réplicas.
Paso 7: actualizar sin cortes, y deshacer
Para pasar a la versión 2 basta con cambiar la imagen del Deployment:
kubectl set image deployment/saludo saludo=saludo:2 kubectl rollout status deployment/saludo
salidaWaiting for deployment "saludo" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "saludo" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "saludo" rollout to finish: 1 out of 3 new replicas have been updated... Waiting for deployment "saludo" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "saludo" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "saludo" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "saludo" rollout to finish: 2 out of 3 new replicas have been updated... Waiting for deployment "saludo" rollout to finish: 1 old replicas are pending termination... Waiting for deployment "saludo" rollout to finish: 1 old replicas are pending termination... Waiting for deployment "saludo" rollout to finish: 1 old replicas are pending termination... deployment "saludo" successfully rolled out
Kubernetes no para los tres pods a la vez: hace una actualización progresiva. Arranca uno nuevo, espera a que pase la prueba de disponibilidad y solo entonces quita uno viejo, y así hasta el final. Durante todo el proceso hay pods respondiendo.
las peticiones, ahora a la versión 2Hola desde saludo-85bcd4f69-rp9fr (versión 2) Hola desde saludo-85bcd4f69-rp9fr (versión 2) Hola desde saludo-85bcd4f69-rp9fr (versión 2)
Cada cambio queda apuntado como una revisión:
kubectl rollout history deployment/saludo
salidadeployment.apps/saludo REVISION CHANGE-CAUSE 1 <none> 2 <none>
¿Y si la versión nueva viene rota? Lo probamos pidiendo saludo:3, que no existe:
kubectl set image deployment/saludo saludo=saludo:3 kubectl get pods
salida, 20 segundos despuésNAME READY STATUS RESTARTS AGE saludo-5d5ff4b48d-j55v6 0/1 ErrImageNeverPull 0 20s saludo-85bcd4f69-hq6nz 1/1 Running 0 33s saludo-85bcd4f69-jcrnv 1/1 Running 0 23s saludo-85bcd4f69-rp9fr 1/1 Running 0 22s
El pod nuevo no arranca, así que Kubernetes no quita ninguno de los viejos: los tres de la versión 2 siguen atendiendo y la app no se entera.
las peticiones, mientras tantoHola desde saludo-85bcd4f69-rp9fr (versión 2) Hola desde saludo-85bcd4f69-hq6nz (versión 2) Hola desde saludo-85bcd4f69-jcrnv (versión 2)
Para saber qué le pasa a un pod, kubectl describe pod y mirar los eventos del final. Es la primera orden que hay que usar cuando algo no arranca:
salida de kubectl describe pod saludo-5d5ff4b48d-j55v6, solo los eventosEvents:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 20s default-scheduler Successfully assigned default/saludo-5d5ff4b48d-j55v6 to nodo-1
Warning ErrImageNeverPull 8s (x3 over 20s) kubelet spec.containers{saludo}: Container image "saludo:3" is not present with pull policy of Never
Warning Failed 8s (x3 over 20s) kubelet spec.containers{saludo}: Error: ErrImageNeverPullY para volver atrás, kubectl rollout undo recupera la revisión anterior, la versión 2:
kubectl rollout undo deployment/saludo
salida, y las peticiones despuésWarning: resource deployments/saludo was previously managed with 'kubectl apply'. Rolling back will not update the kubectl.kubernetes.io/last-applied-configuration annotation, which may cause unexpected behavior on future 'kubectl apply' operations. Consider using 'kubectl apply' with your previous configuration file instead. deployment.apps/saludo rolled back Hola desde saludo-85bcd4f69-hq6nz (versión 2) Hola desde saludo-85bcd4f69-hq6nz (versión 2) Hola desde saludo-85bcd4f69-rp9fr (versión 2)
El aviso es importante: rollout undo cambia el clúster, pero no tu archivo, que todavía dice saludo:1. En el día a día, la buena práctica es la contraria: cambiar siempre el YAML, guardarlo en Git y aplicarlo con kubectl apply. Así el archivo y el clúster nunca se contradicen.
Paso 8: cambiar la configuración
La configuración vive en el ConfigMap, no en la imagen. Cambiamos el mensaje sin reconstruir nada:
kubectl create configmap saludo-config --from-literal=MENSAJE="Buenas tardes" \ --dry-run=client -o yaml | kubectl apply -f - kubectl rollout restart deployment/saludo
salida, y las peticiones despuésconfigmap/saludo-config configured deployment.apps/saludo restarted Buenas tardes desde saludo-69988bf4cb-4wdsq (versión 2) Buenas tardes desde saludo-69988bf4cb-c95bl (versión 2) Buenas tardes desde saludo-69988bf4cb-c95bl (versión 2)
Las variables de entorno se leen al arrancar el contenedor, así que hay que reiniciar los pods; rollout restart lo hace de forma progresiva, igual que una actualización. Las contraseñas no van en un ConfigMap, sino en un Secret, que se usa igual pero se guarda aparte y se puede proteger mejor.
Para terminar, todo lo que creó el manifiesto se borra con el mismo archivo:
kubectl delete -f saludo.yaml
salidaconfigmap "saludo-config" deleted from default namespace deployment.apps "saludo" deleted from default namespace service "saludo" deleted from default namespace
Los errores que más vas a ver
Aparecen en la columna STATUS de kubectl get pods. Para cualquiera de ellos, el siguiente paso es kubectl describe pod (los eventos) y kubectl logs (lo que escribió la app).
| Estado | Qué significa | Qué mirar |
|---|---|---|
ImagePullBackOff · ErrImagePull | no puede descargar la imagen | el nombre y la etiqueta de la imagen, y si el registro es privado, sus credenciales |
ErrImageNeverPull | la imagen no está en el nodo y le prohibiste descargarla | la que vimos en la prueba: importar la imagen o quitar imagePullPolicy: Never |
CrashLoopBackOff | el contenedor arranca y se cae una y otra vez | kubectl logs --previous: casi siempre es un error de la app o una variable que falta |
Pending | no hay ningún nodo donde quepa | los eventos: suele faltar CPU o memoria para lo que pide resources |
OOMKilled | superó su límite de memoria y lo mataron | subir limits.memory o buscar la fuga de memoria |
Running pero 0/1 | arrancó, pero no pasa la prueba de disponibilidad | que la ruta y el puerto del readinessProbe sean los de la app |
Resumen
| Quiero… | Orden |
|---|---|
| aplicar o actualizar lo que dice un archivo | kubectl apply -f saludo.yaml |
| ver los pods, con su IP y su nodo | kubectl get pods -o wide |
| ver todo lo de un vistazo | kubectl get deployment,service,pods |
| saber por qué falla un pod | kubectl describe pod NOMBRE |
| leer lo que escribe la app | kubectl logs deploy/saludo |
| entrar en un pod o ejecutar algo en él | kubectl exec -it deploy/saludo -- sh |
| cambiar el número de copias | kubectl scale deployment saludo --replicas=5 |
| pasar a otra versión | kubectl set image deployment/saludo saludo=saludo:2 |
| seguir una actualización | kubectl rollout status deployment/saludo |
| volver a la versión anterior | kubectl rollout undo deployment/saludo |
| reiniciar los pods sin cortes | kubectl rollout restart deployment/saludo |
| llegar a un pod desde tu equipo para probar | kubectl port-forward svc/saludo 8080:80 |
| borrar lo que creó un archivo | kubectl delete -f saludo.yaml |
Esta guía es la continuación natural de la guía de Docker: allí aprendes a construir la imagen, aquí a ponerla a funcionar a escala. Si quieres entender qué pasa por debajo cuando un Service reparte tráfico o abre un puerto, tienes también la guía de redes básicas y la de DNS.
Las pruebas se ejecutaron el 15 de octubre de 2026 con Docker 29.8.1 y k3s v1.37.1 (Kubernetes 1.37.1) en un contenedor desechable, borrado al terminar junto con sus imágenes; las salidas están copiadas de esa ejecución, y los nombres de los pods y las IP cambian en cada una. La orden kubectl port-forward del resumen no se ejecutó. Conceptos, objetos y valores por omisión según la documentación oficial de Kubernetes; versiones vigentes en kubernetes.io/releases; el cambio de motor de contenedores, en las preguntas frecuentes sobre la retirada de dockershim.
Fuente: Kubernetes, «Overview»
