Kubernetes: orquestación de contenedores
Docker ejecuta contenedores; Kubernetes los orquesta: escala, balancea tráfico, reinicia fallos y despliega versiones sin tiempo de inactividad. Aprende la arquitectura, los objetos principales y el flujo de trabajo con kubectl.
¿Qué es Kubernetes?
Kubernetes (k8s) es un orquestador de contenedores open-source nacido de la experiencia de Google con Borg. En lugar de que tú decidas dónde ejecutar cada contenedor, le declaras el estado deseado: "3 réplicas de mi-api con la imagen v1.2" — y el sistema trabaja continuamente para que la realidad coincida con esa declaración.
De ahí lo de declarativo: no le dices "crea esto", "borra aquello", sino cómo quieres que sea el sistema al final. Si un contenedor muere, k8s crea otro sin que lo pidas. Si pides 5 réplicas, ajusta hasta tener 5.
Arquitectura
Un clúster tiene dos planos. El control plane es el cerebro: toma decisiones globales. Los nodes (o workers) son las máquinas que ejecutan tus aplicaciones. El control plane suele ser el doble de disponible que los workers porque su caída paraliza el clúster.
Componentes del control plane
kube-apiserver: única puerta de entrada al clúster. Toda comunicación (kubectl, otros componentes) pasa por esta API REST.
etcd: base de datos clave-valor distribuida que guarda todo el estado del clúster. Lo que no está en etcd no existe.
kube-scheduler: decide en qué node colocar cada nuevo pod según recursos disponibles, afinidades y restricciones.
kube-controller-manager: ejecuta los controladores que reconcilian el estado deseado (replicas, deployments, jobs...).
Componentes del node
kubelet: agente que vive en cada node. Garantiza que los pods asignados estén corriendo y reporta su estado al apiserver.
kube-proxy: gestiona las reglas de red que enrutan el tráfico a los pods.
Container runtime: el motor que ejecuta contenedores (containerd, CRI-O). Docker no corre pods directamente en k8s moderno, pero las imágenes que construyes con Docker son las mismas que corre el runtime.
Instalar minikube y kubectl
Para practicar localmente necesitas un clúster de una sola máquina (minikube o kind) y el cliente kubectl:
# macOS con Homebrew
brew install minikube kubectl
# Arrancar el clúster local (descarga una imagen de VM la primera vez)
minikube start
# Verificar que kubectl habla con el clúster
kubectl cluster-info
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# minikube Ready control-plane ... v1.30.0kubectl no es el clúster: es solo el cliente. Sin un clúster al que apuntar (minikube en local, EKS, GKE o K3s en la nube), los comandos fallan con "Unable to connect to the server". minikube start lo levanta en tu máquina.Pods
El pod es la unidad mínima de computación en k8s: uno o más contenedores que comparten red y almacenamiento. En la práctica, casi siempre es un único contenedor por pod. Los pods son efímeros — no los crees a mano en producción; se gestionan con workloads:
# Crear un pod directamente (solo para probar)
kubectl run nginx --image=nginx:alpine
# Estado de los pods
kubectl get pods
kubectl get pods -o wide # con el node y la IP
# Detalle completo en YAML
kubectl describe pod nginx
# Logs e inspección
kubectl logs nginx
kubectl exec -it nginx -- sh
# Acceder a un puerto del pod desde tu máquina (debug)
kubectl port-forward pod/nginx 8080:80
curl http://localhost:8080
kubectl delete pod nginxkubectl run es cómodo para probar, pero los pods así creados no se auto-reparan: si mueren, nadie los recrea. Para eso existen los controllers, empezando por el Deployment.
Deployments
Un Deployment gestiona un conjunto de réplicas de un pod, declarando el estado deseado. Es el objeto que usarás el 90% del tiempo para aplicaciones stateless:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mi-api
labels:
app: mi-api
spec:
replicas: 3
selector:
matchLabels:
app: mi-api
template:
metadata:
labels:
app: mi-api
spec:
containers:
- name: mi-api
image: miusuario/mi-api:v1
ports:
- containerPort: 3000kubectl apply -f deployment.yaml
kubectl get deployments
kubectl get pods
# 3 pods con prefijo mi-api-...
# Escalar
kubectl scale deployment mi-api --replicas=5
# Desplegar una nueva imagen (rolling update)
kubectl set image deployment/mi-api mi-api=miusuario/mi-api:v2
# Ver el rollout y volver atrás si algo falla
kubectl rollout status deployment/mi-api
kubectl rollout history deployment/mi-api
kubectl rollout undo deployment/mi-apiEl Deployment crea un ReplicaSet por versión: durante un update, el ReplicaSet nuevo va subiendo réplicas mientras el viejo las baja (rolling update). Si una revisión falla, rollout undo restaura la anterior.
matchLabels es la clave de todo: k8s relaciona objetos (Deployment ↔ pods ↔ Service) por etiquetas, no por nombre. Mantén las labels coherentes entre el selector del Deployment y las del pod template.Services
Los pods son efímeros y cambian de IP cada vez que se recrean. Un Service da una dirección estable y balancea el tráfico entre los pods que coinciden con su selector:
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: mi-api
spec:
selector:
app: mi-api
ports:
- port: 80 # puerto del Service
targetPort: 3000 # puerto de los pods
type: ClusterIPLos tres tipos principales:
ClusterIP (por defecto): dirección interna estable, solo accesible dentro del clúster. Ideal para comunicación entre servicios internos.
NodePort: expone el Service en un puerto de cada node (30000-32767). Práctico en local o para tests.
LoadBalancer: provisiona un balanceador del cloud provider (ELB, LB de GCP...) que reparte tráfico externo. El camino estándar en la nube.
kubectl apply -f service.yaml
# DNS interno: los pods acceden a "mi-api" por nombre
kubectl run test --rm -it --image=busybox -- wget -qO- http://mi-api
# En minikube, exponer el Service al host
minikube service mi-apiConfigMaps y Secrets
Separa la configuración de la imagen: los ConfigMaps guardan valores no sensibles y los Secrets (base64, no cifrado por defecto) los sensibles. Ambos se montan como variables de entorno o como archivos:
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mi-api-config
data:
LOG_LEVEL: debug
FEATURES: "notificaciones,analytics"
# secret.yaml — el valor está en base64
apiVersion: v1
kind: Secret
metadata:
name: mi-api-secrets
type: Opaque
data:
API_KEY: c2VjcmV0bzEyMw== # "secreto123" en base64
# Referenciarlos en el Deployment
envFrom:
- configMapRef:
name: mi-api-config
- secretRef:
name: mi-api-secrets
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: mi-api-secrets
key: API_KEYIngress
Un LoadBalancer por Service multiplica costes y URLs. El Ingress es un punto de entrada único que enruta por host y por path hacia distintos Services, con TLS terminado en el borde:
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: mi-ingress
spec:
rules:
- host: api.midominio.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: mi-api
port:
number: 80
- path: /web
pathType: Prefix
backend:
service:
name: frontend
port:
number: 80
- host: admin.midominio.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-panel
port:
number: 80El Ingress requiere un Ingress Controller instalado en el clúster (nginx-ingress, Traefik, o el del cloud provider) que materialice esas reglas en un proxy real. En minikube:
minikube addons enable ingress
kubectl apply -f ingress.yaml
kubectl get ingressNamespaces y apply -f
Los Namespaces particionan el clúster por equipos, entornos o aplicaciones, con límites de recursos y políticas de red propios. Trabajar siempre en default es una mala práctica que tarde o temprano pica:
kubectl create namespace produccion
kubectl get namespaces
# Operar sobre un namespace concreto
kubectl -n produccion get pods
# Aplicar todo un directorio de manifiestos
kubectl apply -f ./k8s/
kubectl apply -n produccion -f deployment.yaml
# Navegar el clúster
kubectl get all
kubectl get events --sort-by=.lastTimestampkubectl apply -f es idempotente: puedes ejecutarlo mil veces y el resultado es el mismo. Eso convierte los manifiestos YAML en tu única fuente de verdad y permite versionarlos en Git (GitOps): el clúster se describe en el repositorio, no se toca a mano.Ejercicios
Kubernetes cambia la mentalidad: dejas de gestionar procesos y empiezas a declarar estados que el sistema reconcilia por sí solo. Con pods, deployments, services, configmaps e ingress ya tienes el vocabulario esencial para trabajar con cualquier clúster — el paso siguiente es GitOps: describir ese estado en Git y dejar que el clúster lo aplique.