K3s es una distribución de Kubernetes certificada por la CNCF que se distribuye como un único binario y consume mucha menos memoria que una instalación con kubeadm. Incluye de serie containerd, Flannel, CoreDNS, el Ingress Controller Traefik, metrics-server y un provisionador de almacenamiento local. En este tutorial instalarás un servidor K3s en Ubuntu 24.04, le unirás un nodo worker, configurarás kubectl para tu usuario y publicarás una aplicación de prueba con Ingress y almacenamiento persistente.
Requisitos previos
- Dos servidores con Ubuntu 24.04 LTS, por ejemplo dos VPS de CubePath: uno hará de servidor (plano de control) y otro de agente (worker). Si solo quieres un clúster de un nodo, basta con uno y puedes saltarte el paso 4.
- Un usuario no root con privilegios
sudoen ambos. - Al menos 2 GB de RAM y 2 vCPU en el servidor, y 1 GB de RAM en el agente. K3s arranca con menos, pero deja poco margen para tus cargas.
- Conectividad entre los nodos, idealmente por una red privada. En esta guía
your_server_ipes la IP del servidor que usará el agente para conectarse. - Nombres de host distintos en cada nodo: K3s rechaza dos nodos con el mismo nombre.
Paso 1: Preparar el firewall
K3s necesita varios puertos entre nodos. En el servidor, permite SSH, la API de Kubernetes, la red overlay de Flannel (VXLAN) y el kubelet. Sustituye 10.0.0.0/24 por la subred privada de tus nodos:
sudo ufw allow OpenSSH
sudo ufw allow 6443/tcp
sudo ufw allow from 10.0.0.0/24 to any port 8472 proto udp
sudo ufw allow from 10.0.0.0/24 to any port 10250 proto tcp
La documentación de K3s indica además que, con UFW activo, hay que permitir el tráfico de las redes de pods (10.42.0.0/16) y de servicios (10.43.0.0/16), que son los rangos por defecto:
sudo ufw allow from 10.42.0.0/16 to any
sudo ufw allow from 10.43.0.0/16 to any
Si vas a publicar aplicaciones por HTTP y HTTPS con Traefik, abre también esos puertos:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Repite en el agente las reglas de SSH, 8472/udp, 10250/tcp y las de las redes de pods y servicios. El agente no necesita el puerto 6443 abierto.
Notasi solo usas
kubectldesde el propio servidor, puedes restringir el 6443 a tu IP consudo ufw allow from your_admin_ip to any port 6443 proto tcpen lugar de abrirlo a todo Internet.
Paso 2: Instalar el servidor K3s
K3s se instala con un script oficial que descarga el binario, crea el servicio systemd k3s y añade los enlaces kubectl, crictl y ctr. Antes de ejecutarlo, crea el archivo de configuración para que el servidor arranque ya con los ajustes deseados:
sudo mkdir -p /etc/rancher/k3s
sudo nano /etc/rancher/k3s/config.yaml
Añade este contenido. write-kubeconfig-mode controla los permisos del kubeconfig que genera K3s, tls-san añade la IP o el nombre por el que accederás a la API al certificado, y node-label etiqueta el nodo:
write-kubeconfig-mode: "0600"
tls-san:
- "your_server_ip"
node-label:
- "environment=production"
Cada clave del archivo equivale a una opción de línea de comandos de k3s server sin los guiones iniciales. Descarga ahora el script de instalación y revísalo antes de ejecutarlo:
curl -sfL https://get.k3s.io -o k3s-install.sh
less k3s-install.sh
Ejecuta el script. Instala la última versión del canal stable y lee automáticamente /etc/rancher/k3s/config.yaml:
sh k3s-install.sh
El script usa sudo internamente cuando lo lanzas con un usuario normal. Comprueba que el servicio está activo:
sudo systemctl status k3s --no-pager
● k3s.service - Lightweight Kubernetes
Loaded: loaded (/etc/systemd/system/k3s.service; enabled; preset: enabled)
Active: active (running) since ...
Y que el nodo aparece como Ready (puede tardar unos 30 segundos):
sudo k3s kubectl get nodes
NAME STATUS ROLES AGE VERSION
k3s-server Ready control-plane,master 45s v1.3x.x+k3s1
Paso 3: Configurar kubectl para tu usuario
K3s escribe el kubeconfig en /etc/rancher/k3s/k3s.yaml, que solo puede leer root. Cópialo a tu directorio personal para usar kubectl sin sudo:
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown "$USER":"$USER" ~/.kube/config
chmod 600 ~/.kube/config
Comprueba que funciona y revisa los componentes del sistema:
kubectl get pods -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system coredns-... 1/1 Running 0 2m
kube-system helm-install-traefik-... 0/1 Completed 1 2m
kube-system helm-install-traefik-crd-... 0/1 Completed 0 2m
kube-system local-path-provisioner-... 1/1 Running 0 2m
kube-system metrics-server-... 1/1 Running 0 2m
kube-system svclb-traefik-... 2/2 Running 0 90s
kube-system traefik-... 1/1 Running 0 90s
Los pods helm-install-* en estado Completed son normales: son los trabajos que instalan Traefik a partir de los manifiestos incluidos.
Si quieres administrar el clúster desde tu equipo, copia ese mismo archivo y cambia server: https://127.0.0.1:6443 por https://your_server_ip:6443. Funciona porque añadiste esa IP a tls-san.
Paso 4: Unir un nodo agente
Los agentes se autentican con el token del servidor. Muéstralo en el servidor:
sudo cat /var/lib/rancher/k3s/server/node-token
K10abc123...::server:0123456789abcdef
En el nodo agente, descarga el mismo script, revísalo y ejecútalo con las variables K3S_URL y K3S_TOKEN. Cuando K3S_URL está definida, el script instala K3s en modo agente con el servicio k3s-agent:
curl -sfL https://get.k3s.io -o k3s-install.sh
less k3s-install.sh
K3S_URL=https://your_server_ip:6443 K3S_TOKEN=your_node_token sh k3s-install.sh
Sustituye your_node_token por el valor completo obtenido antes. Comprueba el servicio en el agente:
sudo systemctl status k3s-agent --no-pager
Y desde el servidor verifica que el nuevo nodo se ha unido:
kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP
k3s-server Ready control-plane,master 10m v1.3x.x+k3s1 10.0.0.10
k3s-agent1 Ready <none> 40s v1.3x.x+k3s1 10.0.0.11
Si los nodos se comunican por una red privada y K3s ha elegido la IP pública como INTERNAL-IP, añade node-ip: "ip_privada_del_nodo" a /etc/rancher/k3s/config.yaml en cada nodo y reinicia el servicio (k3s o k3s-agent).
Paso 5: Desplegar una aplicación de prueba con Ingress
Traefik ya escucha en los puertos 80 y 443 de los nodos gracias al balanceador integrado ServiceLB. Crea un manifiesto con un Deployment de Nginx, su Service y un Ingress:
nano demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo
spec:
replicas: 2
selector:
matchLabels:
app: demo
template:
metadata:
labels:
app: demo
spec:
containers:
- name: nginx
image: nginx:stable
ports:
- containerPort: 80
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: demo
spec:
selector:
app: demo
ports:
- port: 80
targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo
spec:
ingressClassName: traefik
rules:
- host: demo.your_domain
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: demo
port:
number: 80
Aplica el manifiesto y espera a que el Deployment esté disponible:
kubectl apply -f demo.yaml
kubectl rollout status deployment/demo
deployment "demo" successfully rolled out
Prueba el Ingress sin necesidad de DNS enviando la cabecera Host al servidor:
curl -H "Host: demo.your_domain" http://your_server_ip
La respuesta debe ser el HTML de la página de bienvenida de Nginx. Cuando apuntes un registro A de demo.your_domain a la IP del servidor, podrás abrirla en el navegador.
Paso 6: Usar almacenamiento persistente con local-path
K3s crea la StorageClass local-path, marcada como predeterminada, que guarda los datos en /var/lib/rancher/k3s/storage del nodo donde se ejecuta el pod. Crea un PVC y un pod que escriba en él:
nano pvc-demo.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-demo
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
name: pvc-demo
spec:
containers:
- name: busybox
image: busybox:stable
command: ["sh", "-c", "date >> /data/log.txt && sleep 3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: data-demo
kubectl apply -f pvc-demo.yaml
kubectl get pvc data-demo
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-demo Bound pvc-3f1c... 1Gi RWO local-path 20s
El PVC queda en Pending hasta que un pod lo usa, porque la StorageClass tiene el modo WaitForFirstConsumer. Comprueba que el archivo se ha escrito:
kubectl exec pvc-demo -- cat /data/log.txt
Importantelos volúmenes
local-pathviven en el disco de un único nodo. Si ese nodo cae, el pod no puede moverse a otro con sus datos. Para datos críticos usa una solución replicada como Longhorn o copias de seguridad externas.
Cuando termines, elimina los recursos de prueba:
kubectl delete -f pvc-demo.yaml -f demo.yaml
Mantenimiento: actualizar y desinstalar
Para actualizar K3s, vuelve a ejecutar el script de instalación con la misma configuración. Actualiza primero el servidor y después los agentes:
curl -sfL https://get.k3s.io -o k3s-install.sh
sh k3s-install.sh
En los agentes repite el comando con K3S_URL y K3S_TOKEN. El script instala también desinstaladores: /usr/local/bin/k3s-uninstall.sh en el servidor y /usr/local/bin/k3s-agent-uninstall.sh en los agentes. Ambos borran el servicio, los contenedores y los datos del clúster.
Solución de problemas
El servicio k3s no arranca. Revisa el registro del servicio; un error de sintaxis en config.yaml o el puerto 6443 ocupado aparecen aquí:
sudo journalctl -u k3s -n 100 --no-pager
sudo ss -tlnp | grep 6443
El agente no se une al clúster. Mira el registro del agente y comprueba que llega a la API. La respuesta pong confirma la conectividad:
sudo journalctl -u k3s-agent -n 100 --no-pager
curl -ks https://your_server_ip:6443/ping
Si aparece un error de token, vuelve a copiarlo completo desde /var/lib/rancher/k3s/server/node-token. Si el nodo aparece pero los pods de nodos distintos no se ven entre sí, el puerto 8472/udp está bloqueado entre ellos.
El PVC se queda en Pending con un pod ya creado. Revisa el provisionador y el espacio en disco del nodo:
kubectl -n kube-system logs deploy/local-path-provisioner
df -h /var/lib/rancher/k3s
Conclusión
Tienes un clúster K3s con un servidor y un agente, kubectl configurado para tu usuario, una aplicación publicada con Traefik y un volumen persistente local. Como siguientes pasos puedes instalar cert-manager para obtener certificados TLS de Let's Encrypt en tus Ingress, pasar a un plano de control de alta disponibilidad con tres servidores y etcd embebido (cluster-init: true en el primero), o definir requests y límites de recursos para tus cargas.
