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 sudo en 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_ip es 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.

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

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.