Kubernetes Básico para Homelab: Guía desde Cero 2025
📋 TL;DR
Kubernetes es el siguiente paso natural después de Docker Compose. Si ya dominas Docker y quieres escalar tu homelab con auto-restart, escalado automático y rolling updates, esta guía te enseñará Kubernetes desde cero usando K3s (la versión ligera perfecta para homelabs).
Lo que conseguirás:
- ✅ Instalar K3s en 2 minutos (vs horas con Kubernetes estándar)
- ✅ Entender los objetos básicos de K8s comparándolos con Docker (Pod, Deployment, Service, Ingress, PV, PVC)
- ✅ Desplegar aplicaciones reales (WordPress completo) con YAMLs funcionales
- ✅ Troubleshooting de problemas comunes con soluciones prácticas
Tiempo de lectura: ~25 minutos | Nivel: Intermedio (requiere conocimientos de Docker)
⚠️ IMPORTANTE: Este artículo asume que conoces Docker. Si no sabes Docker, primero lee:
Kubernetes sin Docker es como intentar correr sin saber caminar. No es práctico.
—📚 Tabla de Contenidos
- Introducción: ¿Por qué Kubernetes si Docker Compose funciona?
- Conceptos Fundamentales: Docker → Kubernetes
– 2.2 Docker Compose → Deployment
– 2.3 Volúmenes Docker → PV/PVC
– 2.5 Puertos Docker → Service
- Instalación K3s: Kubernetes Ligero para Homelab
- Primer Despliegue: Nginx Básico
- Despliegue con Volúmenes: Persistencia de Datos
- Exponer Servicios: Ingress con Traefik
- Caso Práctico Completo: WordPress en Kubernetes
- Comandos Esenciales kubectl
- Troubleshooting Común
- Mejores Prácticas para Homelab
- ¿Cuándo NO usar Kubernetes?
- Siguientes Pasos
> 📅 Última actualización: Noviembre 2025
> ✅ Verificado con: K3s v1.31.x, Kubernetes v1.31.x
> 🔄 Próxima revisión: Febrero 2026
—Introducción: ¿Por qué Kubernetes si Docker Compose funciona?
Tienes tu homelab funcionando perfectamente con Docker Compose. Plex corriendo, n8n automatizando, Nextcloud guardando archivos… todo funciona. ¿Por qué complicarse con Kubernetes?
La respuesta corta: No necesitas Kubernetes si Docker Compose te funciona bien. Pero si quieres dar el siguiente paso, Kubernetes te da superpoderes que Docker Compose no tiene.
¿Cuándo usar Kubernetes vs Docker Compose?
Usa Docker Compose si:
- ✅ Tienes 1-10 servicios en un único servidor
- ✅ No necesitas escalado automático
- ✅ Un solo servidor es suficiente
- ✅ Quieres simplicidad máxima
- ✅ Prototipo rápido o desarrollo
Usa Kubernetes si:
- ✅ Tienes 10+ servicios que gestionar
- ✅ Necesitas escalado automático (más pods cuando hay carga)
- ✅ Quieres alta disponibilidad (múltiples nodos)
- ✅ Necesitas rolling updates sin downtime
- ✅ Quieres auto-restart robusto (si un pod falla, se recrea automáticamente)
- ✅ Múltiples entornos (dev/staging/prod)
Regla de oro: Si tienes 3 o menos servicios en un único servidor, Docker Compose es suficiente. Si necesitas múltiples nodos, escalado automático, o alta disponibilidad, Kubernetes es tu camino.
K3s vs Kubernetes Completo: ¿Por qué K3s para Homelab?
K3s es Kubernetes completo pero optimizado para edge computing y homelabs. Consume ~512MB RAM vs 2GB+ de Kubernetes estándar.
Ventajas de K3s:
- ⚡ Tamaño: ~100MB vs 1GB+ de Kubernetes estándar
- 🚀 Instalación: 2 minutos vs horas con kubeadm
- 🎯 Optimizado: Para edge/IoT (perfecto para homelabs)
- ✅ Compatible: 100% compatible con API de Kubernetes
- 📦 Incluye: Traefik (Ingress), local-path-provisioner (Storage), CoreDNS (DNS)
K3s incluye por defecto:
- Traefik como Ingress Controller (no necesitas instalarlo)
- local-path-provisioner para almacenamiento local
- CoreDNS para DNS interno
- Flannel para networking
Kubernetes estándar requiere instalar cada componente manualmente, lo cual es complejo y consume más recursos.
Advertencia Crítica: Requisitos Previos
⚠️ Este artículo asume que conoces Docker.
Si no sabes Docker, primero aprende:
¿Por qué es importante?
- Kubernetes usa contenedores Docker (o containerd) internamente
- Los conceptos de Docker (imágenes, contenedores, volúmenes) son fundamentales
- Sin entender Docker, Kubernetes será confuso y frustrante
Kubernetes sin Docker es como intentar correr sin saber caminar. No es práctico.
—Conceptos Fundamentales: Docker → Kubernetes
La mejor forma de aprender Kubernetes es comparándolo con Docker. Si ya entiendes Docker, entenderás K8s rápidamente.
2.1 Contenedor → Pod
En Docker:
docker run -d --name nginx -p 8080:80 nginx:latest
Esto crea un contenedor Nginx que escucha en el puerto 80 del contenedor y lo mapea al puerto 8080 del host.
En Kubernetes (Pod):
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
Analogía: Pod = contenedor con superpoderes
Diferencias clave:
- Pod puede tener múltiples contenedores que comparten red y almacenamiento
- Pod es efímero: Si un Pod falla, Kubernetes NO lo recrea automáticamente
- Por eso usamos Deployments: Gestionan Pods y los recrean si fallan
Ejemplo de Pod con múltiples contenedores:
apiVersion: v1
kind: Pod
metadata:
name: web-app
spec:
containers:
- name: nginx
image: nginx:latest
- name: log-collector
image: fluentd:latest
# Ambos contenedores comparten:
# - Misma IP (localhost entre ellos)
# - Mismo almacenamiento (volúmenes compartidos)
¿Cuándo usar Pods directamente?
Casi nunca en producción. Los Pods son efímeros y no se auto-recrean. Usa Deployments o StatefulSets.
—2.2 Docker Compose → Deployment
En Docker Compose:
version: '3.8'
services:
nginx:
image: nginx:latest
ports:
- "8080:80"
restart: always
Esto define un servicio Nginx que se reinicia automáticamente si falla.
En Kubernetes (Deployment):
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
Analogía: Deployment = docker-compose + auto-restart + escalado automático + rolling updates + rollbacks
Ventajas sobre Docker Compose:
| Característica | Docker Compose | Kubernetes Deployment |
|---|---|---|
| Auto-restart | restart: always | ✅ Automático (siempre) |
| Escalado | Manual (docker compose up --scale) | ✅ Automático (replicas: 3) |
| Rolling updates | Manual (stop, pull, start) | ✅ Automático sin downtime |
| Rollback | Manual (volver a versión anterior) | ✅ Automático (kubectl rollout undo) |
| Health checks | Básico | ✅ Liveness + Readiness probes |
Ejemplo de escalado:
# Docker Compose: Manual
docker compose up --scale nginx=3
# Kubernetes: Automático
kubectl scale deployment nginx-deployment --replicas=3
# O cambiar replicas: 3 en el YAML y aplicar
Rolling update automático:
# Docker Compose: Tienes que hacerlo manualmente
docker compose stop nginx
docker compose pull nginx
docker compose up -d nginx
# Kubernetes: Automático
kubectl set image deployment/nginx-deployment nginx=nginx:1.25
# Kubernetes actualiza pod por pod sin downtime
—
2.3 Volúmenes Docker → PV/PVC
En Docker:
services:
app:
volumes:
- ./data:/app/data # Bind mount (host → container)
- myvolume:/app/storage # Named volume
En Kubernetes (PV + PVC):
Kubernetes separa el almacenamiento en dos conceptos:
- PersistentVolume (PV): El disco físico (recurso del cluster)
- PersistentVolumeClaim (PVC): La solicitud de espacio (lo que pide tu aplicación)
# PersistentVolume (disco físico)
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
storageClassName: local-path
hostPath:
path: /mnt/data
---
# PersistentVolumeClaim (solicitud de espacio)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: local-path
---
# Montar en Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-deployment
spec:
template:
spec:
containers:
- name: app
image: app:latest
volumeMounts:
- name: storage
mountPath: /app/data
volumes:
- name: storage
persistentVolumeClaim:
claimName: my-pvc
Analogía:
- PV = disco físico (recurso del cluster, como un disco duro)
- PVC = solicitud de espacio (lo que pide tu aplicación, como «necesito 10GB»)
- StorageClass = tipo de almacenamiento (local-path, nfs, ceph, etc.)
¿Por qué esta separación?
- Flexibilidad: Puedes cambiar el tipo de almacenamiento sin modificar tu aplicación
- Abstracción: Tu aplicación no sabe dónde está el disco, solo pide espacio
- Gestión: El administrador del cluster gestiona los PVs, los desarrolladores solo piden PVCs
AccessModes (modos de acceso):
- ReadWriteOnce (RWO): Un solo nodo puede montar en read-write (como un disco local)
- ReadOnlyMany (ROX): Múltiples nodos pueden leer (ideal para modelos inmutables)
- ReadWriteMany (RWX): Múltiples nodos pueden leer/escribir (requiere NFS, CephFS)
K3s StorageClass por defecto: local-path (almacenamiento local)
Comparación con Docker:
# Docker: Directo
volumes:
- ./data:/app/data
# Kubernetes: Abstracción
volumes:
- name: storage
persistentVolumeClaim:
claimName: my-pvc
—
2.4 Reverse Proxy → Ingress
En Docker Compose:
services:
nginx:
image: nginx:latest
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
ports:
- "80:80"
- "443:443"
app1:
image: app1:latest
app2:
image: app2:latest
Necesitas configurar Nginx manualmente para enrutar app1.tudominio.com → app1:80 y app2.tudominio.com → app2:80.
En Kubernetes (Ingress):
# Ingress Controller (Traefik viene con K3s)
# No necesitas instalarlo manualmente
---
# Ingress (ruteo HTTP/HTTPS)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: traefik
tls:
- hosts:
- app1.tudominio.com
- app2.tudominio.com
secretName: tls-secret
rules:
- host: app1.tudominio.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app1-service
port:
number: 80
- host: app2.tudominio.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app2-service
port:
number: 80
Analogía: Ingress = reverse proxy integrado en Kubernetes (no necesitas servicio separado)
Ventajas:
- ✅ SSL automático: Cert-manager gestiona certificados Let’s Encrypt
- ✅ Sin configuración manual: No editas nginx.conf
- ✅ Declarativo: Todo en YAML, versionable en Git
- ✅ Multi-servicio: Un solo Ingress para múltiples servicios
K3s: Traefik viene preinstalado como Ingress Controller. No necesitas instalarlo.
Comparación con Docker:
# Docker: Servicio separado + configuración manual
services:
nginx:
image: nginx:latest
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf # Configuración manual
# Kubernetes: Integrado + declarativo
apiVersion: networking.k8s.io/v1
kind: Ingress
# Todo en YAML, sin archivos de configuración separados
—
2.5 Puertos Docker → Service
En Docker:
services:
nginx:
ports:
- "8080:80" # host:container
Esto mapea el puerto 8080 del host al puerto 80 del contenedor.
En Kubernetes (Service):
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: ClusterIP # Solo accesible dentro del cluster
selector:
app: nginx
ports:
- port: 80
targetPort: 80
protocol: TCP
Analogía: Service = mapeo de puertos + load balancing + DNS interno
Tipos de Service:
| Tipo | Descripción | Uso |
|---|---|---|
| ClusterIP | Solo accesible dentro del cluster (por defecto) | Comunicación interna entre pods |
| NodePort | Expone en puerto del nodo (30000-32767) | Acceso externo sin Ingress |
| LoadBalancer | IP externa (requiere cloud provider) | Producción en cloud (AWS, GCP, Azure) |
| ExternalName | Alias a servicio externo | Conectar a servicios fuera del cluster |
DNS interno:
- FQDN completo:
nginx-service.default.svc.cluster.local - Corto (mismo namespace):
nginx-service - Cross-namespace:
nginx-service.production.svc.cluster.local
Ejemplo de uso:
# WordPress se conecta a MySQL usando DNS interno
env:
- name: WORDPRESS_DB_HOST
value: "mysql-service" # No necesitas IP, solo el nombre del Service
Load balancing automático:
Si tienes 3 réplicas de Nginx, el Service distribuye el tráfico automáticamente entre ellas.
Comparación con Docker:
# Docker: Mapeo directo
ports:
- "8080:80"
# Kubernetes: Abstracción + load balancing
type: ClusterIP
selector:
app: nginx # Conecta a todos los pods con label app=nginx
—
Instalación K3s: Kubernetes Ligero para Homelab
Requisitos Hardware/Software
Mínimos:
- RAM: 512MB (mínimo), 1GB recomendado
- CPU: 1 core (mínimo), 2 cores recomendado
- Disco: 10GB mínimo
- OS: Linux (Ubuntu 20.04+, Debian 11+, etc.)
Recomendados para homelab:
- RAM: 4GB+
- CPU: 2+ cores
- Disco: 50GB+ SSD
Software previo:
- Docker instalado (o containerd)
- Conocimientos básicos de línea de comandos Linux
Instalación Básica (2 minutos)
# Instalar K3s (incluye kubectl)
curl -sfL https://get.k3s.io | sh -
# Verificar instalación
sudo k3s kubectl get nodes
# Debería mostrar:
# NAME STATUS ROLES AGE VERSION
# server Ready control-plane,master 1m v1.31.x+k3s1
¡Eso es todo! K3s está instalado y funcionando.
Configurar kubectl sin sudo
Por defecto, necesitas usar sudo k3s kubectl. Para usar kubectl directamente:
# Copiar kubeconfig
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config
# Añadir a .bashrc/.zshrc (opcional pero recomendado)
export KUBECONFIG=~/.kube/config
# Verificar
kubectl get nodes
Ahora puedes usar kubectl directamente sin sudo.
Verificar Componentes Incluidos
K3s incluye varios componentes por defecto:
# Verificar Traefik (Ingress Controller)
kubectl get pods -n kube-system | grep traefik
# Verificar local-path-provisioner (Storage)
kubectl get pods -n kube-system | grep local-path
# Verificar CoreDNS (DNS interno)
kubectl get pods -n kube-system | grep coredns
# Verificar StorageClass
kubectl get storageclass
# Deberías ver: local-path (default)
Todo está listo para empezar a desplegar aplicaciones.
Primer Pod de Prueba
Vamos a crear un pod simple para verificar que todo funciona:
# Crear pod de prueba
kubectl run nginx-test --image=nginx:latest --port=80
# Verificar que está corriendo
kubectl get pods
# Ver logs
kubectl logs nginx-test
# Eliminar pod de prueba
kubectl delete pod nginx-test
Si ves el pod en estado «Running», K3s está funcionando correctamente.
—Primer Despliegue: Nginx Básico
Vamos a desplegar Nginx usando Deployment + Service, comparándolo con Docker Compose.
Comparación: Docker Compose vs Kubernetes
Docker Compose:
version: '3.8'
services:
nginx:
image: nginx:latest
ports:
- "8080:80"
restart: always
Kubernetes (Deployment + Service):
Crea nginx-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
Crea nginx-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
type: ClusterIP
Desplegar
# Aplicar recursos
kubectl apply -f nginx-deployment.yaml
kubectl apply -f nginx-service.yaml
# Verificar
kubectl get pods
kubectl get svc
# Deberías ver:
# NAME READY STATUS RESTARTS AGE
# nginx-deployment-7d4b8c9c6d-xxxxx 1/1 Running 0 30s
# nginx-deployment-7d4b8c9c6d-yyyyy 1/1 Running 0 30s
#
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# nginx-service ClusterIP 10.43.xxx.xxx <none> 80/TCP 30s
Acceder al Servicio
Opción 1: Port Forward (acceso local)
kubectl port-forward svc/nginx-service 8080:80
# Abrir http://localhost:8080 en el navegador
Opción 2: NodePort (acceso externo)
# Cambiar type: ClusterIP a type: NodePort
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort
selector:
app: nginx
ports:
- port: 80
targetPort: 80
nodePort: 30080 # Puerto externo (30000-32767)
# Aplicar cambio
kubectl apply -f nginx-service.yaml
# Acceder desde fuera
curl http://<IP_DEL_NODO>:30080
Opción 3: Ingress (acceso con dominio)
Veremos esto en la sección de Ingress.
Escalar Manualmente
# Escalar a 5 réplicas
kubectl scale deployment nginx-deployment --replicas=5
# Verificar
kubectl get pods
# Deberías ver 5 pods corriendo
Comparación con Docker Compose:
# Docker Compose: Manual
docker compose up --scale nginx=5
# Kubernetes: Más simple
kubectl scale deployment nginx-deployment --replicas=5
—
Despliegue con Volúmenes: Persistencia de Datos
Vamos a desplegar una aplicación que guarda datos y necesita persistencia.
Comparación: Volúmenes Docker vs PV/PVC
Docker Compose:
services:
app:
volumes:
- ./data:/app/data
Kubernetes (PV + PVC):
Crea pv.yaml:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nginx-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
storageClassName: local-path
hostPath:
path: /mnt/data/nginx
Crea pvc.yaml:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: local-path
Crea deployment.yaml (con volumen):
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
persistentVolumeClaim:
claimName: nginx-pvc
Desplegar
# Aplicar en orden
kubectl apply -f pv.yaml
kubectl apply -f pvc.yaml
kubectl apply -f deployment.yaml
# Verificar
kubectl get pv
kubectl get pvc
kubectl get pods
# Deberías ver:
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
# nginx-pv 1Gi RWO Retain Bound default/nginx-pvc local-path 30s
#
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# nginx-pvc Bound nginx-pv 1Gi RWO local-path 30s
Verificar Persistencia
# Crear archivo en el pod
kubectl exec -it deployment/nginx-deployment -- sh -c "echo 'Hola desde K8s' > /usr/share/nginx/html/test.txt"
# Verificar archivo
kubectl exec -it deployment/nginx-deployment -- cat /usr/share/nginx/html/test.txt
# Reiniciar el pod (simular fallo)
kubectl delete pod -l app=nginx
# Esperar a que se recree
kubectl get pods -w
# Verificar que el archivo persiste
kubectl exec -it deployment/nginx-deployment -- cat /usr/share/nginx/html/test.txt
# Deberías ver: "Hola desde K8s"
Los datos persisten aunque reinicies el pod.
StorageClass: Automatización de PVs
En lugar de crear PVs manualmente, puedes usar StorageClass para que Kubernetes cree PVs automáticamente:
# PVC con StorageClass (K3s usa local-path por defecto)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: local-path # K3s crea el PV automáticamente
Con StorageClass, no necesitas crear el PV manualmente. Kubernetes lo crea automáticamente cuando creas el PVC.
—Exponer Servicios: Ingress con Traefik
Vamos a exponer Nginx usando Ingress, comparándolo con un reverse proxy en Docker.
Comparación: Reverse Proxy Docker vs Ingress
Docker Compose (Nginx reverse proxy):
services:
nginx:
image: nginx:latest
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
ports:
- "80:80"
- "443:443"
app1:
image: app1:latest
app2:
image: app2:latest
Necesitas configurar nginx.conf manualmente:
server {
server_name app1.tudominio.com;
location / {
proxy_pass http://app1:80;
}
}
server {
server_name app2.tudominio.com;
location / {
proxy_pass http://app2:80;
}
}
Kubernetes (Ingress):
# Ingress Controller (Traefik viene con K3s)
# No necesitas instalarlo manualmente
---
# Ingress (ruteo HTTP/HTTPS)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
spec:
ingressClassName: traefik
rules:
- host: nginx.tudominio.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
Ventajas:
- ✅ Sin configuración manual: No editas nginx.conf
- ✅ Declarativo: Todo en YAML, versionable en Git
- ✅ SSL automático: Cert-manager gestiona certificados (opcional)
Verificar Traefik está Corriendo
# Verificar Traefik (Ingress Controller)
kubectl get pods -n kube-system | grep traefik
# Deberías ver:
# NAME READY STATUS RESTARTS AGE
# traefik-xxx 1/1 Running 0 5m
K3s incluye Traefik por defecto. No necesitas instalarlo.
Crear Ingress
Crea ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
spec:
ingressClassName: traefik
rules:
- host: nginx.tudominio.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
# Aplicar
kubectl apply -f ingress.yaml
# Verificar
kubectl get ingress
# Deberías ver:
# NAME CLASS HOSTS ADDRESS PORTS AGE
# nginx-ingress traefik nginx.tudominio.com 80 30s
Configurar DNS
Opción 1: /etc/hosts (testing local)
# Añadir en /etc/hosts
<IP_DEL_NODO> nginx.tudominio.com
Opción 2: DNS real (producción)
# Crear registro A en tu DNS
nginx.tudominio.com A <IP_DEL_NODO>
Acceder
# Acceder desde navegador
curl http://nginx.tudominio.com
# O abrir en navegador
# http://nginx.tudominio.com
SSL Automático con Cert-Manager (Opcional)
Para SSL automático, instala cert-manager:
# Instalar cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml
# Esperar a que esté listo
kubectl wait --for=condition=ready pod -l app.kubernetes.io/instance=cert-manager -n cert-manager --timeout=300s
Luego añade anotaciones al Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: traefik
tls:
- hosts:
- nginx.tudominio.com
secretName: nginx-tls
rules:
- host: nginx.tudominio.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
Cert-manager gestionará automáticamente los certificados Let’s Encrypt.
—Caso Práctico Completo: WordPress en Kubernetes
Vamos a desplegar WordPress completo (WordPress + MySQL) con persistencia e Ingress, comparándolo con Docker Compose.
Comparación: Docker Compose vs Kubernetes
Docker Compose:
version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: wppassword
volumes:
- mysql_data:/var/lib/mysql
wordpress:
image: wordpress:latest
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: wppassword
ports:
- "8080:80"
depends_on:
- mysql
volumes:
mysql_data:
Kubernetes (WordPress + MySQL completo):
Crea mysql-pvc.yaml:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: local-path
Crea mysql-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql-deployment
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
value: "rootpassword"
- name: MYSQL_DATABASE
value: "wordpress"
- name: MYSQL_USER
value: "wpuser"
- name: MYSQL_PASSWORD
value: "wppassword"
ports:
- containerPort: 3306
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumes:
- name: mysql-data
persistentVolumeClaim:
claimName: mysql-pvc
Crea mysql-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: mysql-service
spec:
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
Crea wordpress-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress-deployment
spec:
replicas: 2
selector:
matchLabels:
app: wordpress
template:
metadata:
labels:
app: wordpress
spec:
containers:
- name: wordpress
image: wordpress:latest
env:
- name: WORDPRESS_DB_HOST
value: "mysql-service"
- name: WORDPRESS_DB_NAME
value: "wordpress"
- name: WORDPRESS_DB_USER
value: "wpuser"
- name: WORDPRESS_DB_PASSWORD
value: "wppassword"
ports:
- containerPort: 80
Crea wordpress-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: wordpress-service
spec:
selector:
app: wordpress
ports:
- port: 80
targetPort: 80
Crea ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wordpress-ingress
spec:
ingressClassName: traefik
rules:
- host: wordpress.tudominio.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: wordpress-service
port:
number: 80
Desplegar
# Aplicar todos los recursos
kubectl apply -f mysql-pvc.yaml
kubectl apply -f mysql-deployment.yaml
kubectl apply -f mysql-service.yaml
kubectl apply -f wordpress-deployment.yaml
kubectl apply -f wordpress-service.yaml
kubectl apply -f ingress.yaml
# Verificar
kubectl get pods
kubectl get svc
kubectl get ingress
# Esperar a que MySQL esté listo
kubectl wait --for=condition=ready pod -l app=mysql --timeout=300s
# Verificar logs de WordPress
kubectl logs -l app=wordpress
Acceder
# Configurar DNS
# Añadir en /etc/hosts o DNS real:
<IP_DEL_NODO> wordpress.tudominio.com
# Acceder
curl http://wordpress.tudominio.com
# O abrir en navegador: http://wordpress.tudominio.com
Ventajas sobre Docker Compose
| Característica | Docker Compose | Kubernetes |
|---|---|---|
| Escalado WordPress | Manual | ✅ Automático (réplicas: 2) |
| Rolling updates | Manual | ✅ Automático sin downtime |
| Auto-restart | restart: always | ✅ Automático (siempre) |
| Load balancing | No nativo | ✅ Service distribuye tráfico |
| SSL automático | Manual (certbot) | ✅ Cert-manager (opcional) |
Con Kubernetes, puedes escalar WordPress a 5 réplicas sin downtime:
kubectl scale deployment wordpress-deployment --replicas=5
—
Comandos Esenciales kubectl
kubectl es la herramienta de línea de comandos para interactuar con Kubernetes. Aquí están los comandos más importantes.
Ver Recursos
# Ver todos los pods
kubectl get pods
# Ver pods en namespace específico
kubectl get pods -n default
# Ver todos los recursos
kubectl get all
# Ver deployments
kubectl get deployments
# Ver services
kubectl get services
# Ver con más detalles
kubectl get pods -o wide
# Ver en tiempo real (watch)
kubectl get pods -w
Describir Recursos (Debugging)
# Describir pod (ver eventos, estado, etc.)
kubectl describe pod <pod-name>
# Describir deployment
kubectl describe deployment <deployment-name>
# Describir service
kubectl describe service <service-name>
# Describir nodo
kubectl describe node <node-name>
Ejemplo de salida útil:
kubectl describe pod nginx-deployment-xxx
# Muestra:
# - Estado del pod
# - Eventos (qué pasó, errores, etc.)
# - Configuración (imagen, recursos, etc.)
# - Volúmenes montados
Logs
# Ver logs de un pod
kubectl logs <pod-name>
# Ver logs de un deployment
kubectl logs deployment/<deployment-name>
# Seguir logs (tail -f)
kubectl logs -f <pod-name>
# Ver logs del contenedor anterior (si reinició)
kubectl logs <pod-name> --previous
# Ver logs de todos los pods con label
kubectl logs -l app=nginx
Ejecutar Comandos en Pods
# Entrar a un pod (bash)
kubectl exec -it <pod-name> -- /bin/bash
# Ejecutar comando
kubectl exec <pod-name> -- ls /app
# Ejecutar en deployment
kubectl exec -it deployment/<deployment-name> -- /bin/bash
Aplicar/Actualizar Recursos
# Aplicar YAML
kubectl apply -f deployment.yaml
# Aplicar todos los YAMLs en carpeta
kubectl apply -f ./manifests/
# Eliminar recurso
kubectl delete -f deployment.yaml
# Eliminar pod
kubectl delete pod <pod-name>
# Eliminar deployment (elimina todos los pods)
kubectl delete deployment <deployment-name>
kubectl apply vs kubectl create:
kubectl create: Crea recurso (falla si ya existe)kubectl apply: Crea o actualiza recurso (idempotente, recomendado)
Port Forward (Acceso Local)
# Port forward a un pod
kubectl port-forward <pod-name> 8080:80
# Port forward a un service
kubectl port-forward svc/<service-name> 8080:80
# Port forward a un deployment
kubectl port-forward deployment/<deployment-name> 8080:80
Útil para: Testing local, debugging, acceso temporal sin Ingress.
Escalar
# Escalar deployment
kubectl scale deployment nginx-deployment --replicas=5
# Escalar con archivo
kubectl scale --replicas=5 -f deployment.yaml
Rolling Updates
# Actualizar imagen
kubectl set image deployment/nginx-deployment nginx=nginx:1.25
# Ver estado del rollout
kubectl rollout status deployment/nginx-deployment
# Ver historial de rollouts
kubectl rollout history deployment/nginx-deployment
# Rollback a versión anterior
kubectl rollout undo deployment/nginx-deployment
# Rollback a versión específica
kubectl rollout undo deployment/nginx-deployment --to-revision=2
—
Troubleshooting Común
1. Pod en estado Pending
Síntoma:
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# nginx-xxx 0/1 Pending 0 5m
Diagnóstico:
kubectl describe pod <pod-name>
# Events:
# Warning FailedScheduling 5m default-scheduler 0/1 nodes are available: 1 Insufficient memory.
Causas comunes:
- Recursos insuficientes (RAM/CPU)
- No hay nodos disponibles
- Taints/Tolerations no coinciden
- PVC no puede montarse
Solución:
# Ver recursos del nodo
kubectl describe node
# Ver eventos del pod
kubectl describe pod <pod-name>
# Reducir requests/limits en Deployment
Ejemplo de ajuste:
resources:
requests:
memory: "128Mi" # Reducir de 512Mi
cpu: "100m" # Reducir de 500m
limits:
memory: "256Mi"
cpu: "200m"
—
2. Pod en estado CrashLoopBackOff
Síntoma:
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# app-xxx 0/1 CrashLoopBackOff 5 3m
Diagnóstico:
# Ver logs del pod
kubectl logs <pod-name>
# Ver logs del contenedor anterior
kubectl logs <pod-name> --previous
# Describir pod
kubectl describe pod <pod-name>
Causas comunes:
- Error en la aplicación (código)
- Comando mal configurado
- Dependencias faltantes
- Healthcheck demasiado agresivo
- Permisos incorrectos
Solución:
# Ajustar probes
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 30 # Dar tiempo a iniciar
periodSeconds: 10
failureThreshold: 3
Ejemplo de debugging:
# Ver logs para identificar el error
kubectl logs app-xxx --previous
# Si el error es "permission denied", ajustar permisos
kubectl exec -it app-xxx -- chown -R 1000:1000 /app/data
—
3. Service no conecta
Síntoma: Pods no pueden comunicarse entre sí
Diagnóstico:
# Verificar service existe
kubectl get svc
# Verificar selectors del service coinciden con labels del pod
kubectl describe service <service-name>
kubectl get pods --show-labels
# Probar DNS
kubectl exec -it <pod-name> -- nslookup <service-name>
Causas comunes:
- Selectors del Service no coinciden con labels del Pod
- Pod no está en estado Running
- NetworkPolicy bloquea tráfico
Solución:
# Verificar selectors coinciden
apiVersion: v1
kind: Service
spec:
selector:
app: nginx # Debe coincidir con labels del Pod
Ejemplo de debugging:
# Ver labels del pod
kubectl get pods --show-labels
# NAME LABELS
# nginx-xxx app=nginx,version=1.0
# Ver selectors del service
kubectl describe service nginx-service
# Selector: app=nginx # ✅ Coincide
—
4. PVC no se monta (Pending)
Síntoma:
kubectl get pvc
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# my-pvc Pending local-path 5m
Diagnóstico:
kubectl describe pvc <pvc-name>
# Events:
# Warning ProvisioningFailed 5m persistentvolume-controller storageclass.storage.k8s.io "local-path" not found
Causas comunes:
- StorageClass no existe
- No hay PersistentVolumes disponibles
- StorageClass no tiene provisioner
Solución:
# Verificar StorageClass existe
kubectl get storageclass
# K3s incluye local-path-provisioner por defecto
# Si no existe, instalar:
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.30/deploy/local-path-storage.yaml
—
5. Ingress no funciona
Síntoma: No se puede acceder a la aplicación desde fuera
Diagnóstico:
# Verificar Ingress existe
kubectl get ingress
# Verificar Ingress Controller está corriendo
kubectl get pods -n kube-system | grep traefik
# Describir Ingress
kubectl describe ingress <ingress-name>
Causas comunes:
- Ingress Controller no está instalado (K3s lo incluye por defecto)
- Anotaciones incorrectas
- Service no existe o no coincide
- DNS no apunta al nodo
Solución:
# Verificar Traefik está corriendo (K3s)
kubectl get pods -n kube-system | grep traefik
# Verificar Service existe
kubectl get svc
# Verificar DNS apunta al nodo
dig app.tudominio.com
—
Mejores Prácticas para Homelab
1. Namespaces: Organizar Recursos
Los namespaces permiten organizar recursos en grupos lógicos.
# Crear namespace
kubectl create namespace production
kubectl create namespace development
# Desplegar en namespace específico
kubectl apply -f deployment.yaml -n production
# Ver recursos en namespace
kubectl get pods -n production
Ejemplo de organización:
# production/wordpress-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
namespace: production # Especificar namespace
spec:
# ...
Ventajas:
- ✅ Organización clara (production, development, staging)
- ✅ Aislamiento de recursos
- ✅ Quotas de recursos por namespace
2. Resource Limits: Evitar que un Pod Consuma Todo
Siempre define requests y limits para evitar que un pod consuma todos los recursos.
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
Explicación:
- requests: Recursos garantizados (el pod siempre tendrá esto)
- limits: Recursos máximos (el pod no puede exceder esto)
Sin limits:
# ❌ MAL: Sin limits
resources:
requests:
memory: "256Mi"
cpu: "250m"
# El pod puede consumir toda la RAM del nodo
Con limits:
# ✅ BIEN: Con limits
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
# El pod está limitado a 512Mi RAM
—
3. Health Checks: Liveness y Readiness Probes
Los probes permiten a Kubernetes verificar que tu aplicación está funcionando correctamente.
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 80
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
Diferencia:
- Liveness probe: Si falla, Kubernetes reinicia el pod
- Readiness probe: Si falla, Kubernetes quita el pod del Service (no recibe tráfico)
Ejemplo práctico:
# WordPress tarda en iniciar (conexión a MySQL)
readinessProbe:
httpGet:
path: /wp-admin/install.php
port: 80
initialDelaySeconds: 30 # Dar tiempo a conectar a MySQL
periodSeconds: 10
—
4. Secrets: No Hardcodear Contraseñas
Nunca hardcodees contraseñas en YAMLs. Usa Secrets.
# Crear secret
kubectl create secret generic mysql-secret \
--from-literal=password=mi-password-segura
# Usar en Deployment
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: mysql
env:
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: password
Ventajas:
- ✅ Contraseñas no están en YAMLs (no se versionan en Git)
- ✅ Fácil rotación (cambiar secret, reiniciar pods)
- ✅ Separación de configuración y secretos
5. ConfigMaps: Configuración Externa
Usa ConfigMaps para configuración que puede cambiar.
# Crear ConfigMap
kubectl create configmap app-config \
--from-literal=database_host=mysql-service \
--from-literal=log_level=info
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: app
envFrom:
- configMapRef:
name: app-config
Ventajas:
- ✅ Cambiar configuración sin modificar YAMLs
- ✅ Reutilizar configuración entre deployments
- ✅ Separación de código y configuración
¿Cuándo NO usar Kubernetes?
Kubernetes no es la solución para todo. Hay casos donde Docker Compose es mejor.
❌ NO uses Kubernetes si:
1. Homelab simple (1-2 servicios)
- Docker Compose es más simple y suficiente
- Kubernetes añade complejidad innecesaria
- Overhead de recursos no justificado
2. Sin experiencia Docker
- Aprende Docker primero
- Kubernetes sin Docker es confuso
- Curva de aprendizaje muy alta
3. Recursos limitados (<4GB RAM)
- K3s mínimo requiere 512MB, pero recomendado 4GB+
- Docker Compose consume menos recursos
- Mejor usar Docker Compose si tienes recursos limitados
4. Prototipo rápido
- Docker Compose es más rápido para prototipar
- Kubernetes requiere más configuración inicial
- Para pruebas rápidas, Docker Compose es mejor
5. Solo 1 servidor sin necesidad de escalado
- Si no necesitas múltiples nodos, Docker Compose es suficiente
- Kubernetes brilla con múltiples nodos y escalado automático
- Para un solo servidor, Docker Compose es más simple
✅ Usa Kubernetes si:
- Tienes 10+ servicios que gestionar
- Necesitas escalado automático
- Requieres alta disponibilidad (múltiples nodos)
- Quieres rolling updates sin downtime
- Tienes equipo con experiencia DevOps/K8s
Regla de oro: Si Docker Compose te funciona bien, no necesitas Kubernetes. Kubernetes es para cuando Docker Compose se queda corto.
—Siguientes Pasos
1. Helm: Gestor de Paquetes para Kubernetes
Helm es como «apt» o «yum» para Kubernetes. Permite instalar aplicaciones complejas con un solo comando.
# Instalar Helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# Instalar aplicación (ejemplo: WordPress)
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install wordpress bitnami/wordpress
Ventajas:
- ✅ Instalación rápida de aplicaciones complejas
- ✅ Configuración parametrizable
- ✅ Actualizaciones y rollbacks fáciles
Artículo relacionado: Kubernetes para IA: Cómo Desplegar Modelos ML en Producción (cubre Helm para aplicaciones IA)
—2. Monitoring: Prometheus + Grafana
Monitorear tu cluster Kubernetes es esencial para producción.
# Instalar Prometheus + Grafana con Helm
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install prometheus prometheus-community/kube-prometheus-stack
Métricas importantes:
- CPU y RAM por pod
- Uptime de servicios
- Latencia de requests
- Errores y restarts
Artículo relacionado: Prometheus + Grafana: Monitoreo Profesional para Homelab 2025 (próximamente)
—3. CI/CD: GitLab CI, GitHub Actions
Automatiza el despliegue de aplicaciones en Kubernetes.
GitLab CI ejemplo:
deploy:
script:
- kubectl apply -f deployment.yaml
- kubectl rollout status deployment/my-app
GitHub Actions ejemplo:
- name: Deploy to Kubernetes
run: |
kubectl apply -f deployment.yaml
kubectl rollout status deployment/my-app
—
4. Artículos Relacionados
Para profundizar:
- Kubernetes para IA: Cómo Desplegar Modelos ML en Producción – Kubernetes avanzado con GPUs
- GPU Scheduling en Kubernetes: Deploy AI Workloads a Escala – GPUs en K8s
- Docker Compose: De 0 a Homelab Completo 2025 – Comparación con K8s
Requisitos previos:
- Docker para Novatos: La Guía Definitiva 2025 – Aprender Docker primero
- Linux Debian/Ubuntu para Homelab: Guía Completa 2025 – Sistema operativo
📥 Descargar Ejemplos
Todos los ejemplos de este tutorial están disponibles en GitHub:
🔗 GitHub: kubernetes-basico-homelab-guia-desde-cero
Incluye:
- ✅
01-nginx-basico/– Deployment + Service - ✅
02-nginx-con-volumen/– PV + PVC - ✅
03-nginx-con-ingress/– Ingress con Traefik - ✅
04-wordpress-completo/– WordPress + MySQL completo - ✅
docker-compose-equivalente/– Comparación con Docker Compose
Clonar todo:
git clone https://github.com/ziruelen/learningaiagents.git
cd learningaiagents/kubernetes/kubernetes-basico-homelab-guia-desde-cero/
—
🔗 Artículos Relacionados
Continúa aprendiendo con estas guías complementarias:
- Docker para Novatos: La Guía Definitiva 2025 – Aprender Docker primero
- Docker Compose: De 0 a Homelab Completo 2025 – Comparación con K8s
- Kubernetes para IA: Cómo Desplegar Modelos ML en Producción – Kubernetes avanzado
- GPU Scheduling en Kubernetes: Deploy AI Workloads a Escala – GPUs en K8s
- Traefik Reverse Proxy: Guía Completa para Homelab 2025 – Ingress Controller
- Portainer: Gestiona Docker con Interfaz Web (Guía Completa 2025)-cual-elegir-para-gestionar-docker-en-tu-homelab-guia-completa-2025/)-gestiona-docker-con-interfaz-web-guia-completa-2025/) – UI para Docker/K8s
🔗 Artículos Relacionados
Continúa aprendiendo con estas guías complementarias:
Uptime Kuma: Monitoreo Self-Hosted Gratis para tu Homelab (Guía Completa 2025)
Guía completa para instalar y configurar Uptime Kuma, la mejor alternativa open-source y self-hosted…
DGX Spark vs Servidor IA Casero: Comparativa Real 2025
NVIDIA lanza el DGX Spark por $4,000-$5,000. ¿Merece la pena o puedes montar un servidor IA casero m…
Caddy Reverse Proxy: SSL Automático y Configuración Simple para Homelab 2025
Guía completa de Caddy: reverse proxy con SSL automático para homelab. Instalación Docker, configura…
Dockge vs Portainer: ¿Cuál Elegir para Gestionar Docker en tu Homelab? (Guía Completa 2025)
Comparativa completa entre Dockge y Portainer: características, consumo de recursos, casos de uso, i…
Ollama vs LM Studio: ¿Cuál Elegir para Ejecutar LLMs Locales en tu Homelab? (Guía 2025)
Comparativa completa entre Ollama y LM Studio para ejecutar modelos de lenguaje locales. Benchmarks …
Los 10 Blogs Imprescindibles sobre AI Agents para 2025
Top 10 Blogs de Noticias sobre AI Agents y Agentic AI (Actualización 2025) Tiempo estimado…
Portainer: Gestiona Docker con Interfaz Web (Guía Completa 2025)
Instala Portainer en 30 segundos y gestiona Docker con interfaz web. Controla múltiples servidores r…
Homelab Completo 2025: La Guía Definitiva desde Cero
Si alguna vez has soñado con tener tu propio servidor en casa, ejecutar servicios 24/7,…
Preguntas Frecuentes
¿Necesito saber Docker antes de aprender Kubernetes?
Sí, absolutamente. Kubernetes usa contenedores Docker (o containerd) internamente. Sin entender Docker (imágenes, contenedores, volúmenes), Kubernetes será confuso y frustrante.
Recomendación: Aprende Docker primero con Docker para Novatos: La Guía Definitiva 2025 y Docker Compose: De 0 a Homelab Completo 2025.
Kubernetes sin Docker es como intentar correr sin saber caminar. No es práctico.
—¿K3s es lo mismo que Kubernetes?
Sí y no. K3s es Kubernetes completo (100% compatible con la API), pero optimizado para edge computing y homelabs.
Diferencias:
- Tamaño: K3s ~100MB vs Kubernetes estándar 1GB+
- Instalación: K3s 2 minutos vs Kubernetes estándar horas
- Incluye: K3s incluye Traefik, local-path-provisioner, CoreDNS por defecto
- Compatibilidad: 100% compatible con API de Kubernetes
Para homelab, K3s es la mejor opción. Es más ligero, más fácil de instalar, y tiene todo lo que necesitas.
—¿Cuánta RAM necesito para K3s?
Mínimos:
- 512MB RAM (mínimo absoluto)
- 1GB RAM (recomendado mínimo)
Recomendados para homelab:
- 4GB+ RAM (para desplegar aplicaciones reales)
- 2+ cores CPU
- 50GB+ disco SSD
K3s consume:
- Base: ~512MB RAM (solo K3s)
- Con aplicaciones: Depende de tus aplicaciones
Si tienes menos de 4GB RAM, considera Docker Compose en lugar de Kubernetes.
—¿Puedo usar Kubernetes en un solo servidor?
Sí, absolutamente. K3s funciona perfectamente en un solo servidor. No necesitas múltiples nodos para empezar.
Ventajas de un solo servidor:
- ✅ Más simple (no necesitas configurar networking entre nodos)
- ✅ Menos recursos (no necesitas múltiples máquinas)
- ✅ Perfecto para aprender y homelabs
Cuándo necesitas múltiples nodos:
- Alta disponibilidad (si un nodo falla, otros siguen funcionando)
- Escalado horizontal (más capacidad)
- Producción enterprise
Para homelab, un solo servidor con K3s es perfecto.
—¿Qué es la diferencia entre Pod y Deployment?
Pod: Es la unidad básica de Kubernetes. Un Pod puede tener uno o más contenedores que comparten red y almacenamiento.
Deployment: Gestiona Pods. Un Deployment crea y mantiene Pods, los recrea si fallan, y permite escalado y rolling updates.
Analogía:
- Pod = contenedor (unidad básica)
- Deployment = docker-compose + auto-restart + escalado (gestor de pods)
¿Cuándo usar cada uno?
- Pods directamente: Casi nunca en producción (son efímeros, no se auto-recrean)
- Deployments: Siempre en producción (gestionan pods automáticamente)
Ejemplo:
# Pod: Si falla, no se recrea
apiVersion: v1
kind: Pod
# ...
# Deployment: Si falla, se recrea automáticamente
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 3 # Mantiene 3 pods siempre
—
¿Cómo expongo un servicio al exterior en K8s?
Tres opciones:
1. Port Forward (testing local):
kubectl port-forward svc/nginx-service 8080:80
# Acceder: http://localhost:8080
2. NodePort (acceso externo sin dominio):
apiVersion: v1
kind: Service
spec:
type: NodePort
ports:
- port: 80
nodePort: 30080 # Puerto externo (30000-32767)
# Acceder: http://<IP_DEL_NODO>:30080
3. Ingress (acceso con dominio, recomendado):
apiVersion: networking.k8s.io/v1
kind: Ingress
spec:
rules:
- host: app.tudominio.com
http:
paths:
- path: /
backend:
service:
name: nginx-service
port:
number: 80
# Acceder: http://app.tudominio.com
Para producción, usa Ingress. Es más limpio, permite SSL automático, y es el estándar.
—¿Los datos persisten si reinicio un pod?
Depende. Si usas volúmenes normales (emptyDir), los datos se pierden. Si usas PersistentVolume (PV), los datos persisten.
Sin persistencia (datos se pierden):
volumes:
- name: temp
emptyDir: {} # Volumen temporal, se pierde al reiniciar pod
Con persistencia (datos persisten):
volumes:
- name: data
persistentVolumeClaim:
claimName: my-pvc # Volumen persistente, datos persisten
Ejemplo práctico:
# Crear archivo en pod con PVC
kubectl exec -it deployment/my-app -- sh -c "echo 'test' > /data/test.txt"
# Reiniciar pod
kubectl delete pod -l app=my-app
# Verificar que archivo persiste
kubectl exec -it deployment/my-app -- cat /data/test.txt
# Deberías ver: "test"
Para bases de datos y aplicaciones con estado, SIEMPRE usa PersistentVolume.
—¿Kubernetes es más difícil que Docker Compose?
Sí, Kubernetes es más complejo que Docker Compose. Tiene más conceptos, más configuración, y curva de aprendizaje más alta.
Comparación:
| Aspecto | Docker Compose | Kubernetes |
|---|---|---|
| Complejidad | Baja (YAML simple) | Alta (múltiples conceptos) |
| Curva aprendizaje | 1-2 días | 1-2 semanas |
| Configuración | 1 archivo (docker-compose.yml) | Múltiples archivos (Deployment, Service, Ingress, etc.) |
| Conceptos | 5-10 conceptos básicos | 20+ conceptos (Pod, Deployment, Service, Ingress, PV, PVC, Namespace, etc.) |
Pero: Si ya dominas Docker, Kubernetes no es tan difícil. Los conceptos son similares, solo más abstractos.
Recomendación: Aprende Docker Compose primero. Cuando lo domines, Kubernetes será más fácil de entender.
—¿Cuándo debo usar Kubernetes vs Docker Compose?
Usa Docker Compose si:
- ✅ Tienes 1-10 servicios en un único servidor
- ✅ No necesitas escalado automático
- ✅ Un solo servidor es suficiente
- ✅ Quieres simplicidad máxima
- ✅ Prototipo rápido o desarrollo
Usa Kubernetes si:
- ✅ Tienes 10+ servicios que gestionar
- ✅ Necesitas escalado automático
- ✅ Requieres alta disponibilidad (múltiples nodos)
- ✅ Quieres rolling updates sin downtime
- ✅ Tienes equipo con experiencia DevOps/K8s
Regla de oro: Si tienes 3 o menos servicios en un único servidor, Docker Compose es suficiente. Si necesitas múltiples nodos, escalado automático, o alta disponibilidad, Kubernetes es tu camino.
Para homelab típico: Empieza con Docker Compose. Si creces y necesitas más, migra a Kubernetes.
—¿Cómo actualizo una aplicación en K8s sin downtime?
Rolling update automático: Kubernetes actualiza pod por pod sin downtime.
# Actualizar imagen
kubectl set image deployment/nginx-deployment nginx=nginx:1.25
# Kubernetes automáticamente:
# 1. Crea nuevo pod con nueva imagen
# 2. Espera a que esté listo (readiness probe)
# 3. Quita tráfico del pod viejo
# 4. Elimina pod viejo
# 5. Repite para cada pod
Ver estado del rollout:
kubectl rollout status deployment/nginx-deployment
Rollback si algo falla:
kubectl rollout undo deployment/nginx-deployment
Comparación con Docker Compose:
# Docker Compose: Manual (hay downtime)
docker compose stop nginx
docker compose pull nginx
docker compose up -d nginx
# Kubernetes: Automático (sin downtime)
kubectl set image deployment/nginx-deployment nginx=nginx:1.25
—
¿Qué es un Ingress y para qué sirve?
Ingress es el objeto de Kubernetes que gestiona el acceso HTTP/HTTPS externo a tus servicios.
Analogía: Ingress = reverse proxy integrado en Kubernetes (como Nginx o Traefik, pero nativo de K8s)
Ventajas:
- ✅ SSL automático: Cert-manager gestiona certificados Let’s Encrypt
- ✅ Sin configuración manual: No editas nginx.conf
- ✅ Declarativo: Todo en YAML, versionable en Git
- ✅ Multi-servicio: Un solo Ingress para múltiples servicios
Ejemplo:
apiVersion: networking.k8s.io/v1
kind: Ingress
spec:
rules:
- host: app1.tudominio.com
http:
paths:
- path: /
backend:
service:
name: app1-service
- host: app2.tudominio.com
http:
paths:
- path: /
backend:
service:
name: app2-service
K3s incluye Traefik como Ingress Controller por defecto. No necesitas instalarlo.
—¿Cómo veo los logs de mis aplicaciones en K8s?
Comandos básicos:
# Ver logs de un pod
kubectl logs <pod-name>
# Ver logs de un deployment
kubectl logs deployment/<deployment-name>
# Seguir logs (tail -f)
kubectl logs -f <pod-name>
# Ver logs del contenedor anterior (si reinició)
kubectl logs <pod-name> --previous
# Ver logs de todos los pods con label
kubectl logs -l app=nginx
Comparación con Docker:
# Docker
docker logs <container-name>
# Kubernetes
kubectl logs <pod-name>
Para múltiples pods (deployment):
# Kubernetes muestra logs de todos los pods del deployment
kubectl logs deployment/nginx-deployment
# Docker Compose muestra logs de todos los servicios
docker compose logs
—
¿Puedo usar docker-compose.yml en Kubernetes?
No directamente. Kubernetes no entiende docker-compose.yml. Necesitas convertir los servicios a objetos de Kubernetes (Deployments, Services, etc.).
Herramientas de conversión:
- kompose: Convierte docker-compose.yml a YAMLs de Kubernetes
- Manual: Crear Deployments, Services, etc. manualmente (recomendado para aprender)
Ejemplo con kompose:
# Instalar kompose
curl -L https://github.com/kubernetes/kompose/releases/download/v1.28.0/kompose-linux-amd64 -o kompose
chmod +x kompose
sudo mv kompose /usr/local/bin/
# Convertir
kompose convert
# Genera: deployment.yaml, service.yaml, etc.
Recomendación: Aprende a crear YAMLs de Kubernetes manualmente. Es mejor para entender los conceptos.
—¿Kubernetes consume muchos recursos?
K3s consume:
- Base: ~512MB RAM (solo K3s, sin aplicaciones)
- Con Traefik, CoreDNS, etc.: ~700MB RAM
Kubernetes estándar consume:
- Base: ~2GB RAM (solo Kubernetes, sin aplicaciones)
Comparación con Docker Compose:
- Docker Compose: ~100MB RAM (solo Docker daemon)
Recomendación:
- <4GB RAM: Usa Docker Compose
- 4GB+ RAM: K3s es viable
- 8GB+ RAM: K3s cómodo, puedes desplegar muchas aplicaciones
Para homelab típico (4-8GB RAM), K3s es perfecto.
—¿Qué es kubectl y cómo se usa?
kubectl es la herramienta de línea de comandos para interactuar con Kubernetes. Es como docker para Docker, pero para Kubernetes.
Comandos básicos:
# Ver recursos
kubectl get pods
kubectl get services
kubectl get deployments
# Aplicar YAMLs
kubectl apply -f deployment.yaml
# Ver logs
kubectl logs <pod-name>
# Ejecutar comandos en pods
kubectl exec -it <pod-name> -- /bin/bash
# Eliminar recursos
kubectl delete pod <pod-name>
Instalación:
- K3s incluye kubectl: No necesitas instalarlo por separado
- Kubernetes estándar: Instalar kubectl manualmente
Comparación con Docker:
# Docker
docker ps
docker logs <container>
docker exec -it <container> bash
# Kubernetes
kubectl get pods
kubectl logs <pod>
kubectl exec -it <pod> -- bash
—
Conclusión
Resumen de Puntos Clave
En este tutorial has aprendido:
✅ K3s es Kubernetes ligero – Perfecto para homelabs, instalación en 2 minutos
✅ Comparaciones Docker→K8s – Pod, Deployment, Service, Ingress, PV/PVC explicados con analogías
✅ Despliegue completo – WordPress + MySQL con persistencia e Ingress
✅ Comandos kubectl esenciales – get, describe, logs, exec, apply, scale
✅ Troubleshooting práctico – 5 problemas comunes con soluciones
✅ Mejores prácticas – Namespaces, resource limits, health checks, secrets
Próximos Pasos Recomendados
Para comenzar HOY:
- Instala K3s –
curl -sfL https://get.k3s.io | sh - - Despliega Nginx – Usa los ejemplos de GitHub
- Experimenta – Escala, actualiza, reinicia pods
Para profundizar:
- 📖 Lee: Kubernetes para IA: Cómo Desplegar Modelos ML en Producción
- 🎓 Aprende: Helm (gestor de paquetes), Prometheus + Grafana (monitoring)
- 🛠️ Practica: Despliega tus propias aplicaciones en K3s
Recursos Adicionales:
- Documentación Oficial Kubernetes: https://kubernetes.io/docs/
- Documentación K3s: https://k3s.io/
- kubectl Reference: https://kubernetes.io/docs/reference/kubectl/
- Ejemplos de Código: https://github.com/ziruelen/learningaiagents/tree/main/kubernetes/kubernetes-basico-homelab-guia-desde-cero
